Maybe Collisions

By mischance I am at the coffee shop this morning, even though it’s Saturday, so I think I’ll take a look at collisions. I feel that game play will be better if the missiles can shoot down the ships. If there is even a “down” in Outer Space …

Naively1, ships have positions and missiles have positions, and if they coincide, bad things happen. The ship takes a hit or explodes or something. The missile probably dies as well but they knew the job was tough when they took it. In practice, I imagine that the missile has some kill radius and if it gets that close to a ship, fragments happen. Of course ships colliding with ships is also a bad thing, so I suppose everyone has a radius2.

I think we’ll want a separate list of objects that can collide, even though right now everything Drawn can collide. When two objects are found to have collided, I think each one gets a chance to do something dramatic, but after that, it must be removed from the Drawn list. (There may be exceptions, but we’ll deal with those later.) So we have a sort of N-squared issue here, plus a removal issue. We’ll need to go through the Colliders list, checking each one against each other one to see if they have collided. And if they have, we need to remove them from the Colliders, from Drawn, maybe from Touched. Basically that guy is gone.

I know from long sad experience that removing things from lists you’re looping over is fraught. Maybe we should have a sort of DeadObject class and convert dead guys to that. Or maybe a dynamic list for “remove later”, after we’re done looping.

Speaking of dying, our missiles go on forever at present: they never run out of steam, if in fact they are steam powered at all. In addition, the ship can fire an unlimited number of missiles at a great enough speed to draw a line across the screen. While this raises an interesting tactical possibility, I think we’d like to have a finite store of missiles, fired at finite intervals, going a finite distance or time, and then fizzling out. This is germane for a number of reasons, including that it might help us solve the problem of things dropping dead.

I’ll begin with the firing interval. I think we’d like to be able to fire maybe five or ten missiles per second. And Codea tries to update the screen fifty times per second, if I recall, so what if we just added in an update count that objects can look at: like this:

-- S3 Spacewar

-- The Universe

U = {}
U.tick = 0
U.Drawn = {}
U.Touched = {}

function setup()
    Ship(1)
    Ship(2)
end

function touched(touchid)
    for _,obj in ipairs(U.Touched) do
        obj:touched(touchid)
    end
end

function draw()
    U.tick = U.tick + 1
    background(40, 40, 50)
    
    for _,obj in ipairs(U.Drawn) do
        obj:move()
    end
    
    for _,obj in ipairs(U.Drawn) do
        obj:draw()
    end
end

function addTouched(anObject)
    table.insert(U.Touched, anObject)
end

function addDrawnAtTop(anObject)
    table.insert(U.Drawn, anObject)
end

function addDrawnAtBottom(anObject)
    table.insert(U.Drawn, 1, anObject)
end

I decided it was time to create the Universe, which because the universe and I are old pals, I called U. Note that I added U.tick and update it in the draw function. I believe we’ll probably move most everything into the universe in due time, including most of the functions that are presently global. But that’s not needed yet: my motivation for U was to avoid yet another global variable, which I finessed by creating U and moving things inside it.

Note: Drawn and Touched are capitalized. My rough coding standard is that constants are all caps, global variables are upper case, other member variables generally lower case. I’m not sure whether tick should be upper, or Drawn lower but I think they should be the same. I think upper for Tick. I mention this odd little aside because as I work, I try to notice little things like this that seem to make no difference but that can add up. Sometimes I just notice: sometimes I fix them. This time, I’m fixing. Assume Tick will be upper case next time you see it.

Now to Ship. We have two issues at least: first, fire only once in a while, and second, run out of missiles after a while. For firing interval, let’s try five per second, which is every ten ticks:

function Ship:fireMissile()
    if U.Tick%10 == 0 then
        Missile(self)
    end
end

That seems a bit fast: here’s what it looks like with the fire button held down:

Mod 15 looks a little better. What I’d really like, though, is to express this in missiles per second and make it a ship constant:

function Ship:init(shipNumber)
    local missilesPerSecond = 3
    self.MissileMod = math.ceil(50/missilesPerSecond)
    ...
    
function Ship:fireMissile()
    if U.Tick%self.MissileMod == 0 then
        Missile(self)
    end
end

Three per second looks pretty good:

(You’ll have to look carefully. I was whirling around firing at full speed. Looks good enough for now. We may need to change colors for these pictures, though. Hope I have enough versions saved to roll back.) Now let’s have a finite supply of missiles, maybe 50.

function Ship:init(shipNumber)
    local missilesPerSecond = 3
    self.MissileMod = math.ceil(50/missilesPerSecond)
    self.missileLoad = 50
    ...
    
function Ship:fireMissile()
    if self.missileLoad > 0 and U.Tick%self.MissileMod == 0 then
        self.missileLoad = self.missileLoad - 1 
        Missile(self)
    end
end

This does correctly limit the number of missiles we can fire, but it shows up a playability problem. I wanted to count the number I fired, so I was hitting the fire button rapidly. Turns out that doesn't work. Why not? Because firing is dependent directly on the `Tick`, which means I have to have the button down on a tick that matches `MissileMod`. Not so good. We want the time since last firing to be congruent to zero mod `MissileMod`, not the absolute time. We'll need a time last fired. But think: we're going to write something like this:

~~~lua
function Ship:fireMissile()
    if self.missileLoad > 0 and U.Tick - self.timeLastFired > self.MissileMod then
        self.timeLastFired = U.Tick
        self.missileLoad = self.missileLoad - 1 
        Missile(self)
    end
end

I don’t entirely love that but it should work. But when we initialize timeLastFired we want it such that we can fire immediately. So it needs to be initialized to some large negative number. MissileMod is probably large enough.

function Ship:init(shipNumber)
    local missilesPerSecond = 3
    self.MissileMod = math.ceil(50/missilesPerSecond)
    self.missileLoad = 50
    self.timeLastFired = -self.MissileMod
    ...

This works but it turns up a possible issue: I can tap the fire button faster than three times a second, which means that when I tap as fast as I can, I don’t get an equally spaced pattern. I think that’s just too bad: if you try to fire too fast, nothing happens. I’m calling that “as intended” even though it did surprise me.

How do we stop these babies?

Now to the hard part, giving the missiles finite life. First, how long? I’d like them to be able to cross the screen and hit you from behind, and maybe a bit more. We could do that in a couple of ways, giving them a maximum time of life, or a maximum distance traveled. The latter is kind of an interesting idea: if you did it just right could you lay down some missiles that hung around forever, like mines? I rather like that idea, so let’s make it distance traveled and see what happens. Let’s try one and a half times WIDTH as the maximum distance. And for a quick and dirty implementation, I’ll make the bullet stop moving and draw a big ellipse just to see what happens.

That’s a picture of a missile, fired at an angle, that timed out. Here’s the code:

Missile = class()

function Missile:init(ship)
    self.pos = ship.pos
    local velocity = vec2(0,1):rotate(math.rad(ship.heading))
    self.vel = ship.vel + velocity
    self.maxDistance = WIDTH*1.4
    self.distance = 0
    self.alive = true
    addDrawnAtTop(self)
end

function Missile:move()
    self.distance = self.distance + self.vel:len()
    if self.distance > self.maxDistance then
        self.alive = false
        return
    end
    self.pos = clip_to_screen(self.pos + self.vel)
end

function Missile:draw()
    pushMatrix()
    pushStyle()
    strokeWidth(1)
    stroke(255,0,0,255)
    fill(255,0,0,255)
    translate(self.pos.x, self.pos.y)
    if ( self.alive ) then
        ellipse(0,0,3)
    else
        ellipse(0,0,10)
    end
    popStyle()
    popMatrix()
end

We initialize maxDistance (which I see should be capitalized as a class constant), init distance, adjust it by the amount moved (vel), check for having gone too far, mark ourselves not alive and stop moving. When we draw, we check whether we’re alive and if not, draw the big dot.

This certainly won’t do for the game, but let me remind you how we’re working here and then look to what’s next. My style of work, at least for this project, is to do absolutely simple things, keeping the game running so that I can look at it, and improving the code “just enough” as I go. “Just enough” means, roughly, not letting it get too bad, but not pushing design beyond what we really know.

Looking at what we’ve done so far today, we’ve added a simple time abstraction, used it to throttle firing rate, immediately learned that we don’t quite like it so that we had to add in an elapsed time notion, and liked that better. Then we gave the missile a finite life, and checked that it knows when to die. However, it doesn’t die, it just sits still looking like a big red dot. We’ve not removed it from the Drawn table, and we don’t even know how to do that.

We’ll leave that for another day. I did do a little experiment with Codea Lua’s tween capability, and it looks like this:

MOVIE HERE (missile-pulsing)

Remind you of anything? :) Here’s the code, just for interest:


--# Circle
Circle = class()

function Circle:init(x)
    self.alpha = 255
    self.radius = 3
    tween(1, self, {radius = 50, alpha = 50 }, {loop = tween.loop.forever})
end

function Circle:draw()
    pushStyle()
    strokeWidth(2)
    translate(WIDTH/2,HEIGHT/2)
    stroke(255,0,0, self.alpha)
    fill(255,0,0,self.alpha)
    ellipse(0,0, self.radius)
    popStyle()
end


--# Main
-- TweenThing

-- Use this function to perform your initial setup
function setup()
    c = Circle()
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)
    c:draw()
end

The trick there is that the tween loops forever, adjusting alpha and radius from their initial values to the ones in the second table. Might be an interesting effect, but it’s not much like “real” Spacewar. Still, interesting …

See you next time …

  1. As is my wont … 

  2. Remarks regarding my radius are not appreciated at this time.