I was working on the town area for one of my side-projects and was struggling with how to build it. I didn’t want to create a generic, 2D tiled area, with just one layer to fill the background; I wanted it to be a little more complex and three-dimensional, while still being a 2D game.
That got me thinking about creating a 2.5D game. And you know what GameMaker has? 3D cameras, and depth-based layers.
Let’s learn how this magic can be incorporated into any game!
- You can download the project here, and have it open on the side, as you read this blog.
- There is also a video version of this tutorial (but make sure to also read this post as it covers more than the video):
What is 2.5D?
2.5D is a design technique that adds depth and a 3D feel to a 2D environment.
In a 2.5D game, you can only move up, down, left, and/or right like in 2D games, but your character moves around 3D environments, thanks either to some camera trickery or by crossing into the background or foreground.
It was particularly popular in the 1990s when 3D games were challenging the dominance of 2D games. 2.5D featured in a number of popular titles including Klonoa: Door to Phantomile and Kirby 64: The Crystal Shards.
Klonoa: Door to Phantomile, developed by Namco
How to create depth in GameMaker
In the Room Editor, each layer has a “depth” value. A layer with a lower depth is drawn above a layer with a higher depth.
Three visible layers with depths 0, 100 and 200
Breaking news: the depth value actually corresponds to the “z” value in 3D space! This third dimension, z, is the distance of the layer from the camera.
So, we only need to set up a 3D camera, and the rest will be handled by the layer system!
How to create a 3D camera in GameMaker
A 3D camera uses a view matrix and a projection matrix.
The view matrix defines where the camera is, and where it’s looking.
The projection matrix defines how the world is rendered; for example, whether the view is orthographic or perspective, what the field of view is, etc.
How to create a view matrix in GameMaker
The view matrix can be set up using the function “
var _viewMat = matrix_build_lookat(cam_x, cam_y, cam_z, look_x, look_y, look_z, 0, 1, 0);
The function name itself explains how it works: it lets you “look at” a point in 3D space, from another point.
The arguments “
cam_x, cam_y, cam_z” represent the 3D position of the camera. Then, the arguments “
look_x, look_y, look_z” represent the 3D point where the camera is looking.
And finally, the last three arguments “0, 1, 0” (in order: x, y, z) represent the “UP” vector of the camera. It’s basically the camera asking you: “which direction is up?”
Although we're setting up a 3D world, we still want it to retain the old 2D view, where x represents left-to-right movement and y represents up-to-down movement. So to set the UP vector on the y axis, we set the UP vector to “0, 1, 0” (x, y, z).
How to create a projection matrix in GameMaker
Our projection matrix will be set up using the function “
var _projMat = matrix_build_projection_perspective_fov(70, 16/9, 3, 30000);
For the projection matrix, the arguments are: FOV, Aspect Ratio, ZNear, ZFar
We pass in the field of view of the camera, and the aspect ratio that it needs to maintain. Then we pass in the ZNear and ZFar values. Anything drawn outside of this z range, whether it’s too close to the camera, or too far away, won't be rendered.
Note: When using a perspective camera, tiled background layers do not work and so you need to handle such tiling manually.
How to implement a 3D camera in GameMaker
Before starting the implementation, make sure you have these things ready inside your room:
- Layers, with proper depth order. You can click on the padlock button next to the layer depth, to set a custom depth value.
- A camera view following an object, which can easily be set up in the Room Properties.
a. If your camera view is set up through code, make sure that it is set up before you run the code below.
This is what I’ll be working with:
A simple 2D game -- without anything “3D”
For managing the 3D camera, we'll use a separate “oCamera” object. Alternatively, you can also do this in a game controller/manager object, if you already have one.
Now let’s set up some variables, in the Create event:
// Camera camera = view_camera; // 3D camera properties camDist = -300; camFov = 90; camAsp = camera_get_view_width(camera) / camera_get_view_height(camera);
First, we get the camera ID, and store it in camera.
Then we set up some 3D camera properties:
camDist: z value where the camera is positioned
camFov: Field of view
camAsp: Aspect ratio
Now the magic happens in the Draw Begin event.
Why Draw Begin, you ask? Because it runs before the Draw events, and the 3D camera needs to be updated before anything else is drawn.
// Update 3D camera var _camW = camera_get_view_width(camera); var _camH = camera_get_view_height(camera); var _camX = camera_get_view_x(camera) + _camW / 2; var _camY = camera_get_view_y(camera) + _camH / 2; var _viewMat = matrix_build_lookat(_camX, _camY, camDist, _camX, _camY, 0, 0, 1, 0); var _projMat = matrix_build_projection_perspective_fov(camFov, camAsp, 3, 30000); camera_set_view_mat(camera, _viewMat); camera_set_proj_mat(camera, _projMat); camera_apply(camera);
First, we get the width and the height of our camera, in
_camH. Then we set up the position of the 3D camera, in
_camY. You can see that it points to the center of the camera (by adding half the size to the camera position).
After that we set up the view matrix. The 3D camera is at (
_camX, _camY, camDist), and is looking at (
_camX, _camY, 0). So between these positions, only the z value is different.
For the projection matrix, we simply pass in the FOV and aspect ratio using the variables, and then the ZNear and ZFar values.
Then we apply both matrices to the camera, and at the end, use
camera_apply. This applies all the changes to the camera immediately, instead of waiting for the next frame to update it.
Boom! Our layers now have real depth: you can tell by the neat parallax effect.
But we’re not done yet! Now we only need to have…
Rotation and focus in GameMaker
Let’s take a look at our view matrix:
matrix_build_lookat(_camX, _camY, camDist, _camX, _camY, 0, 0, 1, 0)
The first three arguments represent the 3D position of the camera. So if we offset those coordinates, we can effectively tilt our camera!
So, for example, let’s do this:
matrix_build_lookat(_camX + 100, _camY + 50, camDist, _camX, _camY, 0, 0, 1, 0)
With this offset, and a lower FOV, we get this awesome view:
Let’s say you want to focus on a character, during a cutscene, or to aid in gameplay. You can do that by simply decreasing the camera distance!
Download the bonus project, which includes code for real-time camera rotation and simple focusing.
3D cameras are fun! Use this power creatively to spice up your games and make your 2.5D games stand out from the crowd.
If you need any further help with GML or want to discuss this technique, feel free to hop into my Discord community. You can also tweet at me for any questions: @itsmatharoo. DMs are open.