Every game needs polished animations to immerse users in its world and characters, and GameMaker is here to help make your game look as smooth as possible with its new "Sequences" feature: allowing you to bring your characters to life your own way!
In this tutorial 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!
This post is divided into the following
sections:
- Basic Attack Sequence (+ Hitbox)
- Playing the Sequence using GML
- Heavy Attack Sequence (+ Area of Effect)
- Idle Sleep Sequence
- 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!
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):
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:
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!
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:
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:
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:
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).
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):
I’ve also added keyframes to the
position track, to add additional movement to the player as it
charges its attack and unleashes it:
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).
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:
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!
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.
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:
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:
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:
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):
Player too OP please nerf.
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):
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