In Part 1, we learned how to set up a basic camera, and draw the 3D primitives that Game Maker provides functions for. If you took my advice and messed around on your own a bit, you’ll have noticed a few limitations to included scripts. For instance, how do you go about rotating primitives? There are no arguments for that in the functions. What if you want to scale primitives in interesting ways, beyond simply changing the size via the draw functions? Well, in this instalment, I’ll be answering these questions using a single concept – transformations.
Transformations?
Yes. Transformations.
Transformations are mathematical operations performed on meshes that change their scaling, rotation and positioning. As always, Game Maker provides an easy set of transformation functions that allow you to do them with only a few commands. They’re a little more involved in their implementation, but worry not, it’s not too complicated.
In this demonstration, we’re going to draw a vertical plane, and spin it around on the spot. Firstly, load up the code we wrote last time and create a new object: “Wall”. In its Create event, place the following code:
faceDirection=0;
Then, the following in its Step event:
faceDirection+=2;
Those two bits of code will define the angle by which the plane will be rotated, and increment it each frame. Now we get to the Draw event, where the magic happens:
//Set colour to white so we can see this thing. draw_set_color(c_white); //disable backface culling. This is to stop our plane from vanishing when it faces // away from the camera. d3d_set_culling(false); //reset transformation matrix d3d_transform_set_identity(); //rotate about the z axis (this function accepts angles in degrees). d3d_transform_add_rotation_z(faceDirection); //then shift the model to the coordinates we placed it. d3d_transform_add_translation(x,y,0); //draw the wall d3d_draw_wall(0,-10,-10,0,10,10,-1,1,1); //reset transformation matrix d3d_transform_set_identity(); //re-enable culling d3d_set_culling(true);
So what does all that messy code do, exactly? The first thing we do is invoke the d3d_transform_set_identity() function. This resets any transformations we may already have done. If this command isn’t used, any other transformations you’ve already done before this step will carry over into this Draw event. This means that these transformations and any other transformations you’ve already done to other objects will add up, and place the object in the wrong place, at the wrong angle. I’m sure you can appreciate why this would be a bad thing.
Next, we turn off backface culling. Backface culling stops the renderer from drawing any faces that face away from the camera, to save on rendering time. In this case, we want both sides to be drawn, otherwise our plane will disappear when it spins to face away from the camera, only reappearing again when it spins back to the correct angle. Try removing this code once the code is up and running to see the difference.
The next instruction tells GM to rotate the plane around the z-axis (vertical). You can rotate meshes around the x and y axes too using their respective commands – you can find them in the almighty GM help file. The third command tells GM to shift the rotated mesh by x and y units so that it’s displaced to the coordinates of the object. Finally, we specify the mesh we want to draw, but instead of drawing it around its x and y coordinates like we did before, we draw it relative to the origin (coordinates 0,0,0). Finally, we reset the transformation matrix again.
So why draw the plane relative to the origin, and then shift it? Why not just draw it in its position like normal people do? The reason is that Game Maker performs all transformations relative to the origin. That means that in ALL transformations, the origin is considered the centre point regardless of where we draw our mesh. Also, be aware that the order in which we place these transformation commands is very important for the same reason. Transformations will be done in the order in which you specify them. If you, for instance, translate first, then rotate, you’d have the same problem as you would if you drew the plane in its x,y position.
It’s much easier to explain graphically, so let’s clarify the above with the aid of some pretty pictures:
- Here we have the plane sitting at the origin, where we’ve drawn it.
- Here we’ve rotated it around the z axis (blue).
- We then translate the rotated mesh by x and y units to its position. The result: one rotated mesh, sitting where we want it.
However, if we drew it at its x,y position, or translated it before we rotated, this is what would happen:
- Draw relative to x,y (or translate. Either way, the plane isn’t sitting at the origin here).
- Rotate around z axis. Rotation happens relative to the origin, so our plane shifts position too. Whoops.
- If we were to translate by x and y again, the plane would shift. It’s now completely out of position. Not a good thing in this case.
Hopefully that clears things up. Of course, there may be times that you want a mesh to orbit the axis from a distance as it did in the second example. Just as long as you understand that transformations are origin-relative and sequential, you can achieve all manner of interesting mesh orientations.
Now that we have our Wall object coded up, place it into the room so that the camera can see it, then run the game. You should see a spinning plane like the one inset.
Try playing around with all the transformations available to you (check the GM help file – there are many more transformations other than the ones covered here). Try building different objects that use different transformations and placing them in the level simultaneously. Try coding transformations in different orders, and performing fancy rotations! The best way to learn is by doing.
Lighting
One of the best parts of using 3D mode is the use of lights in a 3D scene. While it is possible to do lighting in 2D, it normally involves a lot of work using off-screen surfaces and other messy code tricks. In 3D, all you do is define a light, and plonk it where you want it. GM takes care of the rest.
Game Maker allows you to define two different types of light – directional and point lights. A directional light has no location, it simply lights everything in a scene equally. A point light has a specific location and strength, and only affects objects within its range.
You may remember that we defined a directional light in our Camera object last time. Let’s take a look at that code again:
d3d_light_define_direction(1,1,0.5,0,c_white); d3d_light_enable(1,true);
The first command defines our directional light. The first argument is a numeric ID that we assign to the light. I’ve used 1 here, but you can use any number, provided it isn’t too large. This number identifies that specific light, and is used whenever we want to alter it.
The next three arguments define how strongly the light shines on each axis (x, y, and z respectively). Try playing with those values, and watch how the lighting shifts around. You can also use negative values to get lighting from the opposite directions too.
Next, we use the d3d_light_enable command to enable the light, since defining it doesn’t actually switch it on. This command accepts two arguments. The first is the ID we specified in the definition, and the second is whether the light should be on or not. This command can be used whenever you need to turn a light on or off, and is used for both directional and point lights.
Let’s try doing a point light now. Create an object called “Light”, and put the following in its Create event:
d3d_light_define_point(id,x,y,0,64,c_aqua); d3d_light_enable(id,true);
Why ‘id’ for the identifier? This is important to remember: lights that we define via the d3d_light_define commands exist independently of the objects that we create them in. If we destroyed this Light object, the light that we defined will continue to exist. In order to have a convenient handle to the light defined, I’ve assigned it the Light object’s unique instance ID. This gives us an easy way to change or disable the light we’re defining. Additionally, since the instance ID is unique by default, it ensures that we won’t inadvertently influence any other lights that we define in other objects.
After we define the light’s id, we define its position. In this case, I’ve placed it at the same position as the Light object, and at 0 height. Finally, we define the light’s range (64 units) and its colour (in this case, aqua). Then we enable it by using the enable command.
Now place the Light object into the room, close to your Wall. For added effect, remove or comment out the directional light code from the Camera, so that the Light provides the only illumination in the room. Then, run the game!
Two final notes before we finish up: Firstly, if you redefine an existing light, you can change its strength, position and colour. So, for example, if we placed the following in our Light’s Step event;
d3d_light_define_point(id,x,y,0,random(96),c_aqua);
we will have a flickering light that changes its strength every frame. Additionally, if you were to change the x and y positions of the Light object, this code would also synchronise the defined light with the Light object’s new position, so you can have lighting that tracks certain objects.
Finally, if you decide that you don’t want an object to be influenced by lighting, you can disable lighting by using the d3d_set_lighting() command. You’ll remember that we used it in our Camera object to enable lights. You can also use it to disable lighting for particular objects, just as was done for backface culling on the wall. Just remember to re-enable it again, otherwise some of your other objects may not be influenced by lighting either.
This concludes Part 2. In Part 3, we’ll look at drawing point sprites and billboards, as well as show you how to go about creating a heads-up-display for your game.