Busting Moves! Bring Your Characters to Life with Sequences


Busting Moves! Bring Your Characters to Life with Sequences

Hi there! In this tech blog we’re going to design animations for our player character using Sequences. This technique makes use of multiple sprites to create a customized attack sequence, and also includes a hitbox (which can be animated too) so you can control its attack range and power - basically allowing you to have everything in one place!

undefined

This post is divided into the following sections:

  1. Basic Attack Sequence (+ Hitbox)
  2. Playing the Sequence using GML
  3. Heavy Attack Sequence (+ Area of Effect)
  4. Idle Sleep Sequence
  5. More Attack Sequences (+ Combining Attacks!)

The base project is a simple top-down game where the character has idle and walking animations. We’ll be adding attacks to this character completely using Sequences, without any animation code in the player itself!

undefined

During an attack animation, the player instance will be disabled so we can only see the Sequence.

For an introduction to Sequences, check out the following resources:

Basic Attack Sequence (+ Hitbox)

For our first Sequence, we’ll simply place the attack sprite into a new Sequence (we’ll call it seqAttack1) and set the length of the Sequence to match the animation (in my case, 42 frames):

undefined

Note that we’re placing the sprite directly into the Sequence, without using an object, since we don’t need any object interaction for these attack animations (except for the hitbox, which we’ll get to in a moment).

Make sure to set the initial position of the player to (0, 0) in the Sequence, so that we get a seamless transition when we play the Sequence at the player instance’s origin. Essentially, the origins of your sprite and Sequence need to match so the transition from instance to Sequence looks good!

Hitbox

We’re also going to place a hitbox in this Sequence, allowing the player to attack enemies. For this I’ve created an object called oPlayerHitbox, and added a collision event in my enemy object to reduce its health when it collides with a player hitbox:

/// oEnemy - Collision event with oPlayerHitbox
if (!attackable) exit;

hp --;
instance_create_depth(x, y, depth, oAttackEffect);

attackable = false;
alarm[0] = 30;

var _dir = point_direction(oPlayer.x, oPlayer.y, x, y);
moveX = lengthdir_x(knockSpeed, _dir);
moveY = lengthdir_y(knockSpeed, _dir);

if (hp <= 0) {
	sprite_index = sEnemyDie;
}
else {
	image_alpha = 0.6;
}

This event is reducing the health of the enemy, and creating an “attack effect” instance just as a visual effect for the enemy being hit. It then sets the attackable variable to false, which exits the event (at the top) as long as it remains false, so the enemy can’t be hit. Then Alarm 0 is set to 30, which resets attackable to true so the enemy can be attacked again.

After that we have some basic code to assign knockback to the enemy’s movement variables, and set the sprite information based on the hp. If the hp is <= 0 then the sprite switches to the dying animation. After that the instance is destroyed in the Animation End event if the instance is on that dying animation. If the enemy hasn’t died, its alpha is lowered, which is also reset to 1 in the Alarm 0 event.

Let’s place the hitbox object in our Sequence now. Drag it in as a new track and make sure to start its Asset Key only when the animation gets to the attack frame:

undefined

You can see in the Canvas how the hitbox only covers a certain portion of the sprite, as that is the area that is able to hit enemies. You can adjust this to your heart’s content and even animate it so it matches the movement in the animation!

undefined

Playing the Sequence using GML

Enabling/Disabling the Player

We’re going to create some functions in our player object to handle the animation, and make sure that the player itself is disabled while the Sequence is active. Let’s handle that first, by creating the following variables and functions in the Create event:

enabled = true;

Enable = function () {
	enabled = true;
	image_alpha = 1;
}

Disable = function () {
	enabled = false;
	alarm[0] = 1;
	moveX = 0;
	moveY = 0;
}

enabled controls whether the player is enabled or not. Calling Enable() will set that variable to true and the instance’s alpha to 1 (to make it visible again).

Calling Disable() will set enabled to false, set Alarm 0 to run after 1 step and set the player’s movement variables to 0 so it stops. Alarm 0 will set the image_alpha to 0 so the player disappears. We’re using an Alarm for this as there is a 1-frame delay in the Sequence appearing, which is why we can’t set the alpha to 0 immediately as that will result in an empty frame where the player can’t be seen at all.

Let’s open the Step event and make sure the player can’t be controlled if it’s disabled. At the top of the event, I’ll add the following code:

if (!enabled) exit;

This will exit the event if enabled is false, meaning that the rest of the event will not run. Of course, this only works if your movement code is in the Step event. If there are any other events that you would like to disable as well, you can put this same line at the top of those events and they won’t run for as long as the player is disabled.

Let’s now create some new functions to handle the Sequence animation!

Playing & Managing the Animation

In the Create event, I’ll create the following variables and functions:

activeAnimation = -1;
sequenceLayer = -1;
activeSequence = -1;

StartAnimation = function (_sequence) {
	activeAnimation = _sequence;
	sequenceLayer = layer_create(depth);
	activeSequence = layer_sequence_create(sequenceLayer, x, y, _sequence);
	layer_sequence_xscale(activeSequence, image_xscale);
	
	Disable();
}

CheckAnimation = function () {
	if (activeSequence == undefined) return;
	
	if (layer_sequence_is_finished(activeSequence)) {
		layer_sequence_destroy(activeSequence);
		layer_destroy(sequenceLayer);
		
		activeAnimation = -1;
		activeSequence = -1;
		sequenceLayer = -1;
		
		Enable();
	}
}

Let’s look at the variables first. activeAnimation stores the Sequence asset ID that is being played at the moment, sequenceLayer stores the ID of the layer that is playing the Sequence, and activeSequence stores the Sequence element ID that is playing on that layer.

Let’s now look at the rest of the code, line-by-line:

StartAnimation = function (_sequence) {
	activeAnimation = _sequence;

The StartAnimation() function is used to start a Sequence animation and takes the Sequence asset as an argument. That Sequence asset is then assigned to the activeAnimation variable.

    sequenceLayer = layer_create(depth);

Then we create a new layer at the player’s current depth and store that layer’s ID in the sequenceLayer variable. I am only doing this because my player instance does not belong to a layer and has a variable depth - this is due to the following code in my manager object:

with (all) {
	depth = -bbox_bottom;
}

This changes the depth of each instance based on the Y coordinate of its bottom bounding box edge, for easy depth-sorting between the instances. Because of this, those instances no longer belong to any certain layer because their depth keeps changing. This is why we’re creating a new layer to play the Sequence so that it appears at the correct depth, however if you are not changing your instance’s depth, then you don’t need to create a new layer and can simply create the Sequence on the instance’s layer, using the layer variable.

Let’s continue looking at our code:

    activeSequence = layer_sequence_create(sequenceLayer, x, y, _sequence);
	layer_sequence_xscale(activeSequence, image_xscale);
	
	Disable();
}

Here we’re creating the Sequence on our new layer, at the instance’s x and y position. The ID of the created Sequence element (“elements” exist on a layer) is stored in the activeSequence variable. We’re then calling the layer_sequence_xscale() function to change the horizontal scale of the Sequence to match the instance’s scale (so that if the player is facing left, the Sequence also faces left -- of course this only works if you are using the image_xscale variable to flip it). Finally we’re calling Disable() to disable the player instance.

Let’s look at the second function now:

CheckAnimation = function () {
	if (activeSequence == -1) return;

This function will be used in the Step event, to check whether the active Sequence has ended. Before doing anything, it checks if the activeSequence variable is -1 (meaning that no Sequence is active at the moment) and in that case, calls return to end the function.

If there is an active Sequence then the function will continue, and come to the following code:

    if (layer_sequence_is_finished(activeSequence)) {
		layer_sequence_destroy(activeSequence);
		layer_destroy(sequenceLayer);
		
		activeAnimation = -1;
		activeSequence = -1;
		sequenceLayer = -1;
		
		Enable();
	}
}

Here we’re checking if the Sequence has finished, and when it has, we destroy it and the layer that holds it. Then we reset all three variables that hold information regarding the active Sequence, and enable the player again.

Let’s open the Step event now to call the CheckAnimation() function. Add a call to the function at the top of the event before the “exit” line, so it runs even when the player is disabled (as that is when it’s actually needed):

CheckAnimation();    // New line
if (!enabled) exit;  // Old line

Finally, let’s add code in the same event to start the attack animation. After the “exit” line (so it only happens while the player is active), we’ll add the following code:

// Attack 1 & 2
if (keyboard_check_pressed(vk_space)) {
	if (keyboard_check(vk_shift)) {
		StartAnimation(seqAttack1_Heavy);
	}
	else {
		StartAnimation(seqAttack1);
	}
}

This block runs if the Space key is hit. If the Shift key is also held down (creating the combination Shift+Space) it starts the seqAttack1_Heavy Sequence (which we’ll create in the next section). If Shift is not held down (meaning only space is pressed) then it starts the seqAttack1 Sequence, which we have just created. So you can see that we’ll also add a heavy variant of our attack that runs when Shift is also held down!

This should now play our animation when you hit Space, and the hitbox should be able to hit enemies. You’ll also notice that the Sequence flips based on the player’s xscale, which is perfect:

undefined

You’ll probably not want the hitbox to be visible in the game, so make sure to make it invisible by using the “Toggle Visibility” button in the Sequence’s Track Panel:

undefined

Heavy Attack Sequence (+ Area of Effect)

Let’s create our heavy attack Sequence now (seqAttack1_Heavy). I’ll simply duplicate seqAttack1 as it will use the same attack animation, but will include some extra editing on our part and an additional hitbox!

For our attack animation in this Sequence, I’ve used the Image Index parameter track so I could control the animation frames manually. I did this so I could add a longer “waiting period” before the actual attack frame hit. You can add the Image Index parameter from this menu:

undefined

Note that this will disable your sprite’s animation completely, and you will have to use keyframes to advance each frame one-by-one. Before doing that, make sure to disable interpolation for the Image Index track by right-clicking on it and turning off the “Interpolation” option. This makes the values change instantly between keyframes without any tweening (so it would jump from 0 -> 1 instead of going through each decimal value in between, for example: 0 -> 0.2 -> 0.4 -> [...] -> 1).

undefined

In the image below you can see the keyframes I’ve used along with their image index values. You can see how I’ve repeated the frames 2 and 3 in the middle to increase the duration of the “hold” animation (as this is a heavier attack and should take more time to charge up):

undefined

I’ve also added keyframes to the position track, to add additional movement to the player as it charges its attack and unleashes it:

undefined

Since the player now moves in this Sequence, make sure that it moves back to (0, 0) at the end of the animation so that the transition from Sequence to instance is seamless as well. Also make sure to adjust the hitbox so it fits the new animation (you’ll see in the GIF above that I’ve made it larger for this attack).

Area of Effect

To spice up this attack and make it stronger, we’ll add an area of effect circle. I’ll make it start small when the player attacks and grow gradually until the end of the animation. I’ll also add the Colour Multiply parameter track and add keyframes to make it fade away at the end (you can set the alpha to 0 in the colour picker to achieve this effect).

undefined

However, this is only a sprite and we need another hitbox for this area of effect attack. You can add the hitbox object again to create another attack, and make it fit the new circle:

undefined

I’ve changed its colour using the Colour Multiply track so I can easily differentiate between the two hitboxes. Also note that I am using the same hitbox object in all my Sequences, however you can create multiple hitbox objects and use different damage values and status effects for each, which you can then use in different Sequences to add variation to your combat system. For example, you can create a new hitbox object that does double the damage and use that in the heavy attack Sequence.

You can now hit Shift+Space in the game to play the heavy attack animation, which has an awesome area of effect attack that easily inflicts damage on multiple enemies!

undefined

Idle Sleep Sequence

You can do a lot with Sequences, so instead of focusing only on creating attack animations, we’ll also create a “sleep” animation that plays 5 seconds after the player has been left idle.

I’ll create a simple Sequence called seqIdle1 and make it so the player looks around and then goes to sleep. For this I am using the Image Index parameter to manually control the frames, so I can make the player sleep for longer. At the end of the Sequence, it wakes up and returns to its usual pose.

undefined

Of course, we’ll also make it so that the player can wake it up by pressing an input button.

To implement this animation, we’ll first create a variable in the player object called idleTime, and set it to 0. This will tell how many frames it has been since the player has been left idle, and will be used to trigger the sleep animation.

idleTime = 0;

I’ll add some code in the Begin Step event to handle this animation, so it runs before the Step event and is also able to run while the player is disabled (as I haven’t put the “exit” code into this event -- if you have, make sure to add this code before that line).

if (inputX == 0 && inputY == 0) {
	if (enabled) idleTime ++;
	
	if (idleTime > 300) {
		StartAnimation(seqIdle1);
		idleTime = 0;
	}
}
else {
	idleTime = 0;
	
	if (activeAnimation == seqIdle1) {
		var _seqLength = layer_sequence_get_length(activeSequence);
		var _newHeadPos = _seqLength - 20;
		
		if (layer_sequence_get_headpos(activeSequence) < _newHeadPos) {
			layer_sequence_headpos(activeSequence, _newHeadPos);
		}
	}
}

First we check if inputX and inputY are zero, meaning there is no input on either axis. In that case, as long as the player is enabled, we increase its idleTime value. When that value exceeds 300 (which is 5 seconds as by default each second has 60 frames) we start the seqIdle1 animation and reset the idleTime to 0.

This will now start that animation, and enable the player again once it’s over. However, we also want the player to be able to end the animation manually by giving input on either axis, so they can control the character again. That is what the else block handles.

The else block runs if there is input on either axis, and in that case it resets idleTime to 0. Then it checks if the seqIdle1 animation is currently active, and in that case gets the length of that Sequence. We now want to end the Sequence, however we’re not going to make it jump to the last frame immediately as that would look abrupt. Instead, we’ll make the Sequence play its last 20 frames, so that we see the character getting up.

The _newHeadPos local variable stores that frame number, by subtracting 20 from the Sequence’s length. It then checks if the Sequence’s current playhead position is less than that frame, in which case it moves the playhead to that frame. So basically, once the player gives input, the Sequence jumps to that frame, which means the character will be seen getting up and then the control will be returned to the player.

You can now run the game, wait 5 seconds and see how the player goes to sleep. Pressing a movement button at any moment wakes it up:

undefined

More Attack Sequences (+ Combining Attacks!)

Using the same technique as we did for seqAttack1, I’ve created a new attack called seqAttack2 and assigned a new key to it. This is just another attack animation that was placed into the Sequence with a hitbox, without any changes to its frames:

undefined

We can now create a heavy version of this attack: seqAttack2_Heavy. Similar to the previous heavy attack Sequence, this also has additional hold frames and movement animation so we get a stronger-looking attack:

undefined

You can see that it also has an animating hitbox, which is pretty cool!

We’ll make this Sequence special by adding another attack to it. I’ll simply repeat the steps I followed for animating seqAttack1_Heavy and create the same animation in this Sequence, along with a new hitbox. Note that I’ve used the Image Index parameter for this as well so I could start it on the 4th frame instead of the first one (to leave out the “charging” part of that animation):

undefined

Player too OP please nerf.

undefined

To create combined attacks easily, you can pull your older Sequences directly into a new one without having to recreate the attack animations. For example, the following Sequence combines three different attacks by including those three Sequences in it (you can simply drag a Sequence from the Asset Browser into the Sequence Editor to place it there):

undefined

Make sure the positions of your Sequences are (0, 0) in the combined Sequence to keep the transitions seamless.

Conclusion

Having Sequences in your Game Making arsenal means you don’t always need to depend on sprite animations, as you can create custom animations within GMS2 to extend on the art you have and to improve your visuals without drawing new sprites. Of course, this post is only a small example to demonstrate the power of Sequences, and when it comes to using Sequences in your game, the sky’s the limit!

Let us know what you thought of this blog on Twitter @YoYoGames, and remember to use the #GameMakerStudio2 hashtag when sharing your creations with the world. You can also hit me up at @itsmatharoo if you have any technical questions.

Happy GameMaking!


Sprites by Penusbmic on itch.io: https://penusbmic.itch.io/sci-fi-character-pack-11