Space Quest: The Beginning
1st Tutorial
Welcome,
aspiring game programmer! I shake you warmly by the hand (actually,
that's one of Willy Wonka's lines). We will learn to design our first
game level in this tutorial.
We will work with a WYSIWYG level editor which can be downloaded for free from here: http://www.3dgamestudio.com/
Install the application, and then run it.
Then, create a room that is centered in the origin. Use a total of five
blocks; you will need to keep a small opening at the top, because that's
how our player will leave the current game level.
Here's
how my level looks now. I have also added the exit gate, which is
created as a separate prefab; that's why it has a different color in the
level editor. We can't add actions/functions to regular walls; however,
we can assign them to entities (3D models, sprites, and level prefabs).
To create the gate, start a new level, add a
wall that's got the proper size, build it, and then save it. Then, open
the old level and add the gate as a prefab. If its size isn't right, you
can recreate it or (my recommendation) adjust the surrounding walls.
When you are done, build the level, and then
run it. Press the "0" (zero) key to unlock the camera, and then fly in
the level to see what you've created. Next time we will add some code
for our player, power-ups, boulder and exit gate.
Writing Our First Function
2nd Tutorial
Welcome
back! This time we will start to write our own code. So, open your
favorite code/script editor, and then type in the lines of code in the
image. You can download Notepad++ if you've never used it before; it is really easy to use and very powerful.
As you can see, the lines of code are enclosed
within a pair of winged brackets; that's how you start/end a function or
action. By the way, functions are executed on their own, while actions
can be attached to various entities. To give you an idea, we will attach
an action to the exit gate.
Everything
starts with function main; without it, there's no way for the code to
be executed, so our game wouldn't do anything. Always make sure to
create a function main() in your games, and have it include instructions
that initiate things, load levels, etc. One more thing: main() is of
type void because it doesn't return any value; we don't want to evaluate
and reuse the result of its execution.
The first line of code sets a black sky color;
we can use RGB values which range from 0 to 255 here, with 0 being the
darkest black, and 255 the brightest white. Then, we limit the frame
rate to 70 frames per second. I know that modern PCs deliver much more
than that, especially when we are coding a simple game, but we need to
think about the guys who own older computers as well. By limiting the
frame rate to 70 fps, everyone will have a nice, smooth experience;
don't forget that most gaming consoles do the same thing, limiting the
frame rate to 60.
The following lines of code set the video
resolution (mode 8), color depth (32 bits) and screen mode (full
screen). We wait for a frame, and then we evaluate if the game
soundtrack is already running or not. If it doesn't run, we start
playing it in a loop, and then we load the game level we've created in
our previous tutorial. Finally, we wait for 3 frames to make sure that
the level has been loaded in its entirety, and then we set the camera
angles in a way that makes it focus on the player. By the way, the
camera is our eye in the level, the way we're seeing what's happening in
the game.
It's time to create our player. You will need a
player model (a spaceship), which can be downloaded for free using the
same resource site which was mentioned in the first tutorial. Choose any
model you want, its shape doesn't matter. We will also need to create a
target for our player, an entity that will guide it as it moves around
in the level. We will make this entity invisible, of course.
The player will fire a bullet whenever we press
the left mouse button; this way, we'll (hopefully) be able to defeat
the enemies. The last lines of code inside our function will keep the
camera pointed towards the player at all times, making sure that it is
fully visible each and every frame.
That's all for now! Duplicate my code and then
save it as a .c file. We will add all the missing pieces next time.
Coding The Player Function
3rd Tutorial
It's
time to code player's function! The first line waits until the virtual
entity that guides the player around is created; otherwise, the code
would look for it without finding it, and the game would crash. We
define a few variables, and then a couple of vectors.
A variable can store numerical values, while
vectors group three variables together. You would use a variable to
store the number of bullets, and a vector to store player's X, Y, and Z
coordinates, get it?
The player will use a polygon-based
collision detection mechanism. Most games use simplified collision
detection shapes (spheres, ellipsoids, etc.) but since we're going to
activate "real" collision for a single entity (player's ship),
performance won't take a hit. We will also set player's FLAG2, because
we want to get access to its properties from within other functions. By
activating FLAG2, we're creating a unique entity.
We'll use a while loop to run player's
instructions; that loop will continue to do its job for as long as the
player is alive and its height doesn't reach -100. You will see why I
coded the things that way later on.
The player will constantly turn toward its virtual
target, which is placed in front of it. If the player needs to make a
sudden mouse move (to avoid an enemy that's really close, etc.) the code
will allow it to do that by rotating the ship faster; otherwise, if the
mouse movement isn't fast, the code will change player's pan angle
gradually.
We are constantly monitoring the distance between
the player and its virtual target; we want to make sure that it is
always kept below 50 units. If it exceeds this value, we move the player
towards the target, ignoring passable entities and the player itself. I
will teach you how to code engine exhaust particles in a future
tutorial; the code that computes their position is already included in
player's function. We will code particles that are generated 20 units
below player's ship and 5 units above its origin; otherwise, they would
be generated inside the player model, so they wouldn't be visible.
The last code section checks player's y coordinate.
If the gate opens, the player will be able to exit the level. In this
case, its z coordinate will be set to -100 units, and the level number
will be increased. The player is now ready to move on to the next level.
Auxiliary Player Functions
4th Tutorial
Welcome
to a new tutorial. This time we will code a few functions that may not
look that exciting, but are essential. The first one controls the
virtual target that sits in front of the player, helping it rotate and
move around. Sure, we could have created a function which moves player's
ship directly, but I guarantee that its movement wouldn't have been
that smooth; it is much better to create an invisible entity in front of
the player, and then have it move towards it, adjusting its speed and
angles gradually.
Okay, let's explore the code. So, the first
function is attached to our invisible player target, which was also made
passable; this way, we prevent it from getting stuck by colliding with
enemies, space rocks, etc. The target is displaced when you move the
mouse around; tweak those 50s if you want to change player's movement
speed.
The next function is activated when the
player collides with something. You don't want that to happen too often,
because whenever this function is run, the player loses a life. When
this unfortunate event happens, we make the player insensitive to
collisions. Since a typical collision can last for a few frames, we'd
subtract much more than one from players_lives, and we don't want that
to happen.
It's time to create an explosion; you will
need to use an explosion sprite with 9 frames here, which can be
downloaded for free from the Internet. If you can't find a sprite with 9
animation frames, you can use one that's got a different number of
frames, of course; just replace the 9 from "while (my.frame < 9)"
inside function player_explodes() with your sprite's number of frames.
Since the player has died, it makes sense to
play a dead sound. Then, we wait for 2 seconds, we make sure that the
player doesn't move the mouse anymore, and then we reset player's
position, making it sensitive to collision with the level entities
again.
Weaponizing The Player
5th Tutorial
This
tutorial will teach you how to code player's weapon. As you may
remember from one of the previous tutorials, player's ship will fire
bullets when you press the left mouse button. To make this happen, I
created function players_bullets().
The first line of code ensures that the
player is alive; it doesn't make any sense to fire bullets if you are
dead, right? Then, we set up a vector which will store the coordinates
of our bullets. We want to create the bullet in front of player's ship;
otherwise, it will collide with it, making the player die without being
hit by an enemy projectile - how lame is that?
This is a 3D space game, so we'll use a 3D
projectile named laser1.mdl. Once again, you can use TurboSquid or any
other 3D game model site to get the file for free. We also play a laser
firing sound, which lasts for one second.
Bringing Particles To Life
6th Tutorial
I
know it's been a while, but I have finally managed to find the time for
a new tutorial. This time we will learn how to create particle effects.
We have used sprites before (remember the explosion effect?) but the
truth is that particles run much faster. We can easily create fantastic
effects that use 10,000 particles, but even modern computers would
struggle while trying to render 10,000 sprites at the same time. I hope
that you are as excited as I am - let's get started!
The first function fades the fire/engine
exhaust effect; it decreases the alpha (transparency) value until it
goes below zero. Believe it or not, particles can live even if they
aren't visible, so we make sure to set their lifespan to zero - this
destroys them, freeing up the video card for more work (new particles).
Function fire_effect() sets random X and Y
velocities for our particles; we don't want to touch the Z component,
because it would bring the particles closer to player's eyes. The
transparency value ranges from 25 to 75, and the bitmap used for the
fire effect is a tga file. You already know the drill; you'll find lots
of bitmaps like these on the web. The initial size of the particle is 25
units, and its BRIGHT and MOVE flags are set. The last line of code
sets the event function - the one discussed above.
Function fade_star() is the event for the one
below it; it is very similar with function fade_fire(), so there's no
need to discuss it.
The last function in this tutorial is
star_effect(). Just like the ones above it, it takes a particle pointer
as a parameter, and then sets the alpha, bitmap, size, flags and the
event function.
The code uses a random transparency value,
which ranges from 5 to 55, while the size of the generated particles
ranges from 2 to 3. This way, we get a great star field effect, even
though it uses a single bitmap.
Power-Ups And Asteroids
7th Tutorial
Welcome
to a brand-new tutorial! This time we will code the power-ups that need
to be collected and the asteroids that should be avoided.
You will notice that we are going to use
actions this time; we need to do that, because we will attach them to
entities. The first action is attached to the power-ups; the gate to the
next level won't open until the player has collected all the items.
Each power-up is passable, and we use the
total_power variable to store the number of available entities. The code
waits until the player entity is loaded, and then sets the height of
all the power-ups to player's Z coordinate; otherwise, if we tried to
place them in the level, some of them may be above or below player's
height, making it difficult for him/her to collect them all.
We want to make those entities look as random
as possible; that's why they use random pan, tilt and roll angles. Each
power-up is driven by a "while" loop which rotates the entity and keeps
running until the distance between the player and the power-up is less
than, or equal to 30 units. As soon as that happens, we play a sound
effect, we increment players_power, and then we add a random score that
ranges from 8 to 12. The power-up will be made invisible, and it will
disappear for good after 3 seconds.
The action attached to the asteroid waits
until the player shows up, aligns its Z to player's Z, gets a random
orientation angle and keeps spinning all the time. Since this is a
deadly asteroid, the loop will continue to run for as long as the entity
exists; we don't want to end it prematurely, because it will be a
permanent threat.
That's all for today! And here's your
homework: make these power-ups and asteroids look more random by setting
different sizes for each one of them. You will have to play with the
entities' scale_x, scale_y and scale_z parameters, but I promise that
the code is quite simple. Good luck!