With the ongoing development in Animation Sheets, I’ve also formed a command syntax to allow flexibility in how actions are sequentially executed, while also being aware of sequence animation.
The flow in C2 is a bit convoluted so I want to document this for future reference.
This is the variable that contains a sequence of ‘intended actions’ An example intend_list looks like this:
An action_prefix can be ‘a’, ‘p’, ‘w’, ‘s’. This corresponds with ‘action’, ‘pose’, ‘weapon’, and ‘speed’, respectively. I’ll refer to this as SWAP for simplicity.
Actions can be move, wepi, wepo, shoot, idle.
Pose can be up, dn.
Weapon can be u, as, am, al (this is not yet implemented as of writing)
Speed can be walk, run.
C2 logic flow
Like I said, the flow jumps around.
The first, we start with a given animation. An animation is defined by SWAP, and SWAP are actually something like states. The SWAP is referred to in order to make the right decision which animation is to be played.
So even at the beginning, the SWAP is initialised manually and then PlayerSetAnimation, which is the function that does much of the decision-making is run, and in constantly run.
How it works
At the beginning of PlayerSetAnimation, the first item () in intend_list is read and then popped. (Since it is a string, it is implicitly tokenised; when I say ‘popped’ I mean that the first token is popped, since it is not really an array, though it is treated as such.)
The popped data is called command. The command is queried if it is an Action. If is not an Action, then the state variables of the SWP is set. For example, if the command is
s:run, then the intend_speed variable, which is the internal C2 state variable for controlling actual speed, is set to
One important note is that the intend_list commands continue to be popped until an Action is encountered (so that SWP are processed as state variable changes only).
However, if it is an Action, then it starts querying the SWP variables, then forming an appropriate lookup to the animation map list.
If it is Speed or Pose change then after setting the state variables, it will determine a lookup but uses the nominal animation with changed Pose or Speed variables.
The complicated bit
The complication lay in the combination of the implicit ‘next animation’ of one animation. Some important things to remember:
- An animation, whether it has a sequence or not, will default to looping itself.
- In the animation map, a ‘next animation’ can be specified. This makes one animation inherently connected to another.
I’ll demonstrate different intend_list examples and how they are processed, and what goes on beyond the PlayerSetAnimation function.
Given an intend_list:
This is used when switching from a small weapon to a medium weapon, and only applies when a small weapon is equipped.
When a:wepi is popped, ‘wepi’ is the intend_anim, which is the keyword. SWP variables are queried (although in this case only the pose and weapon are relevant). Then the animation lookup is set, in this case ‘am upwo’ (medium weapon, standing weapon out).
Remember that the intend_list is popped, so it now looks like this.
Then the lookup is used to get the seq_start/seq_end, and other variables needed to play the animation. The next_anim and is_seq variables are also populated.
Then PlayerPlayAnimation is called. What this function does is simply play the animation, and starts the playhead at the appropriate place. It doesn’t check anything; it just makes the animation run.
Then there is a On frame change C2 trigger, which calls the PlayerCheckAnimation function. This function checks to see if the sequence as ended. If the sequence’s parameters have been satisfied, it calls PlayerAdvanceSequence.
PlayerAdvanceSequence is reponsible for determining what to do next. If there is a sequence of animation to be played (ie segmentations of the animation to be played discretely), then this function increments the sequence index. This is important, because as long as there is a sequence to be played, the system will continue to play the sequence.
If however, the sequence has been played out PlayerAdvanceSequence decides several things. First, does the current animation have a ‘next_anim’ name? In this example, the ‘wepi’ animation has ‘idle’ for its ‘next_anim’.
So if there is a ‘next_anim’ name, the next thing is to determine if there are any other Actions in the recently popped intend_list. In our case, yes. Remember our intend_list:
‘a:wepo’ is still present. So here, nothing special happens. The above intend_list is retained, and PlayerSetAnimation is called again.
Back to PlayerSetAnimation
When it is called again it sees ‘w:am’, and pops that. But we already know that PlayerSetAnimation will always keep on popping non-Action commands (setting variables as commanded by SWP). So after popping ‘w:am’, it sees the final command ‘a:wepo’ and pops that.
When the last is popped, intend_list is blank. But the whole process is repeated: the lookup is made, and then run. At some point, we end up in PlayerAdvanceSequence again, and then from here we ask the same questions. Does it have a ‘next_anim’ variable? Yes: ‘idle’.
Does it have any other Action in intend_list? No, it’s blank. When that happens, the default ‘next_anim’ is prepended to the intend_list (although because it’s blank, it doesn’t matter). Then the intend_list looks like:
Then PlayerSetAnimation is called with that intend_list, in which is goes back to idle.
Let’s try another intend_list going through the same process:
‘a:wepi’ is processed and popped first in PlayerSetAnimation. Because it’s an Action, a lookup is immediately processed for it. Then the intend_list looks like:
After the lookup is processed, it is played and then checked like Example 1.
Eventually we arrive at PlayerAdvanceSequence again when the ‘weapon in’ animation sequence is finished. The ‘weapon in’ animation has ‘idle’ for its ‘next_anim’.
PlayerAdvanceSequence queries if there are still any Action commands in the intend_list. There are none (ie ‘w:u’). So what happens is that the ‘next_anim’ is appended to the intend_list so that the PlayerSetAnimation knows it is the right time to use the ‘next_anim’. The intend_list now looks like this:
So when PlayerSetAnimation is called with that intend_list, it processes and pops ‘w:u’, which switches the weapon to ‘unarmed’, and then processes/pops the Action ‘idle’. This completes the command sequence for this example.
Given the this intend_list:
These are just Pose and Speed changes, so they are considered separately.
Pose and Speed commands change the intend_pose, and intend_speed variables. But a lookup must also be made for them in the same way Actions are. But the main difference is that in Pose and Speed, they are popped and processed immediately at the beginning with the setting of the variables. In our example, before a lookup is created, the intend_list would be empty, though the variables have been set (ie intend_pose=”dn”, intend_speed=”run”)
The lookup is made in the same place where Actions are. The main difference is that Speed and Pose uses the state variables to create the lookup. And just as importantly, it relies on the current PLAYER.anim variable to know what the current animation is.
PLAYER.anim is an instance variable in the sprite which contains the ‘base’ animation which specifies only the Pose and the Action. Some examples:
- If player is idle, unarmed, is standing, PLAYER.anim=”up idle”.
- If player is idle, unarmed, is crouched, PLAYER.anim=”dn idle”.
- If player is idle, small weapon drawn, crouched, PLAYER.anim=”dn idle”
- If player is walking, standing up, weapon drawn, PLAYER.anim=”up move w”
- If player is walking, crouching, unarmed, PLAYER.anim=”dn move w”
- If player is intending to run, crouching, unarmed, PLAYER.anim=”dn move w”
Note that PLAYER.anim doesn’t express whether a weapon is drawn or not.
Using PLAYER.anim, we can get the nominal state of movement. Then is a Pose change is required, then the Pose aspect of the PLAYER.anim is modified and PlayerPlayAnimation is called.
The rest goes through the same process again.
A variable called
do_not_check is used for bypassing actual checking of frames during OnFrameChange. The reason behind this is because there is not much frame control when I switch animation folders. C2 only allows two options: start at the beginning of the animation folder, or the current frame. But because I’m using it as a map rather than as sequence, I’m jumping from one folder to another, so the frame numbers become arbitrary, based on the animation map. So either frame method is useless in this case. What was actually happening was that the frame being queried as the wrong value; when the the animation folder was switched to, and the play_start and play_end variables were populated (through the values in the map), the OnFrameChange event was triggered. The current AnimationFrame, at this point, is likely to be wrong, since I can only begin at the start, or at the current frame integer (which won’t correspond to a new animation folder). Therefore, I needed a way to prevent the check before I could properly set the frame at the next tick.
To do this, before calling an animation folder, I first put
do_not_check=1, then call the animation folder (ie ‘Set animation (start at the beginning’). This has the instant effect of triggering OnFrameChange. But a condition checking the
do_not_check variable will bypass it.
The main ideas are
- intend_list is the the entry point for all animation changes
- intend_list is popped and data is put into PLAYER.intend_anim
- intend_anim is used to process what command this is
- If a command is an Action, is processed normally
- An animation lookup is constructed from the command itself
- In addition, the other SWP variables are used
- When a sequence has played out a check is made if there are still an Action command in the intend_list, to know whether the ‘next_anim’ animation needs to be played out.
- If there are still Action commands, then the intend_list is fed back in PlayerSetAnimation.
- If there are no other Action commands, the current animation’s ‘next_anim’ name is appended to intend_list and then is fed back to PlayerSetAnimation.
- If a Pose or Speed change is made, the intend_pose and intend_speed variables are changed. Then the command is made a lookup using the nominal animation that is currently playing. The nominal animation is found out by referencing the PLAYER.anim variable.