Tutorial details

Tutorial 14 - SpaceWar! & Extended Ship Class | App Code for Sale | Preview

Tutorial 14 - SpaceWar! & Extended Ship Class | iOS Tutorial

How to work with ship class

Overview PAGE TOP


14.1 The Extended Ship Class PAGE TOP

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.

1   --# Ship
2   Ship = class()
4   function Ship:init(x, y, enemy, heading)
5      -- you can accept and set parameters here
6      self.x = x or 0
7      self.y = y or 0
8      self.enemy = enemy or false
9      self.speed = 3
10     self.heading = heading or 0
11     self.mode = CORNER            -- Supported co-ordinate modes are CENTER or CORNER
12     self.width = 50
13     self.height = 35
14     self.selected = false
15     self.imageMesh = mesh()
16     self.destination = nil
17     self.shipMoving = false
19     -- Ship Image Mesh Vertices
21     self.imageMesh.vertices = {vec2(0, 0), vec2(0, self.height), vec2(self.width, self.height/2), 
22                                vec2(0, 0), vec2(self.height/2, self.height/2), 
23                                vec2(0, self.height)}
25     -- Set the colour of every vertex in the mesh
26     -- red for enemy ships, blue for friendly.
28     if enemy then
29         shipColorTable = {whiteColor, whiteColor, redColor, 
30                           blackColor, blackColor, blackColor,}
31     else
32         shipColorTable = {blueColor, blueColor, lightBlueColor, 
33                           blackColor, blackColor, blackColor,}
34     end
35     self.imageMesh.colors = shipColorTable
37  end
39  -- If a destination is set, the ship will automatically move
40  -- towards this point each frame.
42  function Ship:updatePosition()
44     local distance, heading = math.polar(self.destination.x, self.destination.y, self.x, self.y)
46     if distance <= self.width or pointOffScreen(self.x, self.y) then
47             self.shipMoving = false -- approximate destination reached
48     else
49         self.heading = heading
50         if self.destination.x > self.x then
51             self.x = self.x + self.speed
52         elseif self.destination.x < self.x then
53             self.x = self.x - self.speed
54         end
55         if self.destination.y > self.y then
56             self.y = self.y + self.speed
57         elseif self.destination.y < self.y then
58             self.y = self.y - self.speed
59         end
60     end
62  end
64  function Ship:draw()
66     -- Codea does not automatically call this method
67     -- Draw the ship at the current (x,y) co-ordinates,
68     -- once updated if it is moving.
70     pushStyle()
71     pushMatrix()
73     if self.shipMoving then
74         self:updatePosition()
75     end
77     if self.mode == CORNER then
78         translate(self.x, self.y)
79     elseif self.mode == CENTER then
80         translate(self.x + self.width/2, self.y + self.height/2)
81     else
82         print("ERROR:: Ship translation failed - unsupported co-ordinate mode.")
83     end
84     rotate(self.heading)
86     -- draw rectangle indicating selected ship
88     if self.selected then
89         stroke(greenColor)
90         strokeWidth(3)
91         fill(clearColor)
92         rect(-10, -10, self.width + 20, self.height +20)
93     end
95     -- draw ship
97     self.imageMesh:draw()
99     popMatrix()
100    popStyle()
102 end
104 function Ship:touched(touch)
105    -- Codea does not automatically call this method
106    -- pointInRect() function is located in Collisions
108    if not self.enemy and touch.state == ENDED then 
109        -- if touch is on the rectangle bounding the ship
110        if pointInRect(touch.x, touch.y, self.x, self.y, self.width, self.height) then
111            if self.selected then
112                self.selected = false
113            else
114                self.selected = true
115            end
116        elseif self.selected then -- if selected set ship destination
117            local dx, dy
118            if self.mode == CORNER then
119                dx, dy = touch.x - self.width/2, touch.y - self.height/2
120            else
121                dx, dy = touch.x, touch.y
122            end
123            if self.destination == nil then
124                self.destination = vec2(dx, dy)
125            else
126                self.destination.x, self.destination.y = dx, dy
127            end
128            self.shipMoving = true
129            print("Ship destination - x: "..self.destination.x.." y: "..self.destination.y)
130        end
131    end
133 end

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 PAGE TOP

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 PAGE TOP

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 PAGE TOP

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.

Reference PAGE TOP


0 Comments Leave a comment

Please login in order to leave a comment.

Newest first

to your Chupamobile Account.

The Easiest way to Launch your next App or Game.

Join Chupamobile and get instant access to thousands of ready made App and Game Templates.

Creating an account means you’re okay with Chupamobile’s Terms of Service and Privacy Policy.