I will leave a (dd/mm/yy) formatted date on this post, so you know when it was last updated: 18/09/19 — Moved the guide to my website and made formatting tweaks (the last content update was 28/11/16 – Added layer position and speed functions)
- Updated layer_create as layers can be created with names after all!
- Updated “depth = -y alternatives” with a link to @Ariak’s faster BinaryList depth sorting!
I’ll also update the guide if there a better methods that I haven’t thought of/found out about yet, or if it turns out I’m using some function totally inappropriately.
GM Version: IDE: 2.0.2.44, RUNTIME: 2.0.1.27
Target Platforms: ALL
If you get stuck, ask a question and check the documentation!
Summary:
Okay, here is my third guide for GMS2. This time I’ll be covering one of GameMaker Studio 2’s biggest features, and I believe last major change that I have not covered – Room Layers
I asked if anyone wanted/needed this and I got a load of “Yes!”s, so here we are!
Now, there are a few different types of layer introduced, so I’ll be breaking this guide down into the following parts:
- Layer Basics – The Room Editor
- General GML Layer Functions
- Instances and Layers – update “instance_create”
- Tile Layers – The Big Subject
- Assets and Backgrounds
- New Particle System Functions
- Layers and “depth = -y”
I’ll basically be going over the most important GML functions that can be used with layers and their usage, as well as linking to the relevant documentation pages. There are Drag n’ Drop equivalents to some of the functions, but not all of them.
I am not including a downloadable project for this guide – this is because you can see more advanced implementations of most of these features by looking at the demos included with GMS2, so there’s little point in having me writing a whole project. For example, tilemap collision is shown in both “YoYo Dungeon Lite” and “YoYo Platform Lite” – the latter shows examples of both using tiles for one-way ground collision and using tiles (from the same tileset) as ladders.
Relevant Documentation Links:
Tutorial:
Layer Basics – The Room Editor
It’s possible you may not need to read all this section. Some of this stuff explains itself, but I’ll cover enough stuff to minimise confusion and be as beginner friendly as possible. If you use the tutorials included with GMS2, you probably won’t even need this section.
Layers, in their most basic form, are a new way of organizing content within a room, though they provide much more useful functionality which will make certain aspects of development easier and faster both in the IDE and at runtime.
First, a brief coverage of the 5 different types of layers that you’ll see in the room editor:
- Instance Layers
- These layers contain instances and, in a way, replace depth.
- Asset Layers
- These layers a used to render sprites without having to create an object. This is handy if you have an animation that isn’t compatible with a tilemap (non-power of 2 frame count, can’t be snapped to a rigid grid), for example. Assets can also be colour blended and transformed in ways tiles can’t.
- Tile Layers
- One of the biggest changes for GMS2, the tile layers – these completely replace what you may have previously known tiles to be from previous iterations of GameMaker. These layers can be used for faster collision and much faster word building.
- Background Layers
- Backgrounds no longer exist as a resource in GMS2, but we still have background layers. These allow a sprite to be used in much the same way as backgrounds used to be used – but they can be animated now, as well as stretched AND tiled at the same time – which is good for moving some moving backgrounds.
- Path Layers
- Path layers are used to view and edit a path within a room. A path layer may only use one path at a time, and are only particularly useful when you are editing a path (as you get an in-room preview)
- Path layers are only available in the IDE and cannot be used or referenced at runtime.
- I don’t think I’ll cover these layers in detail, since they are basically the same as the path editor.
So, that’s a summary of the layer types. Note that at runtime, layers do not have a specific type and can have any number of different elements dynamically added to a single layer. Now for some basic usage of layers in general. We’ll focus more specifically on each individual layer type later.
Creating and deleting layers in the IDE is pretty straightforward
You can simply open the room you wish to edit, and use these convenient buttons to create, delete and structure layers
In order, from left to right, these buttons perform the following operations:
- Create a background layer
- Create an instance layer
- Create a tilemap layer
- Create a path layer
- Create an asset layer
- Create a layer folder
- Delete the currently selected layer/folder
- Toggle inheritence for all layers in the room
When a layer is created, it is automatically placed above the currently selected layer.
Layers can be reorganized just by dragging them to a new desired position in the list.
Right clicking an existing layer can allow you to add “Sub Layers”, which seem to exist purely for organization purposes, as they do not effect layer usage at runtime.
As another cool thing to note, while layers in the IDE can only contain one type of element, a single layer can contain many different types of element at runtime, assuming the elements are added dynamically through code. This means a single layer could contain backgrounds, tiles, sprites and instances all at once.
Here’s a little image of what you’ll see in layer properties. I’ve labelled and outlined settings that appear in all different layer properties:
Here’s what each thing means and does:
- Depth – like the previous iterations of GameMaker, this is the draw depth and z position of the layer. If the lock icon is highlighted, the IDE will automatically space layers 100 units apart, to try and reduce unexpected behaviour. To change the depth, you must first uncheck the lock.
- Layer depth can be inherited from parent rooms. If you change the depth of a parent, the child will change too. If you change the depth of the child, it will automatically stop inheriting parent values.
- This second inherit button works in much the same way as the depth inherit button, applies to all properties of the layer EXCEPT depth. Sometimes this button is at the top of layer properties (like here), though sometimes it appears at the bottom (e.g instance layer properties)
Other properties can be edited within the layer list:
- A slow double click can be used to edit the layer name
- Clicking this toggles layer visibility for both the runtime and the IDE. The layer still exists, but it does not draw
- Clicking this can be used to lock layer properties – this can prevent accidental editing of layers, as you must manually unlock the layer to perform any edits.
- Right-clicking a layer brings up a drop-down list of other options. These include:
- Renaming the layer
- Deleting the layer
- Duplicating the layer
- Adding sub-layers
- Tweaking inheritence
General GML Layer Functions
Let’s get on with some GML, shall we? A lot of these functions are covered within the documentation. Here’s a link to a list of general layer functions from the documentation: http://docs2.yoyogames.com/source/_build/3_scripting/4_gml_reference/rooms/layers/index.html
Now, there are a lot of layer functions – and I mean a LOT, so I’ll I’m going to cover what I consider the most vital functions – Once you know these, you should be able to explore the rest without too many problems.
The function you’ll probably find the most useful is this one:
layer_get_id("Layer Name");
It takes the name of the layer as an argument and returns an ID for use in other functions.
In general, I’d say it’s best to call this at room start and store the ID in a global variable to save time accessing the layer later.
Sometimes, you may need to create or destroy a layer. This can be done with these respective functions:
layer_create(Depth, *Name);
layer_destroy(LayerID);
layer_create
does allow you to specify a layer name, however it returns the ID directly, so there is no need to call layer_get_id
. Calling layer_get_name
on an unnamed layer returns an empty string. If you try to call “layer_get_id” with an empty string, it will return -1. There is no layer_set_name
currently.
layer_destroy
Removes a layer from the room and all its contents – tiles, instances, sprites etc. This is handy for removing whole groups of things you no longer need – just be careful to not accidentally delete a layer that contains important stuff!
Another important function is
layer_exists(LayerID)
This function takes either the layer name as a string, or the layer ID as a real number and returns true if the layer exists, and false if it does not. This is useful as a way of checking if a layer even exists before attempting to manipulate it in any way.
If you need to show or hide a layer at runtime, perhaps for debug purposes, you can use this simple function:
layer_set_visible(LayerID, Visible?)
This function takes the layer ID and either true
or false
as arguments, where true
makes the layer visible and false
hides the layer. Hidden layers can still be used in the same way as visible layers; they just don’t perform any draw operations. If
Moving layer offset and changing their speed is pretty straightforward too:
layer_x(LayerID, x_offset);
layer_y(LayerID, y_offset);
layer_hspeed(LayerID, horizontal_speed);
layer_vspeed(LayerID, vertical_speed);
//Respective getter functions
layer_get_x(LayerID);
layer_get_y(LayerID);
layer_get_hspeed(LayerID);
layer_get_vspeed(LayerID);
Apparently, these functions work on all layers except for instance layers. Good examples of usage include creating parallax backgrounds.
These are a few other general layer functions that I believe are worth mentioning
layer_script_begin(LayerID, ScriptID);
layer_script_end(LayerID, ScriptID);
layer_shader(LayerID, ShaderID);
//Respective getter functions
layer_get_script_begin(LayerID);
layer_get_script_end(LayerID);
layer_get_shader(LayerID);
These functions provide some quite useful options. Each of this functions run for each element on the target layer and for each individual draw event of an instance. A simple use for this would be setting a shader and passing some constant uniforms to it, though you could theoretically do much more.
There are still quite a few more general layer functions, though they aren’t necessary to explain right now, as they aren’t essential in creating a basic game. Check the documentation if you want to read up on them!
Instances and Layers – update “instance_create”
I was initially going to cover tiles here since they are the BIG thing, but I reckon people would probably find instance-related stuff slightly more useful to begin with – say goodbye to instance_create! …Unless you import a 1.4 project, in which case compatibility scripts are automatically generated.
So, let’s get straight to it – “instance_create” is gone. If you’re a DnD user, the change won’t be too harsh as the input box for the function plainly lists usage. As for the GML side of things, here’s the 2 functions you’ll need to know about:
instance_create_layer(x, y, LayerID or "Layer Name", ObjectIndex);
instance_create_depth(x, y, Depth, ObjectIndex);
Out of the 2, it’s preferred to use “instance_create_layer” as it follows the new layering rules properly. Depth apparently creates “pseudo-layers” for objects at irregular depths, which is slightly less efficient. At this point, depth mainly exists for compatibility with 1.X projects and the old depth = -y
trick for pseudo-3D instance draw ordering for Isometric or top-down Zelda-like games.
As per previous iterations of GameMaker, both of these functions return the unique ID of the created instance.
If you need to move instances between layers, or check if a layer contains an instance or any instance of an object, we can use these functions:
layer_has_instance(LayerID, InstanceID or ObjectIndex);
layer_add_instance(LayerID, InstanceID);
layer_has_instance
returns true if the layer contains the instance passed, or any object or child of the object index passed.
layer_add_instance
does not create an instance, but instead allows you to change which layer an instance is on at runtime. This is handy for layered collision environments, for example.
The last functions related to instances and layers that I feel I should mention are these:
instance_activate_layer(LayerID or "Layer Name");
instance_deactivate_layer(LayerID or "Layer Name");
These functions activate or deactivate all instances on a specific layer. This finally provides us with a way to isolate certain groups of instances without needing using a with statement. The only problem with this is it cannot be restricted to a specific object index, though this shouldn’t matter too much if your layers are well organized.
Tile Layers – The Big Subject
The thing we’ve all been waiting for! Proper tilemaps!
Now, I’ll be focusing on GML usage, rather than creating tiles and populating a room.
“Why?”, you may ask. Well, because GMS2 has built-in tutorials and a huge manual http://docs2.yoyogames.com/source/_build/3_scripting/4_gml_reference/rooms/index.html to teach you these bits! Coding is my specialty, so I’d rather focus on that.
At runtime, there’s a few things you may want to do with tiles – set some, update them, or check if a tile is present for tile based collisions. The latter is fairly likely, since the new tile system permits collisions much, much faster and more accurate than instance based collisions.
But, before you can do all this, you’ll need to get the tilemap ID. Since layers can contain multiple different things, we can’t just use the layer ID and need to specifically grab the tile layer assigned to the layer. We use this function to get the tilemap ID:
layer_tilemap_get_id(LayerID);
We pass the layer ID (either received through layer_get_id
or layer_create
) and the function returns the tilemap ID, assuming it exists, otherwise returning -1.
We can also check if a layer actually has a tilemap using layer_tilemap_exists
which returns either true
or false
, depending on whether or not the layer has a tilemap – building upon this, while a layer can have multiple instances or sprites, a layer cannot have more than one tilemap.
If you need to create or destroy a tilemap at runtime, we have these handy functions:
layer_tilemap_create(LayerID, x_in_room, y_in_room, TilesetID, width_in_cells, height_in_cells);
layer_tilemap_destroy(TilemapID);
layer_tilemap_create
returns the tilemap ID so you don’t need to call layer_tilemap_get_id
.
Sometimes, you may just want to start fresh with your tilemap, without deleting and creating a new one. You can do this by just calling:
tilemap_clear(TilemapID);
Okay, now you should have some way of acquiring the ID of a tilemap – now you can start manipulating it! First, we’ll cover some handy tilemap functions. Once we’ve been through that, I’ll demo a simple implementation of tilemap-based collisions.
There are more tile functions than the ones I’ll go through, like finding cell size, position in room, getting the current frame (for animated tiles), but they aren’t necessary for understanding and using tiles in general – they’re more like “highly useful extras”. Check out the manual for more on these!
First, let’s get on with getting and setting tiles.
When we “get” a tile in the room, we get a kind of number “blob” of information about the tile. Assuming your tile is untransformed, it will simply represent the index of the tile from the tileset. This is because the first 19-bits of information represent the tile index, with the remaining bits representing mirror, flip and rotation. Tiledata does not store information about the tile size or location in the room.
Moving on, here’s how to get some tiledata from a tilemap:
tilemap_get(TilemapID, cell_x, cell_y);
tilemap_get_at_pixel(TilemapID, x, y);
The first function, tilemap_get
, returns the tile data found at the specific cell of a tilemap. You’d probably find this function more useful when looping through a tilemap to populate a world with instances.
The second function, tilemap_get_at_pixel
, returns the tiledata found at a specific coordinate in the room. This function is more useful in systems such as tile based collision, as you don’t have to figure out which cells to check manually. If you do need to check cells surrounding a pixel, perhaps these functions would interest you:
tilemap_get_cell_x_at_pixel(TilemapID, x);
tilemap_get_cell_y_at_pixel(TilemapID, y);
These functions return the x and y of the cell at a pixel, which can then be used with functions such as tilemap_get
. They are also a little faster than rounding a coordinate to tile cell size manually.
Now that you’ve retrieved the tile data, you can now read it.
Assuming none of your tiles are transformed in any way, the number represented by the tiledata retrieved will just be the tile index – which you can check using the room editor. Tile ID’s start from 0 in the top left corner and increase in value from left-to-right.
If the value is 0, then the tile is the empty tile, where nothing is drawn.
If you do transform tiles in any way, either at runtime or in the editor, you’ll need to use another function:
tile_get_index(tiledata);
This function returns the actual tile index, stripping off the extra data. If you need to find out if a tile has any other transform data, you can use these functions:
These functions return true
if the transform is applied to the data and each have appropriate setter functions so you can apply transforms to tiledata.
tile_set_mirror(tiledata, true or false);
tile_set_flip(tiledata, true or false);
tile_set_rotate(tiledata, true or false);
Okay, this may raise the question “why is rotate only true or false?”. Well, the rotate flag only rotates the tile 90 degrees. To rotate a tile 180 degrees, rotate must be false and the tile must be both flipped and mirrored. To rotate 270 degrees, the tile must be mirrored, flipped and rotated.
Additionally, I’m just going to point out that mirroring a tile reflects it horizontally, while flipping reflects vertically.
There are also read-only variable equivalents to these functions which can be used with bitwise operations to apply/remove transforms (tile_mirror
, tile_flip
, tile_rotate
, tile_index_mask
) for example, tiledata & tile_index_mask
returns the same result as tile_get_index(tiledata);
.
There is a tile_set_empty
function too, though I’m fairly sure it has no real use, since it should always return 0, but the documentation on this function is a little confusing, since it says it returns true, false and modified tiledata.
If you need to add a tile to the tilemap (or update a tile with transformed tile data), you’ll need to use one of these:
tilemap_set(TilemapID, new_tiledata, cell_x, cell_y);
tilemap_set_at_pixel(TilemapID, new_tiledata, x_in_room, y_in_room);
Passing 0 as the new tiledata will clear the tile. Otherwise, it will update and replace the tile in the targeted cell. Be aware that tilemap_set_at_pixel
does not place a tile at that specific coordinate, but at the cell found at the coordinate!
For a simple demo of the functions, here’s a sample of code which will mirror a tile horizontally when clicked on with the mouse, assuming the tile is not empty:
//Get the tile layer ID.
//It's better to run this at create or room start and store in a global, but for demoing, it's fine here
var layer_id = layer_get_id("Tiles_1");
var tilemap = layer_tilemap_get_id(layer_id);
//If clicking
if(mouse_check_button_pressed(mb_left))
{
//Get tiledata at mouse
var tiledata = tilemap_get_at_pixel(tilemap, mouse_x, mouse_y);
//Check if tile is not empty
if(tiledata != 0)
{
//get mirror state and toggle it
var mirror_state = tile_get_mirror(tiledata);
tiledata = tile_set_mirror(tiledata, !mirror_state);
//Update the tile in the map
tilemap_set_at_pixel(tilemap, tiledata, mouse_x, mouse_y);
}
}
Okay, so that’s how to get and set tiles. Now let’s look at changing the tileset a tilemap uses at runtime. This could be useful in games that feature seasons, for example. Note that you cannot change the tilemap for an individual tile without creating a whole new layer.
Well, that’s nice and simple. We have two functions for this:
tilemap_get_tileset(TilemapID);
tilemap_tileset(TilemapID, TilesetID);
The first function returns the ID of the tileset currently in use. The second allows you to set the tileset the tilemap should use, automatically updating tiles visually.
Okay, that’s about all there is to the basics of tiles. Like I said earlier, there are more functions, but these are the ones I think you will find most useful for making a game.
Assets and Backgrounds
Asset layers and background layers are basically ways of drawing sprites without using objects.
We’ll quickly cover background layers, then move on to asset layers.
Backgrounds
Background layers replace the backgrounds you may know from GM: S 1.X and now use sprite resources and hence can even be animated.
The old background array has now been replaced with layers and functions. Here is a list of all background functions within GMS2, so you can find out how to transform and edit backgrounds: http://docs2.yoyogames.com/source/_build/3_scripting/4_gml_reference/rooms/backgrounds/index.html
For the most part, you’re probably going to just create and edit backgrounds in the room editor, but here’s a few handy functions to know about.
Creating, Destroying and checking the existence of background layers
layer_background_create(LayerID, Sprite);
layer_background_destroy(BackgroundID);
layer_background_exists(LayerID, BackgroundID);
Getting the background ID that was attached to a background layer in the IDE, so you can manipulate it at runtime
layer_background_get_id(LayerID);
Changing the layer a background is bound to
layer_element_move(BackgroundID, LayerID);
You need to use layer_element_move
as there isn’t a specific function for background layers to change the layer. Just pass the background ID as normal.
Changing the background image:
layer_background_sprite(BackgroundID, NewSprite);
Like I said, very quickly going over that, since the basic use is very similar to the usage in 1.X.
Moving on to asset layer manipulation
To start with, despite the name of Asset layers, the only assets you can currently put on them in the IDE are sprites. Basically, this section will be more about sprite manipulation on an asset layer. Should more asset types appear, I will update the guide with relevant information.
Here’s a link to the relevant documentation page on sprites in layers: http://docs2.yoyogames.com/source/_build/3_scripting/4_gml_reference/rooms/sprites/index.html
Okay, basic stuff first.
Creating, destroying and checking the existence of a sprite asset at runtime:
layer_sprite_create(LayerID, x_in_room, y_in_room, Sprite);
layer_sprite_destroy(SpriteElementID);
layer_sprite_exists(LayerID, SpriteElementID);
layer_sprite_create
returns the sprite element ID for use in other functions.
Retrieving the ID of an asset placed in the room editor:
layer_sprite_get_id(LayerID, AssetName);
For the “AssetName” argument, you need to provide the name that was generated or that you gave the asset when placing it in the room – this can be found by looking at the layer properties of an asset layer or by double clicking on the desired asset in the room editor.
Changing the drawn sprite of the asset:
layer_sprite_change(SpriteElementID, NewSprite);
Changing the layer a sprite asset is on:
layer_element_move(SpriteElementID, TargetLayerID);
You can transform all the aspects of the sprite as usual (x, y, scale, color, angle etc) by using the relevant, named functions as shown in the link to the documentation. They basically explain themselves, so there is little reason for me to rewrite the manual here.
More Information
This bit isn’t very long, it’s just a quick update on how particle systems work with layers. All other particle related functionality (emitters, particles, drawing etc.) still seem to be the same.
Firstly, I will point out that part_system_create
still exists, and creates a managed layer for the system.
However, you can now create particle systems that are attached to layers with:
part_system_create_layer(LayerID);
This functions still returns the ID of the particle system.
To get and change the current layer of a particle system, we have these functions:
part_system_get_layer(ParticleSystemID);
part_system_layer(ParticleSystemID, TargetLayerID);
part_system_get_layer
returns the ID of the layer, unless the layer is an internally managed one, in which case 0 is returned.
part_system_layer
binds the particle system to the specified layer.
Particle system visibility/existence is tied to the layer it is bound to.
Layers and “depth = -y”
Okay, here’s one thing that did change, in a big way. If you’ve ever made an isometric game, or a Zelda-alike where closer entities are drawn on top of further entities, you may have used depth = -y;
.
Well, you can still use this as it exists for compatibility with 1.X projects, however, if you’re developing purely from a GMS2 standpoint, you may come across some issues. For example, here is an instance sorted with depth = -y;
, on a layer with depth at -100, as well as 2 other layers – one at -50 depth, the other at -150. Here is the result:
Not so desirable. The purple should always be below the red circle, while the green should always be on top.
Obviously, you can work around this with spacing the layers in different ways, but layers would need separating by the height of the room to work around this. If you have a lot of layers or a very tall room, that could cause problems.
One other option is to use a ds_priority data structure. The problem with this is it gets very slow when dealing with 1000s of instances – you must keep the number of drawn instances for every frame low to keep the framerate reasonable (though if you’re drawing thousands of instances every frame, you may want to consider optimising anyway).
You’ll need a priority queue for every single layer that instances are drawn on, so this can get a little too much if you’re dealing with complex environments.
So, for a single layer, here is some basic code:
💡 Create///Create of draw controller
draw_queue = ds_priority_create();
🖌️ Draw///Draw event of draw controller
with(obj_drawParent)//Parent objects of all ordered instances
{
ds_priority_add(other.draw_queue, id, y);
}
//Loop n' draw
while(!ds_priority_empty(draw_queue))
{
with(ds_priority_delete_min(draw_queue))
{
draw_self();
}
}
The result is this:
Much more satisfying. As you can see, it still sorts depth within the same layer properly too:
The instance drawn is set to be invisible for this, just to make it a little easier to code. I wouldn’t recommend using disabling visibility for things you want to draw as that’s a little counter-intuitive. You’d also want to test that the object is on the same layer, in some situations.
GameMaker Forum user @Ariak has proposed an alternative to priority lists, which run consistently twice as fast by using “BinaryLists” – please check out their thread!
A basic summary of the BinaryLists is that instead of a priority list, you add the IDs of the instances to a ds_list with their y-position bitwise-or’d in, sorting the list and then going through it in order. Here’s a greatly simplified overview:
//Adding the instance to the list - y is bit-shifted left to make it the most significant value
//y is also converted to an Int64 value due to the bit-shift.
with(obj_drawParent)
{
ds_list_add(other.BinaryList, (y << 32) | id);
}
//Sort the list when it's time to render
ds_list_sort(BinaryList, true);
//Iterate through the list
for(var i = 0; i < ds_list_size(BinaryList); ++i)
{
var value = BinaryList[| i];
//Retrieve the instance ID by bitwise and-ing the y position away.
var ID = value & $FFFFFFFF;
with(ID)
{
//Do draw
draw_self();
}
}
This also has the added benefit of allowing you to loop through the same list multiple times!
Another possible suggestion is to use 3D techniques and rendering, but with an orthographic perspective. You could, for example, keep the depth constant, but draw the top 2 vertices of the sprite a little higher up in the z-axis, so it looks like the sprites are correctly layered. This removes the need for the queue, but adds the slight graphical overhead of the z-test and write. (Note from the future: 2019! Apparently, this technique is know as “z-tilting“, if you want to Google it some more!). To acheive this, you either need to use vertex buffers, matrix magic or use shader_enable_corner_id
with a shader.
That should be everything you need.
If you have any more suggestions on how I could expand on/improve this guide, or if you have a suggestion for a new guide (it could be ANYTHING!), let me know!
Excellent article. One of the few clear enough to got me motivated to experiment with and tweak the code examples without feeling lost.