YoYo Games Staff
Matharoo makes games, tutorials and courses using GameMaker. He has joined us on the blog to share the following quick and exciting use of cameras in GMS2.
There’s an amazing little trick, or rather, feature in GameMaker Studio 2, that you maybe didn’t know about! Let’s take a look at it in this blog.
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...
And you know what GameMaker Studio 2 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):
THE THIRD DIMENSION
If you’ve used the Room Editor, you’ll know that each layer has a “depth” value. So, a layer with a lower depth would be drawn above a layer that has a higher depth.
Three visible layers with depths 0, 100 and 200
What’s interesting about this, is that the depth value actually corresponds to the “z” value in 3D space! So the third dimension, z, would be the distance of a layer from the camera.
This means that we only need to set up our 3D camera properly, and the rest will be handled by the layer system!
Using a 3D camera in GML mainly consists of setting up a projection matrix and a view 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.
The view matrix can be set up using the function “matrix_build_lookat”:
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 are 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” (again: x, y, z).
Our projection matrix will be set up using the function “matrix_build_projection_perspective_fov”:
var _projMat = matrix_build_projection_perspective_fov(70, 16/9, 3, 30000);
For the projection matrix, these are the arguments, in order: FOV, Aspect Ratio, ZNear, ZFar
So we pass in the field of view of the camera, and the aspect ratio that it needs to maintain. And 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, will not be rendered.
Before starting the implementation, make sure you have these things ready inside your room:
This is what I’ll be working with:
- Layers, with proper depth order. Additionally, you can use 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.
A simple 2D game -- without anything “3D”
For managing the 3D camera, we’re gonna 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 = 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:
Now the magic will happen in the Draw Begin event.
- camDist: z value where the camera is positioned
- camFov: Field of view
- camAsp: Aspect ratio
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);
First, we get the width and the height of our camera, in _camW and _camH. Then we set up the position of the 3D camera, in _camX and _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 vectors, 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 step 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…
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!
Here is a bonus project, that includes code for real-time camera rotation, and simple focusing: http://matharoo.net/projects/2-5D_Platformer_Matharoo_Bonus.yyz
3D cameras are fun! We can use this power (creatively) to spice up our games and make them stand out from the crowd.
If you need any further help with GML or want to discuss this technique, feel free to hop into our Discord community. You can also tweet at me for any questions: @itsmatharoo. DMs are open too.