Saturday, August 25, 2012

Tutorial 14 - SpaceWar! & Extended Ship Class


14.1 The Extended Ship Class

In Interlude 11 we introduced a ship class. We will use this to explore some new concepts in game design. In particular we will look at:
  1. Using a mesh to represent our ship image;
  2. Simple path finding for our ship;
  3. The use of polar co-ordinates to determine the angle and distance to a destination point; and
  4. Simple collision detection by testing for the intersection of two bounding rectangles.
The updated ship class is shown below. This class will automatically move your selected ship to a point that you tap on the screen. The init() function sets up the following variables:
x, y -  Are the screen co-ordinates of the ship. Depending on the mode variable, these will refer to either the bottom left of the bounding rectangle (CORNER) or the centre (CENTER). If x and y aren't specified in the constructor then (0,0) is assigned because nil evaluates to false and hence the second parameter in the "or" construct is assigned. We use this trick for all of the specified init() parameters to provide default values.
enemy - is a boolean. If true the ship will be drawn in red and wont be selectable.
heading - is the bearing in degrees that the ship will point towards. 0 (the default) positions the ship facing right. A value of 180 degrees will face the ship left.
speed - determines how far the ship will move each frame (if it is moving which is determined by the boolean shipMoving).
width and height - refer to the rectangle which bounds the ship. Changing these will alter the dimensions of your ship.
selected - a boolean which indicates whether the ship has been selected. You can select one of your ships by tapping it. A selected ship will be indicated by a rectangle drawn around it. Tapping the same ship again will deselect it.
imageMesh - is a mesh containing the representation of your ship. It is built up using two overlapping triangles.
destination - this variable is a vec2 which contains the (x, y) co-ordinates of the point you want to move the ship to. If the boolean shipMoving is true then this class will automatically move its ship object towards this point every frame. Initially this variable is nil.
The Ship class currently contains three functions to provide the appropriate ship behaviours. We will look at each of these in turn.
Ship:updatePosition() - if the ship is moving, this function provides a simple path finding algorithm to move the ship towards the destination co-ordinates. In addition, it will automatically point your ship in the right direction. It does this by working out the polar co-ordinates of the destination using the current ship position as the origin. The two variables returned by math.polar (found in the Math tab of the project) are the distance from the ship to the destination and the angle (or heading). We use the distance to determine when to stop moving the ship. When the distance is less than the width of the bounding box we are roughly where we want to be. If we try to get much closer the angle starts getting a bit wonky. The angle is assigned to the heading class variable and is used to rotate the ship in the right direction when we draw it.

Ship:draw() - is responsible for drawing your ship each frame. If the ship is moving (shipMoving is true) then we first call the Ship:updatePosition() function to calculate the new ship position (x, y). We then translate to the new ship position and rotate the ship to the current heading. If the ship is selected a green rectangle will be drawn around the ship. The last thing we do is draw() the mesh (imageMesh) which represents the ship.
Ship:touched(touch) - This function does the touch handling. We only use this function for our own ships not enemy ships. Tapping your ship will toggle its selection. If there is a tap on another part of the screen when a ship is selected, it will start moving towards that point. This is achieved by setting shipMoving to true and assigning the destination co-ordinates. The pointInRect() function (found in the Math tab) determines whether the current tap was on the ship.

14.2 The Main Class

The Main class is very simple. We create two ships in init() and our twinkling star background using the old favourite Twinkle class. In the touched(touch) function we pass on the touches to both ships and in draw() we draw the twinkling stars and the two ships by calling their associated draw() functions.

The only other point of interest is the simple collision detection provided by the intersectRects() function (found in the Math tab). At the moment, if there is a collision between ships we just print out "BANG!!!" This will be enhanced in later versions of the program.

14.3 Download the Source Code

As usual, the source code for this tutorial may be downloaded from dropbox using the following links.
1. Main Class - described in section 14.2;
2. Ship Class - described in section 14.3;
3. Math File - which contains a number of helper & collision detection functions;
4. Colors File - which defines some standard colours; and
5. Twinkle Class - which provides our background star field.

14.4 Problems with this Design

This code is functional but it has some issues. In no particular order these include:
  1. Our ship speed is dependant on our frame rate. In a later tutorial we will look at methods we can use to move our ship at a constant speed regardless of the frame rate.
  2. The path finding algorithm is rudimentary at best. It doesn't handle obstacles, different "terrain" or take account of enemy positions. Our next tutorial will develop the A* path finding technique which is much more sophisticated.
  3. Collision detection is very rough. As with the other issues we will evolve our collision detection in subsequent tutorials.
  4. The selection methodology when there are multiple own ships is a bit flakey.
  5. Portrait orientation is not handled properly.
All of these issues will be addressed in subsequent tutorials.