Aeva / m.grl

Midnight Graphics & Recreation Library
http://mgrl.midnightsisters.org
GNU Lesser General Public License v3.0
44 stars 3 forks source link

Allow any loaded gani to be called from any gani node; make SETBACKTO work #240

Closed wijnen closed 7 years ago

wijnen commented 7 years ago

With jta files, a single object can contain multiple actions. With gani files, every action must be loaded separately and manually linked with add_gani(). It would be nice to have a way to load an object that already contains multiple actions. So my questions are:

One implementation that comes to mind is a new file type that contains names of ganis, which will be loaded as actions to the object.

Aeva commented 7 years ago

I think this is a good idea. In historical usage of the format, the basic animations for characters would already be stored on disk, and new ones would be streamed in. A given game object could be displayed as any gani, though the results might look funny if the attributes weren't consistent.

M.GRL obviously has some different constraints, the big one that everything needs to be streamed in at run time, and there is no "I already have this". The streaming model of M.GRL is in part based on the game that ganis are from, but there is no persistent local storage beyond the browser cache.

I've been thinking for a while it that it might be useful to have some kind of manifest fileformat, where when the manifest asset is loaded, it immediately queues up several other assets. This could be related, though it might make sense to be its own thing.

We could also modify the gani format slightly, so that, say, an idle character animation can specify that it also should pull in several other animations if they are available, so that you don't need to explicitly add them. Or, maybe M.GRL could automatically group compatible ganis when loaded, allowing for the add mechanism to happen automatically. This might still necessitate adding in some metadata to the format, though the sprite definities or attributes block might be enough for this.

I don't want to modify the format too aggressively for the sake of keeping compatibility, but I think there are some ways in which we could add metadata into the existing files without breaking backwards compatibility. Backwards compatibility is mainly sentimental, though.

wijnen commented 7 years ago

Here's another idea which is even backwards compatible: the gani format expects EOF after ANIEND; a simple extension to the format would be to allow a file to contain multiple concatenated animations. Add the name of every animation to the block as "NAME idle", which defaults to the filename (as it does now) and I think it does everything I want.

Aeva commented 7 years ago

I think the way graal handled ganis was if it didn't recognize something on a line, it just ignored it. Also, a later iteration of the game added in a field for specifying revisions of the format, which according to the docs, is GANI0001 at the top of the file - so maybe we should have our own version field, like MGRL0001 =)

I would say for now, just throw whatever new properties you want into the part where "continuous" and the likes are declared, and if that breaks backwards compatibility, then we can revise. (Or if you already went and did something else, thats cool too).

Aeva commented 7 years ago

I verified some things:

  1. if both continuous and loop are unset, then when the animation finishes, it should then play the "setbackto" animation. If either continuous is set and loop is not, the animation will just remain on the last frame when it finishes. If loop is set, then the animation loops.

  2. we can just add arbitrary properties, and the old software won't trip up over it - so I would say that the best place to add new things would just be in the 'property bag' between the sprite defs and the ANI/ANIEND block, to be consistent.

  3. Adding the MGRL0001 header at the top doesn't hurt anything either.

wijnen commented 7 years ago

I have yet another idea: every gani node has a "current" animation, and play() will change that animation into any other, without the need for add_gani(). So in terms of the current implementation, it means every gani that is loaded is implicitly added to every gani that is instanced. There's no need to actually add them; instead, play() should simply search all ganis for one with the correct name (that already happens for SETBACKTO). It would also be possible to try loading a gani when it is referenced through play, but I think that would let people get away with bad code, and they should fix it instead (by adding explicit load calls).

Aeva commented 7 years ago

So, to make sure we're on the same page, I'm going to restate what I think you are saying:


For review:

The asset objects (the thing you get from please.access) is meant to represent a file 1:1 - its sole responsibilities are to be able to create GraphNode objects via its instance method and to provide access to the raw file data. GraphNode's themselves are meant to represent game objects, and contain data that a renderer can use to present the character. GraphNodes do not need to represent any particular asset, or can represent several.

The handler that creates the gani asset objects could be used to also immediately request referenced animations to be downloaded, resulting in more gani asset objects being created, and to do whatever pre-processing is needed right away.


What you proposed sounds like one of two things to me:

variation 1 Upon instancing an animation that references another (via SEEALSO or SETBACKTO), the result is a GraphNode with the play machinery that allows it to immediately play any of the animations referenced by the instanced one, without calling something like "add_gani(...)".

Or

variation 2 Upon loading a gani asset, the gani asset produces all of the machinery needed to represent and render it's animation (and only its own), and stores that on the asset object. GraphNode instances of gani assets would then only book keep its own animation progress, what the values are for its gani "attributes", and point to the animation data needed to render its current animation. The gani instances then don't actually meaningfully differ from one another, and can play any animation without having to attach it first.

Either of these versions are fine for now. I think variation 2 is best for a number of reasons, main ones being that it has better separation of concerns / code can be more concise; it would simplify what is required of renderers; the edge case for trying to play an asset that isn't loaded is easier to handle; and better code reuse means the javascript vm can optimize easier.

wijnen commented 7 years ago

Variation 1 is what I think the proposal so far was; variation 2 is what I tried to propose just now. I agree that variation 2 is better, so I'll see if I can get that working.

Thanks for making sure there are no misunderstandings. :)

Aeva commented 7 years ago

Great! I'm excited to see what you come up with :D

Aeva commented 7 years ago

Closing this out since the PR has been merged, assuming it satisfactorily resolves this? If not, feel free to reopen.