Along the way we create a very simple game with the main features required for more complex game design.
Movement is approximated in computer games in the same way as television or film. A sequence of frames (the content of the GraphicsWindow in Small Basic) that vary by a small amount are displayed in rapid succession. Usually we aim for a 'frame rate' of around 20 to 50 fps (frames per second).
To control the speed of the action we often put in a small delay during each frame to keep the frame rate at the desired rate. Therefore everything that needs to be done for each frame needs to be done within this time, if it takes longer, then the game will slow or be jumpy. Careful design of the program is required to keep it all running smoothly. It is often better to approximate something and keep the frame rate up, rather than do too much and have a lag. Game play is much more important than complex calculations.
A game usually contains a background and sprites. Sprites are objects that can move and interact on top of the static background. The background may also change with the game play, but this can be more complex to handle smoothly.
Usually there are 3 main stages to the update of a frame, they are:
The temptation when first starting doing movement in Small Basic is to use Shapes.Animate. This is easy to use and great for visual animations like fancy introduction screens with animated text appearing. However, it is not much use for a dynamic game where we need to know where everything is at each frame update so we can detect collisions or other aspects of game play where what happens depends on where things are.
The 3 stages described above are performed in sequence for each frame update. This is usually performed in a continuously repeating 'game loop'. Often in Small Basic the game loop is coded as a While ("True") loop that just keeps running for ever as the game progresses, just updating the display as the game runs.
In addition to the game loop we usually have other sections to the code, including:
The following code stub is a good start for structuring a graphical game. We will fill in some of the bits in the sections below, but most of it will be 'your game'.
Initialise
(
)
'------------------------------------------------
'GAME LOOP
While
"True"
start
=
Clock
.
ElapsedMilliseconds
UpdateGamePlay
UpdateFrame
'A delay up to 20 ms (50 fps) depending on time spent doing work preparing frame update
delay
20
-
If
>
0
Then
Program
Delay
EndIf
EndWhile
'SUBROUTINES
Sub
'Register events
GraphicsWindow
KeyDown
OnKeyDown
KeyUp
OnKeyUp
MouseMove
OnMouseMove
MouseDown
OnMouseDown
'TODO
EndSub
'EVENT SUBROUTINES
We can set up an event to detect mouse position and if a mouse click was made. This involves simply setting variables showing what happened.
Consider the mouse move and down events, we add the following code.
mouseMove
mouseDown
Set the 'mouseMove' and 'mouseDown' flags initially to "False" in the Initialise subroutine.
'Event flags
"False"
Finally set code to process the event in the game loop subroutine UpdateGamePlay.
'Reset the event to false (we have processed it)
ouseDown
We still have to do something on these events, but the structure to handle them is set. Note that we can get the mouse position from GraphicsWindow.MouseX and GraphicsWindow.MouseY, so there is no need to set them in the mouse events.
This is just the same as the mouse entry, except for one aspect. When a key is pressed, there is an operating system delay imposed before another key stroke is registered (auto repeat delay). This is so that when we type we don't get a repeated character unless we hold the key down for some period of time (usually half a second or so).
In a game, we want to detect that a key is down and using the key down event will detect the initial key down, then not record that the key remains down until the auto repeat delay is passed, which introduces a delay between the first key down event and subsequent ones.
To get round this we can use the key down and key up events to set the state of keys we are interested in, regardless of the auto repeat delay.
lastKey
LastKey
"Left"
keyLeft
ElseIf
"Right"
keyRight
"Up"
keyUp
"Down"
keyDown
Initialise the key states in subroutine Initialise.
Handle the key flags in UpdateGamePlay. Note that we do not reset the key flags to "False" since this is handled by the OnKeyUp event and we want to keep reacting to a key held down. If we only want to react to each individual key press (perhaps a space bar to fire), then we will reset the flag to "False" after we process it, requiring a new press for a new action.
So far we have set some basic structure, but have no game yet. This is the opposite to most games beginners start to write - they have sprites and instructions and images, but the structure and control is not present properly.
The lesson is to get the structure and control right first, then add the game play and your content.
So now we add some sprites, just boxes here - again the idea is get the basic game play then add the details, not the other way round.
We will have some enemies and a player. The enemies will be stored in arrays - we need to store the sprite objects and their positions and velocities.
We create the sprites in the Initialise subroutine.
'GraphicsWindow
Title
"My Game"
gw
600
gh
Width
Height
'Sprites
size
50
BrushColor
"Blue"
numEnemy
5
For
i
1
To
enemy
[
]
Shapes
AddRectangle
,
enemyPosX
+
Math
GetRandomNumber
100
enemyPosY
enemyVelX
3
enemyVelY
EndFor
"Red"
player
playerSpeed
playerPosX
/
2
playerPosY
*
Next we consider the player movement based on the arrow keys, we use a variable playerSpeed to control the speed of the player. It is a good idea to use variables rather than just type values when possible; this means that we can change it easily (even during the game to speed or slow the player). Also note that we don't let the player move off the screen.
<
We will let the enemies move with their velocities, bouncing off the walls in the UpdateGamePlay subroutine. On a wall bounce we change the velocity and reposition the enemy sprite on the wall; this prevents the enemies appearing to partially leave the game view if their velocities are large.
'Enemy movement
Finally we update the display for the player and enemies in the UpdateFrame subroutine.
Move
Note that the positions of the sprites we are using is their center, and the Shapes.Move command positions the top left corner, therefore we subtract half the width (and height) during the move. It makes logic easier later if we do the math based on the shape center rather than top left corner and just use the required offset when we do the Shapes.Move.
Basically, collision detection finds that one object is over another. There are 2 simple ways to do this:
We will consider the second, when an enemy hits the player. We will create a subroutine for this called Collision, and check if any enemy overlaps the player using method 2 above.
Collision
sepX
Abs
sepY
And
Sound
PlayClick
AndWait()
We call this subroutine at the end of UpdateGamePlay.
This isn't much of a game and we didn't even use the mouse events, but the basics are there to start adding interesting content. This is the full code.
'Player movement
'Check for collisions
PlayClickAndWait
If you are creating or removing sprites during a game, then special care is needed. In particular if you are finished with a sprite then don't just Shapes.Hide it or let it run off the screen and forget about it (Shapes.Remove it). If you don't remove unused sprites your game will slow. This can be complicated if the sprites are stored in arrays, when you need to remove all array elements associated with the sprite (shape, position velocity etc), which can leave arrays with some elements missing, so simple For loops over array indexes can fail or be inefficient.
A good way to handle this is to temporarily hide sprites, but reuse them by showing them when a new sprite of the same type (e.g. a missile) is needed.
Therefore be careful whenever you create a new sprite during the game (not set in the initialisation). Don't let them just build up indiscriminately.
To get sprites to bounce off each other realistically can be complicated. Basically we need to do the collision in a center of mass reference frame using geometry like the figure below, then convert from the center of mass frame back to the actual frame.
This is an example implementation of this in Small Basic.
CollisionCheck
Ball_Number
j
dx
Ball_X
dy
Ball_Y
Distance
SquareRoot
Ball_Diameter
Cx
Ball_vX
ball_vX
Cy
Ball_vY
ball_vY
Relative_vX
Relative_vY
Nx
Ny
L
A physics engine handles collision dynamics for you, solving the forces and interactions of dynamic objects. You only interact with this kind of simulation by applying forces and torques to the objects, rather than calculating their velocities and positions yourself.
The LitDev extension (LDPhysics) uses the Box2D physics engine and has plenty of examples and documentation. This is quite advanced and some understanding of the physics is required.
So far we have only considered 2D models. 3D is considerably more complex to code yourself and the large number of geometric and rendering calculations requires the use of hardware accelerated methods.