Space Invaders 22--Assets and Things
I’ve moved the good iPad into the TV room and while waiting for our Sunday festivities to start, I’ll set up the project’s bitmap assets.
I really didn’t remember how to move the bitmaps from where they were created to the current project assets. Fortunately, I had written that up when I did the sound assets for Asteroids, so I copied the process. I’ll describe it here as well.
The bitmaps were created by the program MakeInvaders. I’ll put a copy of that in an appendix.
Moving them into our project turns out to be easy. I pasted in a temporary access to a sprite:
function setup()
local bits = sprite(xxx,0,0)
runTests()
createShields()
TheArmy = Army()
setupGunner()
Missile = {v=0, p=vec2(0,0)}
invaderNumber = 1
end
When you touch the xxx, the Codea asset browser pops up:
From that you can tap any of the sources. The one called Dropbox is a shared folder among all the Codea programs, and it’s the one where MakeInvaders has put the ones we want. We tap Dropbox and:
And there they all are. (That gray dot is the touchpad cursor. It shows up randomly when I accidentally touch the touchpad. You can safely ignore it.)
Now one taps the Edit up at the top. Edit changes to Done and we can begin selecting:
And we can scroll down until we’ve selected everything we might want:
Press the “AddTo” down at the bottom and you get this:
Select the Invaders project assets, and they’ll all be copied in. Thereafter, when you touch anything that wants an asset, you can use the built in ones:
And there they all are:
Far from obvious, but quite easy.
Just for fun, I’ll see if I can make it display the gunner asset, which is named “player” if I’m not mistaken. Here’s our draw now:
function drawGunner()
pushMatrix()
pushStyle()
if Gunner.alive then
stroke(255)
fill(255)
else
stroke(255,0,0)
fill(255,0,0)
Gunner.count = Gunner.count - 1
if Gunner.count <= 0 then Gunner.alive = true end
end
rect(Gunner.pos.x, Gunner.pos.y, 16,8)
stroke(255,0,0)
fill(255,0,0)
popStyle()
popMatrix()
Gunner.pos = Gunner.pos + GunMove
end
We just need to use the sprite, I reckon. It turns out that the name is “play”, not “player” for some reason. I replace the call to rect
with this:
sprite(asset.play,Gunner.pos.x,Gunner.pos.y)
And Lo! the sprite appears:
It doesn’t change color when hit, however, because that’s not done with stroke and fill, but with tint
, if I’m not mistaken.
function drawGunner()
pushMatrix()
pushStyle()
if Gunner.alive then
tint(255)
else
tint(255,0,0)
Gunner.count = Gunner.count - 1
if Gunner.count <= 0 then Gunner.alive = true end
end
sprite(asset.play,Gunner.pos.x,Gunner.pos.y)
stroke(255,0,0)
fill(255,0,0)
popStyle()
popMatrix()
Gunner.pos = Gunner.pos + GunMove
end
And now the sprite turns red when hit. I didn’t have to wait too long to catch this snapshot:
It’s time for Sunday brekkers, so I’ll stop here and commit (55 files, by the way, counting two sizes for the bitmaps).
Monday, Monday
Well, here we are on Monday at 0723, and probably the thing to do is to add in the rest of the bitmaps.
It seems to me that the shields should be easy, because we are already making copies:
function createShields()
local img = image(22,16)
for row = 1,16 do
for col = 1,22 do
img:set(col,row,0,255,0)
end
end
local posX = 34+11
local posY = 100
Shields = {}
for s = 1,4 do
local entry = {img=img:copy(), pos=vec2(posX,posY), count=0, alive=true}
table.insert(Shields,entry)
posX = posX + 22 + 23
end
end
I’m slightly irritated that this code makes four copies rather than using the original plus three copies, but I don’t know a really super way to deal with that. Hmm, I’ve just thought of one, but we’ll deal with that later. For now, let’s just replace that procedural bitmap with our official one.
I’m not exactly sure how to do that. My guess is that I should assign an asset to the image.
This attempt fails:
function createShields()
local img = asset.shield
local posX = 34+11
local posY = 100
Shields = {}
for s = 1,4 do
local entry = {img=img:copy(), pos=vec2(posX,posY), count=0, alive=true}
table.insert(Shields,entry)
posX = posX + 22 + 23
end
end
The message is:
Main:52: attempt to call a nil value (method 'copy')
stack traceback:
Main:52: in function 'createShields'
Main:6: in function 'setup'
So whatever came back from that assignment must not be a legitimate image. Time to RTFM. (Read the Fine Manual, of course.) Ah. We want ‘readImage`:
function createShields()
local img = readImage(asset.shield)
local posX = 34+11
local posY = 100
Shields = {}
for s = 1,4 do
local entry = {img=img:copy(), pos=vec2(posX,posY), count=0, alive=true}
table.insert(Shields,entry)
posX = posX + 22 + 23
end
end
That does the trick, and now we have individual shields:
While we’re at it, let’s move those guys down until they’re right above the player, then we’ll commit. We’ll set posY
to just a bit above the player, which is at Y = 10. He’s 16 high, so his top is at 18, so let’s try 24.
That height looks about right, and since the right edge of one of our guys is right on the center, we should probably find that removing that +11
will center them nicely:
function createShields()
local img = readImage(asset.shield)
local posX = 34
local posY = 24
Shields = {}
for s = 1,4 do
local entry = {img=img:copy(), pos=vec2(posX,posY), count=0, alive=true}
table.insert(Shields,entry)
posX = posX + 22 + 23
end
end
And that looks good:
Let’s commit: shields use real asset.
Digression? The Copy Thing
I want to reflect a moment on the fact that we read the shield asset and then make four copies of it, wasting one since we only need four. That’s certainly not as efficient as making only three copies. Why didn’t I “fix” that? There are a few reasons.
First, since the inefficiency is during setup, it’s not terribly important. And since the extra is created inside a function, it’ll get garbage collected if the storage is ever needed. Overall, it’s not a big issue.
Second, I don’t know a simple and easy pattern for doing it. There’ll be some kind of if statement inside the loop, and it seems to me that it’ll be messy.
Third, I was on another mission, to use the stored image, and diverting to fix this not very important thing would have detracted from what I was doing, and quite possibly confused me. Now, since I was really just replacing a few lines of code with one, the chances of confusion were pretty low–but remember who you’re dealing with here–but the principle of not going off plan is one that I try to cultivate.
Why not fix it now, though, since we’ve completed the task? That’s a fair question, since we’re supposed to clean up our code when we make a thing work, so this is the right moment to do it.
However: the code is clean. It just isn’t quite as efficient as it might be. “Make it fast” is the third step in the mantra “Make it work, make it right, make it fast.” I was admonished by Kent Beck Lo! these many years ago, to optimize code only when a performance measurement showed that the optimization was needed. I took that advice to heart after I had spent a day optimizing something and it had no actual effect on the system’s run time. OK, maybe that happened a few times.
So, no. This is clean, and it’s fast enough.
Let’s move on.
Invaders? or Missiles?
We have at least the invader sprites, and the missile sprites left to do. I think the invaders offer more “business value”, because they improve the game a lot. There’s nothing all that deeply menacing about a few rows of encroaching evil squares.
We did a spike for this, and I’m going to have a look at it now.
Some of the code isn’t bad, but I’ve decided not to include it here. Instead, I’ll put it down in another appendix and use it for reference to write what we need in our current structure.
In our current scheme we have a class Army
that contains all the instance of the class Invader
. The init
looks like this:
function Army:init()
self.invaders = {}
for row = 1,5 do
for col = 1,11 do
local p = vec2(col*16, 256-row*16)
table.insert(self.invaders, 1, Invader(p))
end
end
self.invaderNumber = 1
self.overTheEdge = false
self.motion = vec2(2,0)
self.updateDelay = 1/65 -- less than 1/60th by a bit
self:resetTimeToUpdate()
self.bombs = {}
end
A decent pattern seems to me to be to fetch the sprite images here, and to give each invader the info she needs to draw herself. In the spike, I gave each one just her two basic images, based on the row she’s in. So let’s try that … this will read them in:
function Army:init()
local vader11 = readImage(asset.documents.Dropbox.inv11)
local vader12 = readImage(asset.documents.Dropbox.inv12)
local vader21 = readImage(asset.documents.Dropbox.inv21)
local vader22 = readImage(asset.documents.Dropbox.inv22)
local vader31 = readImage(asset.documents.Dropbox.inv31)
local vader32 = readImage(asset.documents.Dropbox.inv32)
self.invaders = {}
self.invaders = {}
for row = 1,5 do
for col = 1,11 do
local p = vec2(col*16, 256-row*16)
table.insert(self.invaders, 1, Invader(p))
end
end
...
The game layout is that the first two rows get inv11 and inv12, the next two get inv21 and inv22, and the third gets inv31 and inv32.
So what if we just made a little table of pairs …
local vaders = {
{vader11,vader12}, {vader11,vader12},
{vader21,vader22}, {vader21,vader22},
{vader31,vader32}}
}
And we give each invader her pair:
self.invaders = {}
for row = 1,5 do
for col = 1,11 do
local p = vec2(col*16, 256-row*16)
table.insert(self.invaders, 1, Invader(p,vaders(row)))
end
end
Now each invader has a new parameter, her pair of sprites for her init:
function Invader:init(pos)
self.pos = pos
self.alive = true
end
So she’ll save those …
function Invader:init(pos, sprites)
self.sprites = sprites
self.pos = pos
self.alive = true
end
And to close this out for a commit, we’ll have her just draw herself as the first one:
function Invader:draw()
if self.alive then
sprite(sprites[1])
end
end
Well. I expected that to work. But I get this message:
Army:19: attempt to call a table value (local 'vaders')
stack traceback:
Army:19: in field 'init'
... false
end
setmetatable(c, mt)
return c
end:24: in global 'Army'
Main:7: in function 'setup'
Sounds like a parenthesis problem. Here’s that code:
table.insert(self.invaders, 1, Invader(p,vaders(row)))
Yes, we meant this:
table.insert(self.invaders, 1, Invader(p,vaders[row]))
We get another error, and fix this typo, a missing self
:
function Invader:draw()
if self.alive then
sprite(self.sprites[1])
end
end
Nothing appears now, and I suspect it’s because a sprite has to have a location.
function Invader:draw()
if self.alive then
sprite(self.sprites[1], self.pos.x, self.pos.y)
end
end
And we have invaders!
They’re in the wrong rows, because we build the rows top down, not bottom up. Reorder the table:
local vaders = {
{vader31,vader32},
{vader21,vader22}, {vader21,vader22},
{vader11,vader12}, {vader11,vader12},
}
And we’re good:
Commit: Invaders have bitmaps, no animation.
Did you notice the trailing comma in the vaders
table, after the last entry. Turns out that Lua doesn’t mind that being there, and languages that allow that are among my favorites. You might argue that allowing it invites leaving elements out by accident, but it’s my computer and I like it.
That said, full disclosure, I’m going to remove it, since it’s not needed and we’ve noticed it now.
I want to press on to animation but first I think I’ll go get a chai. Back in a half hour: 0830 … 0900. Perfect estimate.
Animation
Our little invaders have two images, indexed 1 and 2, because Lua, and we want them to switch between them on each update, if they’re alive:
function Invader:update(motion, army)
if self.alive then
self:possiblyDropBomb(army)
self.pos = self.pos + motion
if self:overEdge() then
army:invaderOverEdge(self)
end
return false
else
return true
end
end
So we need a new member variable that will let us do that. Because it’s easy to do one that varies between 0 and 1, we’ll do this:
function Invader:init(pos, sprites)
self.sprites = sprites
self.pos = pos
self.alive = true
self.picture = 0 -- <---
end
function Invader:draw()
if self.alive then
sprite(self.sprites[self.picture+1], self.pos.x, self.pos.y) -- <---
end
end
function Invader:update(motion, army)
if self.alive then
self.picture = (self.picture+1)%2 -- <---
self:possiblyDropBomb(army)
self.pos = self.pos + motion
if self:overEdge() then
army:invaderOverEdge(self)
end
return false
else
return true
end
end
At first, I initialized the picture to 1, thinking she’d update before drawing, and I wanted to get her off on the right foot, but she draws and then updates, so I changed the init to zero. In practice it makes no discernible difference.
The invaders are now animated:
We can see quite a few issues in that movie. Missiles fired from the player take off from the far left, because we’ve changed origins to CORNER
. Missiles don’t hit aliens when they’re hit dead on, but do hit them when the missile goes left. Same issue. Same with bombs hitting the player. We’ll deal with that in due time.
For now, commit: animated aliens.
Story done, let’s reflect:
Reflection
When Chet and I wrote about our “new approach”, which I fondly hope we never really try to do, we named it Twelve™, because XP had the dials turned to eleven and we wanted to go further.
In Twelve™, e think of the work as being done in what we called “Brackets”, nested time intervals with a beginning and an end. If you think about it for a moment, such things generally have the closing and opening brackets together:
…][ 1 ][ 2 ][ 3 ][ …
At each boundary, we look back to assess what we’ve done, and then we look forward to see what we’ll do next.
I really hope we never try to push Twelve™, as the world doesn’t really need yet another approach, but it’s an interesting way to think about things. In particular, at every work break, it’s useful to look back and look forward, to kind of lock in any discoveries or learning, and to see what has been accomplished. We might even want to tell someone about it.
So, reflecting …
The sprites for the player, the shields, and the invaders have all gone in quite readily. The invader animation was just a few lines of code, which is what we’d hope for. The update function provided the exact moment for doing the selection.
It’s worth noting that this went easily, because the ease or difficulty of doing things tells us something important about code quality and design. When our quality is high enough, and our design is close to what we need, things tend to go smoothly. When the code is not clean enough, or the design is not close enough, things go slowly.
If we pay attention, we can feel the difference between smooth and slow right away. If we do something too quick and dirty today, we can probably feel the impact tomorrow. It will be much longer before those effects show up on a projection of what will be done by a given date, but as developers we can feel the drag right away.
Today, I didn’t feel much drag. Things went in nicely.
We should give some credit to the spikes we did earlier as well. Those spikes gave us concrete experience with reading images, displaying them, and toggling between them, and that experience, plus a glance at the code we used, made today’s work fee more familiar, not so much like doing something in that horrid fumbling first-time fashion.
A good morning’s work. We’re two hours in, even though it’s only 0927, so perhaps we’ll wrap this one after thinking about what’s next.
What’s Next?
Well. Lots to choose from:
- Aiming points are off by half an object;
- Missiles need to be animated;
- Aliens explode when shot;
- Shields get gnawed away by bombs;
- Shields get eaten by aliens if they get there;
- Aliens still fall off the edges of the screen;
- Aliens advance too slowly;
- The game is supposed to have sound;
- There’s a saucer;
- There’s supposed to be scoring;
- There can be two players;
- … and surely a lot more …
Right now, I think I might work on the dimensioning, getting aliens to turn back at the right point, and to move downward more expeditiously. Or maybe just faster. Shield destruction will be fun but I think I understand what needs to be done.
I am not committing to building everything on the list. The idea here is to learn from building, and to have fun doing so. When it gets to be too little fun, or too little learning, I’ll turn my attention to something else.
One *Very Difficult Thing(, which I almost certainly won’t do, would be to use Codea’s ability to produce XCode, and to build the application to run as a native application. I’m sure I’d learn a lot, but I’m also sure it won’t be fun. I might undertake it if there was someone knowledgeable who wanted to pair with me on it.
Anyway, for now, we have some animated aliens, and all the major sprites moved in, so I’ll call it a morning.
See you next time!
Appendix: MakeInvaders
This is the program that takes the bitmaps, lightly modified from the original Space Invaders source, and puts them into Dropbox.assets
, which is a common filing area having nothing at all to do with the Dropbox program you may know and love.
The commented ones are bitmaps I chose not to bring in, part of the attract mode, which I am not planning to replicate.
The save
function puts each bitmap into the Codea Dropbox thingie:
function save(name, map)
saveImage(asset.documents.Dropbox .. name, map)
end
When it runs, the program draws a scale 5 picture of all the bitmaps:
displayMode(STANDARD)
function setup()
parameter.integer("sc",1,15,5)
--[[
alien_upside_down_Y_1
={0x00,0x03,0x04,0x78,0x14,0x13,0x08,0x1A,0x3D,0x68,0xFC,0xFC,0x68,0x3D,0x1A,0x00}
alien_upside_down_Y_2
={0x00,0x00,0x01,0xB8,0x98,0xA0,0x1B,0x10,0xFF,0x00,0xA0,0x1B,0x00,0x00,0x00,0x00}
Splash_shooting_C
={0x00,0x10,0x00,0x0E,0x05,0x00,0x00,0x00,0x00,0x00,0x07,0xD0,0x1C,0xC8,0x9B,0x03}
Alien_Y2
={0x00,0x00,0x03,0x04,0x78,0x14,0x0B,0x19,0x3A,0x6D,0xFA,0xFA,0x6D,0x3A,0x19,0x00}
]]--
player_shot
={0x0F}
map_player_shot = image(1,8)
bitMap(player_shot, map_player_shot)
save("player_shot", map_player_shot)
player_shot_exploding
={0x99,0x3C,0x7E,0x3D,0xBC,0x3E,0x7C,0x99}
makeBitmap(player_shot_exploding, 8,8, "player_shot_exploding")
Alien_Exploding
={0x00,0x08,0x49,0x22,0x14,0x81,0x42,0x00,0x42,0x81,0x14,0x22,0x49,0x08,0x00,0x00}
makeBitmap(Alien_Exploding, 16,8, "alien_exploding")
squig1
={0x44,0xAA,0x10}
makeBitmap(squig1, 3,8, "squig1")
squig2
={0x88,0x54,0x22}
makeBitmap(squig2, 3,8, "squig2")
squig3
={0x10,0xAA,0x44}
makeBitmap(squig3, 3,8, "squig3")
squig4
={0x22,0x54,0x88}
makeBitmap(squig4, 3,8, "squig4")
alien_shot_exploding
={0x4A,0x15,0xBE,0x3F,0x5E,0x25}
makeBitmap(alien_shot_exploding, 6,8, "alien_shot_exploding")
plunger_shot_1
={0x04,0xFC,0x04}
makeBitmap(plunger_shot_1, 3,8, "plunger1")
plunger_shot_2
={0x10,0xFC,0x10}
makeBitmap(plunger_shot_2, 3,8, "plunger2")
plunger_shot_3
={0x20,0xFC,0x20}
makeBitmap(plunger_shot_3, 3,8, "plunger3")
plunger_shot_4
={0x80,0xFC,0x80}
makeBitmap(plunger_shot_4, 3,8, "plunger4")
rolling_shot_1
={0x00,0xFE,0x00}
makeBitmap(rolling_shot_1, 3,8, "rolling1")
rolling_shot_2
={0x24,0xFE,0x12}
makeBitmap(rolling_shot_2, 3,8, "rolling2")
rolling_shot_3
={0x00,0xFE,0x00}
makeBitmap(rolling_shot_3, 3,8, "rolling3")
rolling_shot_4
={0x48,0xFE,0x90}
makeBitmap(rolling_shot_4, 3,8, "rolling4")
shield
= {0xFF,0x0F,0xFF,0x1F,0xFF,0x3F,0xFF,0x7F,
0xFF,0xFF,0xFC,0xFF,0xF8,0xFF,0xF0,0xFF,
0xF0,0xFF,0xF0,0xFF,0xF0,0xFF,0xF0,0xFF,
0xF0,0xFF,0xF0,0xFF,0xF8,0xFF,0xFC,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0x7F,0xFF,0x3F,
0xFF,0x1F,0xFF,0x0F}
makeBitmap2(shield, 22,16, "shield")
Saucer
={0x00,0x00,0x00,0x00,0x04,0x0C,0x1E,0x37,0x3E,0x7C,0x74,0x7E,0x7E,0x74,0x7C,0x3E,0x37,0x1E,0x0C,0x04,0x00,0x00,0x00,0x00}
makeBitmap(Saucer, 24,8, "saucer")
saucer_exploding
={0x00,0x22,0x00,0xA5,0x40,0x08,0x98,0x3D,0xB6,0x3C,0x36,0x1D,0x10,0x48,0x62,0xB6,0x1D,0x98,0x08,0x42,0x90,0x08,0x00,0x00}
makeBitmap(saucer_exploding, 24,8, "saucer_exploding")
--[[
alien_with_Y
={0x60,0x10,0x0F,0x10,0x60,0x30,0x18,0x1A,0x3D,0x68,0xFC,0xFC,0x68,0x3D,0x1A,0x00}
makeBitmap(alien_with_Y, 16,8, "alien_y")
]]--
end
function bitMap(hexString,img)
for a=1,#hexString do
for z=0,7 do
if (hexString[a]>>z)&1==1 then
img:set(a,z+1,255,255,255,255)
end
end
end
end
function bitMap2Row(hexString,img)
local col = 0
for str = 1, #hexString, 2 do
col = col + 1
b1 = hexString[str] -- bottom
b2 = hexString[str+1] -- top
for z = 0,7 do
if (b1>>z)&1==1 then -- set bottom
img:set(col,z+1, 255,255,255,255)
end
if (b2>>z)&1==1 then -- set top
img:set(col,z+9,255,255,255,255)
end
end
end
end
function draw()
background(0)
noSmooth()
fontSize(30)
fill(255)
text("Scale "..sc,WIDTH/2,HEIGHT-100)
local y
scale(sc,sc)
sprite(asset.documents.Dropbox.player_shot,WIDTH/2/sc,HEIGHT*.8/sc)
sprite(asset.documents.Dropbox.player_shot_exploding,WIDTH/2/sc + 3*sc,HEIGHT*.8/sc)
sprite(asset.documents.Dropbox.alien_exploding,WIDTH/2/sc,HEIGHT*.7/sc)
sprite(asset.documents.Dropbox.alien_shot_exploding,WIDTH/2/sc + 3*sc,HEIGHT*.7/sc)
y = 0.6
sprite(asset.documents.Dropbox.plunger1,WIDTH/2/sc,HEIGHT*y/sc)
sprite(asset.documents.Dropbox.plunger2,WIDTH/2/sc + 3*sc,HEIGHT*y/sc)
sprite(asset.documents.Dropbox.plunger3,WIDTH/2/sc + 6*sc,HEIGHT*y/sc)
sprite(asset.documents.Dropbox.plunger4,WIDTH/2/sc + 9*sc,HEIGHT*y/sc)
y = 0.5
sprite(asset.documents.Dropbox.rolling1,WIDTH/2/sc,HEIGHT*y/sc)
sprite(asset.documents.Dropbox.rolling2,WIDTH/2/sc + 3*sc,HEIGHT*y/sc)
sprite(asset.documents.Dropbox.rolling3,WIDTH/2/sc + 6*sc,HEIGHT*y/sc)
sprite(asset.documents.Dropbox.rolling4,WIDTH/2/sc + 9*sc,HEIGHT*y/sc)
y = 0.4
sprite(asset.documents.Dropbox.squig1,WIDTH/2/sc,HEIGHT*y/sc)
sprite(asset.documents.Dropbox.squig2,WIDTH/2/sc + 3*sc,HEIGHT*y/sc)
sprite(asset.documents.Dropbox.squig3,WIDTH/2/sc + 6*sc,HEIGHT*y/sc)
sprite(asset.documents.Dropbox.squig4,WIDTH/2/sc + 9*sc,HEIGHT*y/sc)
y = 0.3
sprite(asset.documents.Dropbox.saucer,WIDTH/2/sc,HEIGHT*y/sc)
sprite(asset.documents.Dropbox.saucer_exploding,WIDTH/2/sc + 6*sc,HEIGHT*y/sc)
y = 0.1
sprite(asset.documents.Dropbox.shield,WIDTH/2/sc,HEIGHT*y/sc)
end
function save(name, map)
saveImage(asset.documents.Dropbox .. name, map)
end
function makeBitmap(bits, cols, rows, name)
local img = image(cols,rows)
bitMap(bits, img)
save(name, img)
end
function makeBitmap2(bits, cols, rows, name)
local img = image(cols,rows)
bitMap2Row(bits, img)
save(name, img)
end
Invaders Spike
-- invadersSpike
local MyRank
function setup()
local vader11 = readImage(asset.documents.Dropbox.inv11)
local vader12 = readImage(asset.documents.Dropbox.inv12)
local vader21 = readImage(asset.documents.Dropbox.inv21)
local vader22 = readImage(asset.documents.Dropbox.inv22)
local vader31 = readImage(asset.documents.Dropbox.inv31)
local vader32 = readImage(asset.documents.Dropbox.inv32)
local vaders = {vader11,vader12, vader21, vader22, vader31,vader32}
MyRank = Rank(vaders)
end
function draw()
MyRank:update(ElapsedTime)
background(40, 40, 50)
rectMode(CENTER)
noFill()
stroke(255)
strokeWidth(2)
rect(WIDTH/2, HEIGHT/2, 224*4, 256*4)
spriteMode(CORNER)
text(DeltaTime, 100,100)
translate(WIDTH/2-224*2, HEIGHT/2)
MyRank:draw()
end
Rank = class()
local Ranks;
function Rank:init(images)
self.invaderNumber = 1
self.updateStep = vec2(2,0)
self.invaders = {}
self.undone = {}
self.reverse = false
self:initInvaders(images)
self.lastElapsedTime = ElapsedTime
self.timeToUpdate = 1/60
self.imageToggle = 1
end
local invaderOffset = {1,1, 3,3, 5}
function Rank:initInvaders(images)
for rank = 0,4 do
for col = 0,10 do
local imageBase = invaderOffset[1 + rank]
local herImages = {images[imageBase], images[imageBase+1]}
table.insert(self.invaders, {p=vec2(col*16,rank*16), images=herImages})
end
end
end
function Rank:draw()
pushMatrix()
pushStyle()
scale(4)
rankEntry = self
for i,invader in ipairs(rankEntry.invaders) do
local pos = invader.p
local v = invader.images[self.imageToggle]
sprite(v, pos.x, pos.y)
end
popStyle()
popMatrix()
end
function Rank:update(elapsed)
if elapsed < self.lastElapsedTime + self.timeToUpdate then return end
if #self.undone ~= 0 then
local invader = self.undone[1]
table.remove(self.undone, 1) -- costly
invader.p =invader.p + self.updateStep
else -- all moved
self.imageToggle = self.imageToggle == 2 and 1 or self.imageToggle + 1
self:copyToUndone()
self.updateStep.y = 0
if self.updateStep.x > 0 and self.invaders[#self.invaders].p.x > 224 - 16 then
self.reverse = true
elseif self.updateStep.x < 0 and self.invaders[1].p.x < 0 then
self.reverse = true
end
if self.reverse then
self.updateStep = - self.updateStep - vec2(0,2)
self.reverse = false
end
end
self.lastElapsedTime = elapsed
end
function Rank:copyToUndone()
self.undone = table.pack(table.unpack(self.invaders))
end