Intermediate Guide to Sprite Stacking using GameMaker Studio 2

dev_dwarf
8 min readApr 28, 2020

--

Hello there! My name is dev_dwarf, and today i’ll be showing you some techniques for your Sprite Stacking games! This tutorial is meant as a follow up to Avis’ popular tutorial on how to add the Sprite Stacking technique to your Gamemaker games, using MagicaVoxel. Note that this tutorial is not for beginners, and will require some basic knowledge of certain functions in Gamemaker.

The original covers the topics:

  1. What is Sprite Stacking?
  2. Exporting your model from MagicaVoxel
  3. Importing into GameMaker
  4. Drawing the Model

So if you don’t know how to do any of those things, check out Avis’ Tutorial.

This text based tutorial will cover some more advanced techniques for your Sprite Stacking games. These techniques have been used to great effect in games like NIUM and CHUMBO.ZONE.

We will be covering:

  1. Camera Rotation
  2. Billboarded Sprites
  3. Depth Sorting

This tutorial will require you to make a few different objects, and 5 different scripts, but otherwise should be easy to implement in an ongoing project. A project file will be included below, showing some example usage of Sprite Stacking, how to use inheritance with your depth sorter object, and include a few more advanced scripts to use in your projects.

By the end, you will be able to achieve a result like this:

pretty cool huh?

Camera Rotation

The first thing we’ll be adding to your project is camera rotation. This effect can be pulled off in an extremely unique way by Sprite Stacked games, but there are some complications you will find if you are coming from the first tutorial.

The first step is to add some sort of camera object to your game. In this object we will enable the use of views, create a view camera, and define two variables, image_zscale, and angle. The angle will be used to let us change the angle of the camera, and the image_zscale will determine the “stretch factor” of the Sprite Stacking, which I encourage you to play around with.

angle = 0;
 image_zscale = 0.75;
 
 view_enabled = true;
 view_visible[0] = true;
 view_camera[0] = camera_create_view(0, 0,
newly created object, and it’s create event

Then, just so that we can test things for the rest of the tutorial, lets add a step event to the camera, that lets us quickly change the angle of the view.

/// @description
 if (check(vk_a)) angle += 2;
 if (check(vk_d)) angle -= 2;
 
 camera_set_view_angle(view_camera[0], angle);
step event of the camera

So now we have a camera, but we need something to draw! Create another object that will use the Sprite Stacking technique. I’ll make mine oWall, because I want to use Sprite Stacking for the walls in my game. Add a create event, and add just one line of code for now:

z = 0;

You’ll be able to change which objects are higher up on the z axis by using this variable in your game. Next, add a draw event to the object, and once again add just one line:

draw_stacked_sprite(sprite_index, x, y, z);

You’ll notice that this script doesn’t exist in your project. Don’t worry, we’ll be making it right now! Create a new script called draw_stacked_sprite and inside it we’ll put the code to draw the object.

Alt text is too short to let me put this here.
Uh Oh! That’s a lot of code.

That’s a lot of code, so lets break it down bit by bit!

  • The first few lines just give some names to the arguments, so lets skip those.
  • Line 12 sets the _image_zscale to be the same as the camera object.
  • Lines 16 and 17 are used to set _x_step and _y_step. Normally, when drawing a stacked sprite, you just offset each layer by subtracting 1 from its y coordinate. However, because we want to rotate the camera, we cant just subtract 1 from y, because the -y axis will no longer be “up” on the screen. So, to fix this we have to use a little trigonometry. I’m not here to teach you trig, so all you need to know is that at a camera angle of 0 this function will still offset the sprite by 1, but at other angles it will calculate a combination of x and y offsets to achieve the same affect, even though the camera has been rotated.
  • Finally, lines 20–22 loop through each frame of the sprite, and draw it in the same way as Avis’ tutorial, but instead of subtracting i from the y coordinate, we subtract _x_step * i from x, and _y_step * i from y.

Place your Sprite Stacked object and camera in a room, and check out the results!

Billboarded Sprites

So that covers us for our 3d objects, but sometimes you also want to include 2d objects in your 3d world! (for example, both games mentioned above us this for the player characters). But the problem with just straight up using the normal draw functions with what we have now, is that when you rotate the camera the objects will rotate right along with it!

To fix this, lets make a new script, called draw_billboarded_sprite.

You’ll see that this script is basically the same as draw_sprite_ext. In fact, the only difference is that we subtract the camera angle from the image_angle of the sprite, so that it stays upright despite the rotation.

I added a new object to my project, called oPlayer, and gave it a draw event that uses this script. Drop that in to your room along with the walls and camera, and you should have something like this:

success??

Huh, that’s not quite right. The walls are making some sort of impossible shape. To fix this, we need the last part of this tutorial, depth sorting!

Depth Sorting

To finish off, we’re going to add a depth sorting system, that has been modified from Friendly Cosmonaut’s tutorial to work with our 3d effect. The first thing we need to do is make two objects. The first, oDepthDrawer, will handle sorting the depth of our instances, and drawing them in the proper order. The second, pDepth, will “tag” the objects we will be drawing with oDepthDrawer.

Firstly, in the create event of oDepthSorter, we will initialize some variables we’ll be using later. We also set the layer of the object to a high depth, so that we know our instances will be drawn above any background we might have in our room.

Next, in the step or end step event of the object, we’re going to add some code to iterate through our list, and calculate the depth they should be drawn at.

Lets break down this code:

  • Line 1 makes sure that we actually have objects in our depth grid
  • Line 7 and 8 iterate through each row in the depth grid, and get the id associated with each instance in the grid.
  • Lines 10–13 make sure that the instance still exists, and otherwise, it deletes the instance from the grid.
  • Line 15 calculates a new depth for the object, based of the object’s x, y and z coordinates.
  • Line 17 sets a new depth for the object.
  • Finally, line 20 sorts the grid so that the objects with the highest depth are at the top, and thus drawn first.

However, once again, you’ll notice that a couple scripts used here aren’t yet in your project. The first, compute_3d_depth does exactly what it says. It also uses some trigonometry, but all you need to know is that at an angle of 0, the depth equals -y, which is a common way to sort depth in Gamemaker projects.

The second script ds_grid_delete_row is more of a thing that should already be in Gamemaker. I can’t take credit for this script, it was made by Pixelated Pope, a legendary Gamemaker tutorial maker. You can find it here.

The final thing our oDepthDrawer needs is a draw event, to draw all the instances we just sorted. This event will have a very similar structure to our step event, but instead of calculating a new depth, we’ll be running the instance’s draw event.

That was a lot of code, but we have one last thing to get through before our depth sorting will work, and that is some code to add the pDepth objects to the depth grid. So open up your pDepth object, and give it a create event like this:

The first thing is setting the z value of the instance. After that:

  • Line 4 makes sure that the oDepthDrawer exists, and if not, creates it.
  • Lines 7–12 resizes the depth grid, adding the current instance to the end.
  • Lines 14–18 creates the depth grid if it doesn’t exist, and makes the first entry the current object.
  • Finally line 21 makes sure that the object isn’t drawing itself, since we draw it in the oDepthDrawer.

The last step is to set the parent object of your oPlayer and oWall to pDepth

red circle, just like youtube!

Now, your room that contains oCamera, oWall and oPlayer should look something like this:

yay!

In part 3, we’ll discuss adding another axis of rotation to your sprite stacking, enhancing the fake-3d effect! Find part 3 here!

If you had any problems with this tutorial, contact me on discord (dev_dwarf#3925) or twitter (dev_dwarf), or leave them down in the comments so others can see! You can also grab the project file for this code at itch.io: https://dev-dwarf.itch.io/sprite-stacking-example. The project file also includes some extra code, including some optimizations, so I highly suggest checking it out!

If this helped you out, it really would help me out if you dropped me a follow on itch.io or twitter, or checked out some of my games!

UPDATE 5/2/2020:

I actually made a mistake in the draw_billboard_sprite. Did you spot it? I offset the y coord by z, but that only gives the proper effect of moving the sprite up if the camera angle is 0. To fix this, change the last line of the script to this: draw_sprite_ext(_sprite_index, _image_index, _x — _z * oCamera.x_step, _y — _z * oCamera.y_step, _image_xscale, _image_yscale, _image_angle-oCamera.angle, _image_blend, _image_alpha);

--

--

dev_dwarf

making games with noisybug. @dev_dwarf on twitter.