loopier / animatron-godot3

Yet another implementation of Animatron, but in Godot
GNU General Public License v3.0
17 stars 1 forks source link

Animation sequences, ranged loops and modifiers #37

Open loopier opened 1 year ago

loopier commented 1 year ago

create animation sequences that can be chained. For example, a character can run, jump, stop, jump, run again. Not sure about the syntax, but an array comes to mind. Also, loops could be played in a range, from frame X to frame Y. Modifiers would be also nice, like play direction and speed. Sequences would ideally include that, so a range of run.range(3,end) could be chained to a jump.range(start, 4), or run.loop(3 times) then jump.loop(4 times).

We discussed it a bit:

loopier commented 1 year ago

Your suggestion of "looping/repeating OSC commands/lists, or behaviours (may not be just animations). When one finishes, start next (without having to worry about timing/duration)" sounds like the best idea to me.

It would allow to do all kinds of stuff. I was thinking on adding some kind of modulation with embeded OSC commands that might be possible with this. It might be a possible derivation of this same idea (see the LFO's proposal in the Cool Pool of Ideas .

Godot's signaling and finite state machine systems might be useful in this regard.

loopier commented 1 year ago

How about braking down the /play command into atoms? Something like:

then we could use def to define sequences:

def /seq/1 actor
    /reverse $actor false
    /range $actor 0 inf # start to finish
    /repeats $actor 4
    /on/finished $actor /seq/2 # maybe passing the $actor as argument?

def /seq/2 actor
    /range $actor 4 12
    /reverse $actor true
    /repeats $actor 3
    /on/finished $actor /seq/1 # would loop between /seq/1 and /seq/2

Some advantages of this approach I see:

loopier commented 1 year ago

Should we use AnimationPlayer or AnimatedTexture instead of AnimationSprite? Is this new? Why didn't we choose these and went for AnimationSprite?

totalgee commented 1 year ago

I don't recall, but I imagine AnimatedSprite was chosen as an initial implementation because it was simpler.

For more details of the differences and how to use it and AnimationPlayer, see here. I think an AP could even be used to control the AS, if desired (for more complex cases).

loopier commented 1 year ago

Can we take advantage of the /def syntax for animation sequences? A collection of commands to be triggered on entering a frame sounds reasonable, and consistent with the /def syntax. Maybe something like:

/seq actor
    /onframe 40  /create $actor another-anim
    /onfinished /create $actor yet-another-anim
    /onframe 100 /create $actor yet-another-anim

if we want more complex behaviours, we can also use /def:

/def /seq/1 actor
    /create $actor one-anim
    /stop $actor
    /frame $actor 30

/def /seq/2 actor
    /create $actor another-anim
    /reverse $actor
    /frame $actor 10

/seq actor
    /onframe 40  /seq/1 $actor
    /onframe 20 /seq/2 $actor

It looks simple and intuitive.

We would need an array of [frame, cmd] pairs. Cheking it within MetaNode._on_frame_changed(), it would send the command and advance one index position in the array on arriving at the specific frame. I'm not sure this is very efficient.

How would we manage loops? Repeating commands? Adding a /loop times cmd?

/seq actor
    /onframe 40  /create $actor another-anim
    /onfinished /create $actor yet-another-anim
    /onfinished /create $actor yet-another-anim
# or
    /frame $actor 0
    /onframe 100 /create $actor yet-another-anim
totalgee commented 1 year ago

How would onframe be different from wait?

loopier commented 1 year ago

isn't wait in seconds?

loopier commented 1 year ago

onframe would specify the frame number when the message would be sent.

totalgee commented 1 year ago

Okay, I get it, but what about (in your example) if the actor is not playing (or doesn't exist yet), or if the specified frame isn't in the animation? It will never get to that frame.

loopier commented 1 year ago

If the actor is not playing I guess we would need to use wait. Not sure how to mix both ways. Any suggestion?

I guess that by "the specified frame isn't in the animation" you mean if it's shorter. Right now the frame count wraps back to 0 if the value is greater than the number of frames in the animation.

loopier commented 1 year ago

Do we want a global sequencer or a per-actor one?

Somehow I've always thought of per-actor, but that was my first approach to MIDI and sound and it was better to do it global.

totalgee commented 1 year ago

Off the top of my head, I think one per actor makes more sense. There may be a case (also) for some kind is global or hierarchical sequencing (when A finishes playing, do something with B), but maybe we should start simpler.

loopier commented 1 year ago

I think this can also be done with per actor approach. If the syntax is /onframe x /acommand actor args the actor can be any actor. So we could make some actors trigger other actors. A sequence would be an actor triggering itself.

loopier commented 1 year ago

ah, but these lacks the actor that holds the sequence... mmmm A quick fix is /onframe actorA x /cmd actorB args. But isn't it a bit confusing? actorA and actor B will be the same actor for sequences.

Maybe a /def would fix this? (Always back to defs):

/def /onsequenceframe frame cmd actor args
    /onframe $actor $frame $cmd $actor $args

Not at all sure about this.

loopier commented 1 year ago

Another solution is to name the actor command that triggers commands like:

/trig actor frame cmd

where cmd would be any command (just like with def, sound and midi). It would be different from /onframe frame cmd which would hold the sequence commands...

This is essentially the same as before but with different command name.

loopier commented 1 year ago

I just saw that this doesn't work. /onframe frame cmd is consistent with def, sound and midi but not with actor commands.

It should be /onframe actor frame cmd, making it exactly like the /trig command I just proposed. Back to square one.

loopier commented 1 year ago
if the specified frame isn't in the animation? It will never get to that frame.

I see what you mean by this, now. Not sure what the solution is. Any suggestions?

loopier commented 1 year ago

What's the reasoning behind the syntax for /def /cmdname var1 var2 "/cmd1 ..." "/cmd2 ..."?

loopier commented 1 year ago

I have implemented the /onframe in c458134.

It can be used to create sequences with:

/create actorA animA
/onframe X /create actorA animB

Let me know what you think.

loopier commented 1 year ago

I implemented the /onfinish command (with its corresponding /onfinish/free counterpart) in https://github.com/loopier/animatron/commit/606eec1455e1a15eb6590da16969c5343ec33fa4. It works just like /onframe but without the need to specify a frame number. It triggers on the last frame.

I tried creating a sequence with:

/create acotrA animA
/onfinish actorA /create actorA animB
/onfinish actorA /create actorB animC

if animB has more frames than animA, the /create command is in a frame which no longer is the last one. So when animB reaches the end, it changes to animC but on the former "last frame" it switches back to animB, then to animC again on the last frame, etc.

Not sure what would be a good way to create sequences.

totalgee commented 1 year ago

Not sure I totally understand the problem. What about if the command you trigger from /onfinish is a /def that contains /create actorA animB followed by /onfinish actorA /create actorA animC -- so it doesn't set the next "onfinish" until the next animation starts to play? (I'm not sure if an actor can have more than one "onfinish" at a time, or it gets overwritten each time you call it...if the existing "onfinish" is not removed when it executes, then it would also need to be removed.) Not sure if that's the most elegant way to handle sequences in general, though. Maybe there needs to be a higher-level structure where you can set up and store the sequence for an actor. (or maybe we should be using some built-in Godot animation thing for this?)

loopier commented 1 year ago

I agree this is not the most elegant approach to create sequences. I thought it was good enough because we don't need to implement new code, but it's not working so we'll have to find out something else, probably at the Godot level as you suggest.

The /def solution you propose I already tried, but it had issues. I'd explain why, but I reimplemented this and I no longer have these issues. I'm not sure we can create sequences, but at least it's not buggy as before. The commands are triggered on the last frame no matter how long is the animation.

loopier commented 1 year ago

I'm not sure if an actor can have more than one "onfinish" at a time, or it gets overwritten each time you call it yes, it can have more than one "onfinish" at a time. We might want to trigger different commands every time it reaches the end, like playing another actor or something

totalgee commented 1 year ago

yes, it can have more than one "onfinish" at a time.

In that case, is it possible to free a particular one, or does that command free all "onfinish" for the actor, or just the first/last one?

loopier commented 1 year ago

It holds a dictionary called finishCmds, the keys being /cmd/actor and the values being the commands themselves (/cmd actor value). For example:

/onfinish actorA /scale actorA 0.5

would add a key-value pair to the finishCmds dictionary:

{ /scale/actorA : ["/scale", "actorA", 0.5] }

They need to be freed with /onfinish/free actorX /cmd actorY, where actorX and actorY can be the same actor, or not. In the previous example the command to free would be: /onfinish/free actorA /scale actorA. Does it make sense?

loopier commented 1 year ago

Maybe there needs to be a higher-level structure where you can set up and store the sequence for an actor. (or maybe we should be using some built-in Godot animation thing for this?)

I looked a bit into AnimationTree. It seems to be the structure we're looking for, but it does the same we are doing now. I'm not sure it's worth using it. It's almost the same amount of code, or so it seems. And, to me, the OSC approach seems more flexible (being able to trigger other things through random OSC messages at random frames). Anyway, it's good too know we're not that far off, and should consider using it.

loopier commented 1 year ago

I implemented a custom state machine in the MetaNode (https://github.com/loopier/animatron/commit/8c82d46c5640d5964cd31bb5e9c209f7d78c7779) to chain animations. The user can add possible next animations, like in SC's Pfsm. The current state is the animation name, and next possible states is an arbitrary list of other asset names.

/state/add actor anim next1 ... nextN
/state/free actor anim
/list/states

The state change is invoked in on_animation_finished. The animation is finished when it reaches the last frame. If a range is set with /play/range actor loopStart loopEnd, the animation finished is called when entering the frame set by loopEnd.

I think it would be interesting to apply this with commands as well. This does not substitute the /onframe and /onfinish commands, but I think it's more intuitive and might be more efficient as well.