Download C# source code and ClickOnce installer - requires .NET Framework 2.0
The first step is to get the ship moving around the screen properly. For now, there are no asteroids, and the ship is just a line.
Although the example game is written in C#, the math and logic will be very similar in most languages, and are far more significant than the details of the implementation.
The code excerpts below use the Vector2 class included in the source. It provides basic operations such as addition and multiplication through overloaded operators. You can just store the components seperately if you want, and perform individual operations on them (ax = bx + cx; ay = by + cy; rather than a = b + c;). However, this tends to complicate the code unnecessarily, and is much harder to manage, since you have twice as many variables (worse, in 3D, three times as many).
Movement in Asteroids
- The game uses a basic Newtonian physics system. The ship has a position, velocity, and orientation.
- The direction the ship faces is independent of the direction it's moving in. If you turn the ship, it will
continue to move in the same direction.
- If you release the thrust key, the ship should continue to move for some time (inertia)
Turning the ship
We need to be able to handle the user pressing two keys at once. This often happens by accident in fast gameplay - the right key is pressed enough to contact before the left is fully released, or the user starts to switch from left to right, presses the left key, then changes their mind before releasing the right key. Therefore, we need to keep track of whether each button is pressed, rather than simply set the direction whenever a key is pressed or released.
Some input systems simply specify whether every key is up or down. In this case, the ship can be allowed to stop turning if both keys are pressed at once. It is usually preferrable to continue in the direction of whichever key was pressed last, however. This is fairly easy to do with event-based input (such as Windows Forms or messages in MFC or plain Win32).
bool keyLeft, keyRight;
// When the left arrow is pressed:
keyLeft = true;
TurnDirection = left;
// When the left arrow is released:
keyLeft = false;
TurnDirection = right;
TurnDirection = 0;
// On update:
float deltaTime; // time in seconds since last update
if (TurnDirection == left)
direction -= deltaTime * turning speed in radians per second;
For games that allow both forwards and backwards movement, the up/down keys can be handled similarly.
Updating the position
Vector2 position, velocity, acceleration;
calculate acceleration based on what direction the ship is pointing, and whether the user is pressing the thrust button
position = position + velocity * deltaTime + acceleration * (deltaTime * deltaTime / 2);
velocity = velocity + acceleration * deltaTime;
This comes from basic physics. The last position term tends to make the results more stable if the framerate varies. It still won't be perfect - the ship's movement will be slightly different at different update rates - but it's about as good as it gets without resorting to far more complicated integrators.
Finding the acceleration
Given an angle in radians representing the ship's direction (direction), we can find the acceleration from thrust with:
acceleration.x = amount * cos(direction);
acceleration.y = amount * sin(direction);
acceleration = acceleration - (small number) * velocity;
The trigonometric functions always use radians. Also, with more complex physics involving rotating objects, you will frequently find you'll have to either use radians or add factors of pi to formulas. Of course, it's a lot easier to understand an angle shown in degrees, but the user never gets to see the angle here, except indirectly through the appearance of the ship. Even if you must display the angle to the user or allow them to enter it, it's easier to store things as radians internally and convert on entry/display than to perform the conversion over and over in every calculation.
We also subtract a small amount of the current velocity to provide drag. This will gradually bring the ship to a stop if all of the controls are released. Under thrust, it will also cause the ship's direction of motion to tend to turn towards the direction it's facing - the sideways component of the velocity is soaked up by drag, while the forward component is continuously replaced by the engines.
Restricting the top speed
Some possible approaches:
- Allow the ship to continue to accelerate indefinitely. This can cause problems with collision detection - if the ship moves fast enough, it may pass through objects. At high speeds it may also be difficult for the player to see how the ship is moving so they can slow it down.
- Set the multiplier used for drag to the desired top speed divided by the acceleration. This is the easiest, but gives the programmer less control over the ship's movement.
- Set a speed cutoff. The sample does this by reducing acceleration whenever the next velocity update (velocity = velocity + acceleration * deltaTime) would cause the ship to reach an excessive speed.
Wrapping around the edge of the screen
If the X or Y coordinate exceeds the size of the playing area, we can use the modulus (%) operator - new X = old X % width. This won't work for negative values, however. We could just add width to the coordinate until it's greater than 0, but this is somewhat inefficient, and could cause the app to freeze if the position is somehow corrupted. new X = width - (-old X % width) produces the proper result without looping.
Notice, however, that this can cause the ship to disappear from one side while the back is still partially visible. Moving asteroids will also have this problem. This could be solved by moving the edge of the game field slightly off-screen, or by drawing up to 4 copies of the ship when it's near the border. In the latter case, collisions with asteroids near the border must also be handled carefully. In the former, the border will probably need to change size dynamically based on the object's size.
Next time: Proper graphics and asteroids.