3D graphics in Game Maker Part 1

It goes without saying that almost every game developer probably started out wanting to make games in 3D. After all, practically all of the AAA games that we played growing up were in 3D, so it comes as no surprise that we want to create our own 3D wonderworks.

What you may or may not realize is that the humble Game Maker comes with the ability to draw 3D graphics. This makes the task of putting 3D games together a lot simpler. However, one thing I would like to emphasize early on is that 3D is just another tool – nothing more, nothing less. Giving a game 3D graphics will not magically make it better. In fact, it can complicate a design that would otherwise work perfectly well in 2D. Conversely, using 3D in GM can also simplify certain graphical aspects of your game. It’s up to you to determine whether it’s worth all the extra effort, and to be aware of the limitations of Game Maker’s 3D functions.

Bear in mind that GM’s 3D functions are fairly simplistic. GM is a 2D game engine with some 3D graphics functions slapped in for good measure. You’ll also need to be quite familiar with GML scripting, since 3D is beyond the scope of the drag ‘n’ drop beginner’s interface.

Furthermore, before you get all excited, take note – Game Maker does NOT do the following:

  • Shaders. Forget about it.
  • 3D movement and positioning calculations. Remember that even though it’s rendering in 3D, GM still operates on a 2 dimensional plane under the hood. If you want to build an FPS where you can fire projectiles up and down as well as horizontally, you’ll have to be prepared to script your own code to have things position and move properly in 3D. GM doesn’t do it for you out of the box. You’ll be fine for 2.5D games though.
  • Accurate 3D polygonal collision detection. GM doesn’t do this for you, nor does it give you direct access to mesh data or the matrix functions you’d need to implement it yourself. There are ways to do collision detection, but not on a per-polygon basis. This makes 3D level design particularly finicky.
  • Intelligent rendering. Most 3D game engines have fancy code running under the hood to ensure that they only draw what they need to, instead of lobbing every polygon in the scene at the graphics card and slowing things down to slideshow speeds. GM doesn’t do this. Depending on the complexity of your rendered scene, you may have to implement your own system to prevent unwanted objects from being drawn.
  • Fancy 3D modelling and animation. While scripts exist for you to import 3D models, and even build animations, GM (say it with me) does not support this out of the box.

I should note that if you search the Internet hard enough, you can find scripts and stand-alone engines that you can plug into a GM game to give it the above functionality. These vary in quality and ease of use, and fall outside the scope of this tutorial.

Okay, enough disclaimers and doomsaying. Let’s see what this thing can do. This tutorial will guide you through setting up a basic 3D scene, and show you what commands are available and how they’re used.

The camera

The camera is your window into the 3D world, so it makes sense that we cover it first. Without the camera, GM won’t know from which viewpoint you want your 3D world to be rendered, and give you a bizarre upside-down default view. As such, it helps to have a core camera object from which you can control the 3D viewpoint. Your implementation may differ, but I generally like to put all of the 3D initialization stuff into the camera object too, since it keeps it in one logical and convenient place.

The first thing we need to do to get our 3D game up and running is to initialize 3D mode. We’ll do this in our camera object. Here’s a sample of the code that goes into the Create event of my camera object:

//Start 3D mode
d3d_start(); 

//Enable lighting
d3d_set_lighting(true); 

//Enable backface culling. This stops the renderer from drawing polygons that
//face away from the camera, speeding the render up.
d3d_set_culling(true); 

//Define and enable a global directional light. We won't go into the details
//right now, but I will cover this in a later tutorial.
d3d_light_define_direction(1,1,0.5,0,c_white);
d3d_light_enable(1,true);

And that does it for your initialization. The next exciting bit will be in your Draw event:

d3d_set_projection(x,y,0,x+20,y,0,0,0,1);

This do-all function specifies where your camera will be placed, and where it will be pointing. And that’s it. Using the information you give that function, GM will render the scene. Quick and easy. For now, the above code will point the camera to the right of wherever it’s placed. Obviously, you can replace the constants here with any variable you like, allowing you to change the viewpoint however you require. I recommend playing with these variables by yourself once you’ve got this tutorial code up and running, to get a better idea of what they do.

Camera settings

Camera settings

One final thing. In 3D mode, it is incredibly important that you take note of the order in which things are drawn, especially the camera. The camera must always be drawn first. No exceptions. So how do you control this? Simple – the “depth” variable that comes part of every GM object you create. If you’ve played with it in 2D mode, you’ll know that objects with higher depths are processed and drawn first. As such, it’s good to set your camera object’s depth to a really high number to ensure that it goes first (I tend to use 10000000 or so). If your 3D scene draws itself from a top-down perspective instead of from the camera, that’s a sure sign that the draw order is out.

Drawing 3D primitives

Now that we have a camera to render stuff from, we need something to point it at. Normally this would involve a whole lot of 3D modeling. Fortunately, GM comes with a set of functions that allows you to render a selection of 3D primitives using nothing but a single line of code. Let’s take a look.

To start off with, we’ll draw a good old cube. Create an object called “Cube”, and place the following in its Draw event.

draw_set_color(c_white);
d3d_draw_block(x-16,y-16,-16,x+16,y+16,16,-1,1,1);

Firstly, we need to define the colour that our primitive is drawn in. We do this using the good old draw_set_color() function that we use in 2D. It’s important to set the colour first, because otherwise our polygons will be drawn in black by default – not the finest colour for 3D objects to be.

Cone!

Cone!

The second line contains the command to draw a cube. The first six arguments allow you to define the dimensions of the cube, using two sets of x,y,and z variables that you want your cube to be draw between. Using these, you can control the proportions of your cube. The last three arguments are used for texture mapping. Since we don’t have a texture at the moment, we’ll set the texture to -1, and the last two variables to 1. Don’t worry – when we eventually delve into texture mapping, I’ll let you know how they work.

Ellipse!

Ellipse!

There are other primitives that you can draw in a very similar way. Once you have this tutorial code up and running, try playing with the following functions: d3d_draw_floor(), d3d_draw_wall(), d3d_draw_cone(), d3d_draw_cylinder(), and d3d_draw_ellipsoid(). You can find out more about them in the GM help file.

Rendering!

Next, put your Camera and Wall objects in a room. Set the room background to a colour that won’t interfere with the visibility of your cube. Then place your camera, and place your cube object to the right of it (since we’ve set the camera to point to the right of where it’s placed).

Room setup

Room setup

Then run the game! You should see a grey square rendered in front of the camera! That’s one face of your cube! Try altering the positioning arguments of the d3d_set_projection() function to get different views of the cube.

That’s it for this month! Until next issue, try playing around with using different primitives, and experiment with positioning and orienting the camera. Next issue, we’ll look at rotating and transforming 3D objects, and find out a little bit more about lighting. Until then!

  • Alejandro

    Wow, great idea! Thank you. I’ll be looking forward to your next issue.

  • Cthulhu

    You suck! It didn’t work! Faggot!

  • Ashtom

    Thanks a lot for pointing out the importance of setting camera’s Depth correctly. I overlooked this and didn’t understand why I was seeing my scene from top!