Asteroids 16: It's Alive!!
Just arriving ‘at work’ this morning, I’m thinking we’ll make the Ship move. We need a safe place to land first.
See the end of the article for news of a released defect! Thanks, Dave1707!
“At work” refers to my computer desk in what would be the living room of the house if we were rational people. My dear wife Ricia has gone off to forage for herbs and bananas, so there’s a bit of time before our traditional Sunday breakfast.
It’s about time to make the Ship move, and that’s my plan at this moment, before reviewing the code. I know I would like to convert the ship to an object, because I find that structure to be more clean and easy to manage. Whether we need to do that now, however, is an open question.
Let’s see where we stand. In the Main tab, we do call moveShip
:
function draw()
Ratio = DeltaTime/0.0083333
--displayMode(FULLSCREEN_NO_BUTTONS)
checkButtons()
pushStyle()
background(40, 40, 50)
U:draw()
drawButtons()
drawShip()
moveShip()
drawMissiles()
drawSplats()
drawScore()
popStyle()
U:findCollisions()
end
That’s not exactly where it belongs, but it tells me we must have an empty function over in Ship tab. Oh, right, not empty, because it handles turning and firing:
function moveShip()
if Button.left then Ship.ang = Ship.ang + 1 end
if Button.right then Ship.ang = Ship.ang - 1 end
if Button.fire then if not Ship.holdFire then fireMissile() end end
if not Button.fire then Ship.holdFire = false end
end
This code is a bit “iffy”, but it’s pretty clear where we can put our motion code. That’s clean enough that I’m inclined to go ahead. Let’s think about …
How To Move
Well. In the original game, the ship accelerates in the direction its pointing, up to some maximum speed. According to my reading on the old program, it also slows down when the power is off. Apparently the ether is sticky in this universe.
So let’s think … the ship will have a velocity in whatever direction it is presently moving. Each cycle, friction will shorten that velocity a bit. And, each cycle, if the power is on, a small increment of velocity will be added to the current velocity, and, if we remember, the velocity will be limited to its maximum. That seems simple enough.
We know that a speed of 1.5 is pretty decent for the asteroids, so that gives us a sense of what kind of numbers we’re dealing with. I’ve not read the 6502 code to get a sense of what the ship’s acceleration rate is, but watching the old videos, it’s clear that it can fly at least twice as fast as the asteroids. Let’s start with a maximum speed of 3.0.
Now asteroids move themselves like this:
function moveAsteroid(asteroid)
local step = Ratio*vec2(Vel,0):rotate(asteroid.angle)
local pos = asteroid.pos + step
asteroid.pos = vec2(keepInBounds(pos.x, WIDTH), keepInBounds(pos.y, HEIGHT))
end
That’s really not ideal, since asteroids never change direction, so that rotate
could be folded into Asteroid shape. That’s for another day. For the ship, we’ll need to handle its angle separately from its velocity, since it can slide sideways or backward just fine.
So let’s store ship velocity as a vector representing the step to be taken to move one cycle’s worth. This vector will have a length less than or equal to 3. When the go button is down, we’ll add a small increment to the velocity, pointing in the direction the ship is pointing.
Sounds easy, let’s do it.
Move The Ship
Because of how iffy that move method is, I’ve decided to break out actual ship motion. We’ll need to improve these names, but that’s not for now
function moveShip()
if Button.left then Ship.ang = Ship.ang + 1 end
if Button.right then Ship.ang = Ship.ang - 1 end
if Button.fire then if not Ship.holdFire then fireMissile() end end
if not Button.fire then Ship.holdFire = false end
actualShipMove()
end
Now we “just” have to write actualShipMove
. OK, in a fit of creativity I’ve typed this in:
function actualShipMove()
if Button.go then
local accel = vec2(0.15,0):rotate(ship.ang)
ship.velocity = ship.velocity + accel
end
ship.pos = ship.pos + ship.velocity
end
It seems to me that this ought to work. I need to initialize velocity, however, up in the create function:
function createShip()
Ship.pos = vec2(WIDTH, HEIGHT)/2
Ship.ang = 0
ship.velocity = vec2(0,0)
end
I’m going to just run this and see what happens. Hold my water bottle.
Yeah, well, it’s capital S ship not lower case all over. Duh.
It might be a good idea to think about that mistake. I actually typed lower-case ship right after two lines with the capital letter, because I was already locked in on the lower case from the actualShipMove
function. Why did I type it there?
Because lower case letters are for instances and upper case are for classes and globals, and I think of the ship as an instance. (The story is a bit more complex than that, because when it becomes an object we’ll be saying self
there, but I think what has bitten me is that I am thinking locally and the Ship is written globally. Another hint that we might be wise to convert it.)
But I digress. NOW I think the ship might fly.
Well, it does. A few things happen. First of all, it goes way too fast, so the 0.15 is too large an acceleration. Second, it doesn’t wrap around: we didn’t keep Ship.pos in bounds yet. Third, it goes off at weird angles. Left facing left, it goes left. Aimed up, it goes right. In between, it does other random things. I am confuse.
Ah. While the screen rotate
function is in degrees, the vector rotate
expects radians. But the asteroids seem not to reflect that!
function moveAsteroid(asteroid)
local step = Ratio*vec2(Vel,0):rotate(asteroid.angle)
local pos = asteroid.pos + step
asteroid.pos = vec2(keepInBounds(pos.x, WIDTH), keepInBounds(pos.y, HEIGHT))
end
What’s up with that? Well, sir, asteroids are given an angle in radians when created:
function createAsteroid()
local a = {}
a.pos = vec2(math.random(WIDTH), math.random(HEIGHT))
a.angle = math.random()*2*math.pi
a.shape = Rocks[math.random(1,4)]
a.scale = 16
return a
end
Since they don’t rotate or change direction, we didn’t stumble on this issue before.
I think the best thing to do is to convert Ship to use radians as well, which will mean we need to adjust its draw
to expect that. We need to go one way or the other and I’d rather be consistent between asteroids and the ship.
I decided to rename the variable as radians
which might serve as a clue. I’ll show you all the code in a moment but I might as well run it to see what I missed.
OK, after dialing down acceleration to 0.015 the ship moves somewhat reasonably. Let me put in keeping in bounds and then show what we’ve got.
Asteroids do it like this:
function moveAsteroid(asteroid)
local step = Ratio*vec2(Vel,0):rotate(asteroid.angle)
local pos = asteroid.pos + step
asteroid.pos = vec2(keepInBounds(pos.x, WIDTH), keepInBounds(pos.y, HEIGHT))
end
I’ll just do similarly for the ship:
function actualShipMove()
if Button.go then
local accel = vec2(0.015,0):rotate(Ship.radians)
Ship.velocity = Ship.velocity + accel
end
Ship.pos = Ship.pos + Ship.velocity
Ship.pos = vec2(keepInBounds(Ship.pos.x, WIDTH), keepInBounds(Ship.pos.y, HEIGHT))
end
And it moves as intended!
I’ve not handled maximizing the speed yet. Here’s my plan for maximizing to three. I’ll write it out longhand first:
function actualShipMove()
if Button.go then
local accel = vec2(0.015,0):rotate(Ship.radians)
Ship.velocity = Ship.velocity + accel
end
Ship.velocity = maximize(Ship.velocity, 3)
Ship.pos = Ship.pos + Ship.velocity
Ship.pos = vec2(keepInBounds(Ship.pos.x, WIDTH), keepInBounds(Ship.pos.y, HEIGHT))
end
function maximize(vec, size)
local s = vec:len()
if s <= size then
return vec
else
return vec*size/s
end
end
We get the length of the velocity vector, compare it to the desired maximum, and if it’s larger, we scale it down by the ratio between the desired size and the current size. If it were trying to go twice as fast, that ratio would be 3/6, or 1/2, and it would scale right back where it’s supposed to be.
And, perhaps more significant, it seems to work on the screen as well.
It appears to me that we can move the call to maximize up into the if statement that adjusts velocity:
function actualShipMove()
if Button.go then
local accel = vec2(0.015,0):rotate(Ship.radians)
Ship.velocity = Ship.velocity + accel
Ship.velocity = maximize(Ship.velocity, 3)
end
Ship.pos = Ship.pos + Ship.velocity
Ship.pos = vec2(keepInBounds(Ship.pos.x, WIDTH), keepInBounds(Ship.pos.y, HEIGHT))
end
The code might be made a bit faster with fewer accesses to Ship, but for now, we have things working. And my dear wife is home from foraging, so it’s probably time to break.
Summing Up
I’ll dump all the code below, so you can scan it and make your own assessment. Here’s the relevant stuff from Ship, which is the only tab we’ve edited:
function createShip()
Ship.pos = vec2(WIDTH, HEIGHT)/2
Ship.radians = 0 -- <-------
Ship.velocity = vec2(0,0)
end
function drawShip()
local sx = 10
local sy = 6
pushStyle()
pushMatrix()
translate(Ship.pos.x, Ship.pos.y)
rotate(math.deg(Ship.radians)) -- <-------
strokeWidth(2)
stroke(255)
line(sx,0, -sx,sy)
line(-sx,sy, -sx,-sy)
line(-sx,-sy, sx,0)
popMatrix()
popStyle()
end
function moveShip()
if Button.left then Ship.radians = Ship.radians + rotationStep end
if Button.right then Ship.radians = Ship.radians - rotationStep end
if Button.fire then if not Ship.holdFire then fireMissile() end end
if not Button.fire then Ship.holdFire = false end
actualShipMove() -- <-------
end
function actualShipMove()
if Button.go then
local accel = vec2(0.015,0):rotate(Ship.radians)
Ship.velocity = Ship.velocity + accel
Ship.velocity = maximize(Ship.velocity, 3)
end
Ship.pos = Ship.pos + Ship.velocity
Ship.pos = vec2(keepInBounds(Ship.pos.x, WIDTH), keepInBounds(Ship.pos.y, HEIGHT))
end
function maximize(vec, size)
local s = vec:len()
if s <= size then
return vec
else
return vec*size/s
end
end
Our changes were’t very invasive, mostly just additions to give moving a place to stand and to do the work. And it went smoothly other than needing to train myself to type “Ship” instead of “ship”, which I surely did about 61 times.
The code is not lovely, and could perhaps be optimized a bit, but it works nicely. We’ll need to tune the magic numbers, and that reminds me of what’s not to like about what we have so far.
- Magic Numbers
- I’ve mentioned before that all these magical 1.5s and 0.0015s and the like need to be centralized and made consistent. Every time we write another function with a constant in it, we are making the code harder to modify.
- Classes and Objects
- I prefer how the code looks for Splats and Missiles, which are defined in classes rather than just a bunch of functions arranged in a tab somewhere. I’d like to convert Ship to that form, and will probably make that the task for next time. Doing so should provide some insights, I suspect.
- Degrees and Radians
- Codea has done it to us, in its use of degrees in graphics and radians in vectors. We just have to do our best to be internally consistent. And I suspect that naming angles things like “radians” and “degrees” will help with that.
- Consistent Move Functions
- The asteroid motion and ship motion are a bit different in style, and I’ve mentioned that the asteroid computes its motion vector more frequently than it needs to. If we initialize asteroids a bit differently, I think we can make them look more similar to ships. That may lead to some consolidation and should at least reduce confusion
- Friction!
- I just realized, while reviewing the article before shipping it, that I didn’t handle friction. Remind me to do that.
- Ratio!!!
- And did you notice that I didn’t scale the velocity to
DeltaTime
? Another oversight. How could we build this code so that oversights like that are less likely? We’ll have to think about that.
There’s probably more than that to be discovered. We’ll keep our eyes open.
I have a more general comment. On one of the private Slack groups I belong to, a member commented that when they write “small” programs, they like to keep them all in one file, but larger programs they like to separate out.
This program was just under 500 lines when we started today and I have it broken out into 9 tabs already, so it’s broken into chunks of around 50 or 60 lines already. I believe that big programs get messy a little bit at a time, so I try to work “in the small” much as I would work “in the large”, observing the little things and fixing them.
I try to keep the program well designed, not for the future, but for today. I try never to put something in because I’ll need it tomorrow. I do things because they’re already wrong and need to be better, or because I’m about to do something that won’t fit in well, and I need to give it a place to be.
It works for me, and it’s kind of fun to do things in these tiny steps. I’m having a good time, and I hope that if you’re reading, you are as well.
See you next time!
A Defect! I shipped a defect!
Dave1707 has been following along, and he tried something that I didn’t try in my zeal to get to breakfast: he tried firing a missile. That caused the program to explode rather than a rock, because of this line in Missile:
self.vel = vec2(MissileVelocity,0):rotate(math.rad(ship.ang))
We changed the ship from using the angle to using a new variable, radians
. Well, OK, I did that. And I didn’t change the line that decides where to aim the Missile. It should be:
self.vel = vec2(MissileVelocity,0):rotate(ship.radians)
I’ve already been thinking about how to centralize the decisions about the member variables on moving objects, with my thought being that if we can get it done in just one place, we should be able to keep it more nearly right.
There’s also a case to be made for some kind of an automated test. That needs some consideration as well.
For now, apologies for the defect and thanks to Dave!
The Code
--# Main
-- Asteroids
-- RJ 20200511
Touches = {}
Ratio = 1.0 -- draw time scaling ratio
Score = 0
function setup()
U = Universe()
U:createAsteroids()
Score = 0
--displayMode(FULLSCREEN_NO_BUTTONS)
createButtons()
createShip()
end
function draw()
Ratio = DeltaTime/0.0083333
--displayMode(FULLSCREEN_NO_BUTTONS)
checkButtons()
pushStyle()
background(40, 40, 50)
U:draw()
drawButtons()
drawShip()
moveShip()
drawMissiles()
drawSplats()
drawScore()
popStyle()
U:findCollisions()
end
function drawScore()
local s= "000000"..tostring(Score)
s = string.sub(s,-5)
fontSize(100)
text(s, 200, HEIGHT-60)
end
function touched(touch)
if touch.state == ENDED or touch.state == CANCELLED then
Touches[touch.id] = nil
else
Touches[touch.id] = touch
end
end
--# TestAsteroids
-- TestAsteroids
-- RJ 20200511
function testAsteroids()
CodeaUnit.detailed = true
_:describe("Asteroids First Tests", function()
_:before(function()
-- Some setup
end)
_:after(function()
-- Some teardown
end)
_:test("Hookup", function()
_:expect( 2+1 ).is(3)
end)
_:test("Random", function()
local min = 100
local max = 0
for i = 0,1000 do
local rand = math.random()*2*math.pi
if rand < min then min = rand end
if rand > max then max = rand end
end
_:expect(min < 0.01).is(true)
_:expect(max > 6.2).is(true)
end)
_:test("Rotated Length", function()
for i = 0, 1000 do
local rand = math.random()*2*math.pi
local v = vec2(1.5,0):rotate(rand)
local d = v:len()
_:expect(d > 1.495).is(true)
_:expect(d < 1.505).is(true)
end
end)
_:test("Some rotates go down", function()
local angle = math.rad(-45)
local v = vec2(1,0):rotate(angle)
local rvx = v.x*1000//1
local rvy = v.y*1000//1
_:expect(rvx).is(707)
_:expect(rvy).is(-708)
end)
_:test("Bounds function", function()
_:expect(keepInBounds(100,1000)).is(100)
_:expect(keepInBounds(1000,1000)).is(0)
_:expect(keepInBounds(1001,1000)).is(1)
_:expect(keepInBounds(-1,1000)).is(999)
end)
end)
end
--# Shapes
RR1 = {
vec4(0.000000, 2.000000, 2.000000, 4.000000),
vec4(2.000000, 4.000000, 4.000000, 2.000000),
vec4(4.000000, 2.000000, 3.000000, 0.000000),
vec4(3.000000, 0.000000, 4.000000, -2.000000),
vec4(4.000000, -2.000000, 1.000000, -4.000000),
vec4(1.000000, -4.000000, -2.000000, -4.000000),
vec4(-2.000000, -4.000000, -4.000000, -2.000000),
vec4(-4.000000, -2.000000, -4.000000, 2.000000),
vec4(-4.000000, 2.000000, -2.000000, 4.000000),
vec4(-2.000000, 4.000000, 0.000000, 2.000000)
}
RR2 = {
vec4(2.000000, 1.000000, 4.000000, 2.000000),
vec4(4.000000, 2.000000, 2.000000, 4.000000),
vec4(2.000000, 4.000000, 0.000000, 3.000000),
vec4(0.000000, 3.000000, -2.000000, 4.000000),
vec4(-2.000000, 4.000000, -4.000000, 2.000000),
vec4(-4.000000, 2.000000, -3.000000, 0.000000),
vec4(-3.000000, 0.000000, -4.000000, -2.000000),
vec4(-4.000000, -2.000000, -2.000000, -4.000000),
vec4(-2.000000, -4.000000, -1.000000, -3.000000),
vec4(-1.000000, -3.000000, 2.000000, -4.000000),
vec4(2.000000, -4.000000, 4.000000, -1.000000),
vec4(4.000000, -1.000000, 2.000000, 1.000000)
}
RR3 = {
vec4(-2.000000, 0.000000, -4.000000, -1.000000),
vec4(-4.000000, -1.000000, -2.000000, -4.000000),
vec4(-2.000000, -4.000000, 0.000000, -1.000000),
vec4(0.000000, -1.000000, 0.000000, -4.000000),
vec4(0.000000, -4.000000, 2.000000, -4.000000),
vec4(2.000000, -4.000000, 4.000000, -1.000000),
vec4(4.000000, -1.000000, 4.000000, 1.000000),
vec4(4.000000, 1.000000, 2.000000, 4.000000),
vec4(2.000000, 4.000000, -1.000000, 4.000000),
vec4(-1.000000, 4.000000, -4.000000, 1.000000),
vec4(-4.000000, 1.000000, -2.000000, 0.000000)
}
RR4 = {
vec4(1.000000, 0.000000, 4.000000, 1.000000),
vec4(4.000000, 1.000000, 4.000000, 2.000000),
vec4(4.000000, 2.000000, 1.000000, 4.000000),
vec4(1.000000, 4.000000, -2.000000, 4.000000),
vec4(-2.000000, 4.000000, -1.000000, 2.000000),
vec4(-1.000000, 2.000000, -4.000000, 2.000000),
vec4(-4.000000, 2.000000, -4.000000, -1.000000),
vec4(-4.000000, -1.000000, -2.000000, -4.000000),
vec4(-2.000000, -4.000000, 1.000000, -3.000000),
vec4(1.000000, -3.000000, 2.000000, -4.000000),
vec4(2.000000, -4.000000, 4.000000, -2.000000),
vec4(4.000000, -2.000000, 1.000000, 0.000000)
}
Rocks = {RR1,RR2,RR3,RR4}
--# Ship
-- Ship
-- RJ 20200520
local Ship = {}
local rotationStep = math.rad(1) -- one degree in radians
function createShip()
Ship.pos = vec2(WIDTH, HEIGHT)/2
Ship.radians = 0
Ship.velocity = vec2(0,0)
end
function drawShip()
local sx = 10
local sy = 6
pushStyle()
pushMatrix()
translate(Ship.pos.x, Ship.pos.y)
rotate(math.deg(Ship.radians))
strokeWidth(2)
stroke(255)
line(sx,0, -sx,sy)
line(-sx,sy, -sx,-sy)
line(-sx,-sy, sx,0)
popMatrix()
popStyle()
end
function moveShip()
if Button.left then Ship.radians = Ship.radians + rotationStep end
if Button.right then Ship.radians = Ship.radians - rotationStep end
if Button.fire then if not Ship.holdFire then fireMissile() end end
if not Button.fire then Ship.holdFire = false end
actualShipMove()
end
function actualShipMove()
if Button.go then
local accel = vec2(0.015,0):rotate(Ship.radians)
Ship.velocity = Ship.velocity + accel
Ship.velocity = maximize(Ship.velocity, 3)
end
Ship.pos = Ship.pos + Ship.velocity
Ship.pos = vec2(keepInBounds(Ship.pos.x, WIDTH), keepInBounds(Ship.pos.y, HEIGHT))
end
function maximize(vec, size)
local s = vec:len()
if s <= size then
return vec
else
return vec*size/s
end
end
function fireMissile()
Ship.holdFire = true
Missile(Ship)
end
--# Button
-- Button
-- RJ 20200520
Button = {}
local Buttons = {}
function createButtons()
local dx=50
local dy=200
table.insert(Buttons, {x=dx, y=dy, name="left"})
table.insert(Buttons, {x=dy, y=dx, name="right"})
table.insert(Buttons, {x=WIDTH-dx, y=dy, name="fire"})
table.insert(Buttons, {x=WIDTH-dy, y=dx, name = "go"})
end
function checkButtons()
Button.left = false
Button.right = false
Button.go = false
Button.fire = false
for id,touch in pairs(Touches) do
for i,button in ipairs(Buttons) do
if touch.pos:dist(vec2(button.x,button.y)) < 50 then
Button[button.name]=true
end
end
end
end
function drawButtons()
pushStyle()
ellipseMode(RADIUS)
textMode(CENTER)
stroke(255)
strokeWidth(1)
for i,b in ipairs(Buttons) do
pushMatrix()
pushStyle()
translate(b.x,b.y)
if Button[b.name] then
fill(128,0,0)
else
fill(128,128,128,128)
end
ellipse(0,0, 50)
fill(255)
fontSize(30)
text(b.name,0,0)
popStyle()
popMatrix()
end
popStyle()
end
--# Asteroid
-- Asteroid
-- RJ 20200520
local DeadAsteroids = {}
local Vel = 1.5
function createAsteroids(asteroids)
for i = 1,4 do
local a = createAsteroid()
asteroids[a] = a
end
end
function createAsteroid()
local a = {}
a.pos = vec2(math.random(WIDTH), math.random(HEIGHT))
a.angle = math.random()*2*math.pi
a.shape = Rocks[math.random(1,4)]
a.scale = 16
return a
end
function drawAsteroids(asteroids)
pushStyle()
stroke(255)
fill(0,0,0, 0)
strokeWidth(2)
rectMode(CENTER)
for i,asteroid in pairs(asteroids) do
drawAsteroid(asteroid)
moveAsteroid(asteroid)
end
popStyle()
killDeadAsteroids(asteroids)
end
function killDist(asteroid)
local s = asteroid.scale
if s == 16 then return 64 elseif s == 8 then return 32 else return 16 end
end
function killDeadAsteroids(asteroids)
for k,a in pairs(DeadAsteroids) do
asteroids[a] = nil
end
DeadAsteroids = {}
end
function deathSize()
local i = 0
for k, a in pairs(DeadAsteroids) do
i = i + 1
end
return i
end
function scoreAsteroid(asteroid)
local s = asteroid.scale
local inc = 0
if s == 16 then inc = 20
elseif s == 8 then inc = 50
else inc = 100
end
Score = Score + inc
end
function splitAsteroid(asteroid, asteroids)
if asteroid.scale == 4 then
Splat(asteroid.pos)
DeadAsteroids[asteroid] = asteroid
return
end
asteroid.scale = asteroid.scale//2
asteroid.angle = math.random()*2*math.pi
local new = createAsteroid()
new.pos = asteroid.pos
new.scale = asteroid.scale
asteroids[new] = new
Splat(asteroid.pos)
end
function drawAsteroid(asteroid)
pushMatrix()
pushStyle()
translate(asteroid.pos.x, asteroid.pos.y)
ellipse(0,0,2*killDist(asteroid))
scale(asteroid.scale)
strokeWidth(1/asteroid.scale)
for i,l in ipairs(asteroid.shape) do
line(l.x, l.y, l.z, l.w)
end
popStyle()
popMatrix()
end
function moveAsteroid(asteroid)
local step = Ratio*vec2(Vel,0):rotate(asteroid.angle)
local pos = asteroid.pos + step
asteroid.pos = vec2(keepInBounds(pos.x, WIDTH), keepInBounds(pos.y, HEIGHT))
end
function keepInBounds(value, bound)
return (value+bound)%bound
end
--# Splat
-- Splat
-- RJ 20200521
local Splats = {}
local Vecs = {
vec2(-2,0), vec2(-2,-2), vec2(2,-2), vec2(3,1), vec2(2,-1), vec2(0,2), vec2(1,3), vec2(-1,3), vec2(-4,-1), vec2(-3,1)
}
function drawSplats()
for k, splat in pairs(Splats) do
splat:draw()
end
end
Splat = class()
function Splat:init(pos)
local die = function()
Splats[self] = nil
end
self.pos = pos
Splats[self] = self
self.size = 2
self.diameter = 6
self.rot = math.random(0,359)
tween(4, self, {size=10, diameter=1}, tween.easing.linear, die)
end
function Splat:draw()
pushStyle()
pushMatrix()
translate(self.pos.x, self.pos.y)
fill(255)
stroke(255)
rotate(self.rot)
local s = self.size
for i,v in ipairs(Vecs) do
ellipse(s*v.x, s*v.y, self.diameter)
end
popMatrix()
popStyle()
end
--# Missile
-- Missile
-- RJ 20200522
Missiles = {}
function drawMissiles()
pushStyle()
pushMatrix()
fill(255)
stroke(255)
for k, missile in pairs(Missiles) do
missile:draw()
end
popMatrix()
popStyle()
for k, missile in pairs(Missiles) do
missile:move()
end
end
Missile = class()
local MissileVelocity = 2.0
function Missile:init(ship)
function die()
self:die()
end
self.pos = ship.pos
self.vel = vec2(MissileVelocity,0):rotate(ship.radians)
Missiles[self] = self
tween(3, self, {}, tween.easing.linear, die)
end
function Missile:die()
Missiles[self] = nil
end
function Missile:draw()
ellipse(self.pos.x, self.pos.y, 6)
end
function Missile:move()
self.pos = self.pos + Ratio*self.vel
self.pos = vec2(keepInBounds(self.pos.x, WIDTH), keepInBounds(self.pos.y, HEIGHT))
end
--# Universe
-- Universe
-- RJ 20200523
Universe = class()
function Universe:init()
self.asteroids = {}
end
function Universe:draw()
drawAsteroids(self.asteroids)
end
function Universe:createAsteroids()
createAsteroids(self.asteroids)
end
function Universe:findCollisions()
for i,a in pairs(self.asteroids) do
for k,m in pairs(Missiles) do
if m.pos:dist(a.pos) < killDist(a) then
scoreAsteroid(a)
splitAsteroid(a, self.asteroids)
m:die()
end
end
end
end