Dungeon 47
Monsters. We need more monsters. Bigger, badder monsters.
The good news is that I’ve downloaded some free and purchased sprite files. The bad news is that they’re on iCloud Drive, and I need to work out how to get them into Codea. I’ve done this before, and I imagine I can look up how I did it. How hard could it be?
I first tried just copying the folder into Codea’s “Dropbox.assets” folder. After telling Codea to sync that folder, since it caches its contents upon first read, the folder was not visible. Codea only shows sprites when looking for sprites.
So I had to move all the sprites to the top level of “Dropbox.assets”. I should really move them into the project’s own assets, so that all the things I use will appear in the zip file. So far, no one appears to be using the zip file, so this doesn’t seem like an urgent issue. Still, it’ll be better for me as well, so here goes:
Moving sprite assets into your project
It turns out you can move things fairly easily, if you only know how.
When you touch a sprite
call in your source code, you get a file list, and can navigate to Dropbox.assets:
Tapping “Edit” at top right give you this:
Where you can select items you want to move. Then you tap “Add To” near bottom right, and you get this:
And if you select your project, D2 in my case, the files will be moved there.
So that’s quite easy: you just have to know the trick.
Now we can have more monsters. Let’s see how we create them now. I’m sure it’s pretty rudimentary:
function GameRunner:createLevel(count)
self:createRandomRooms(count)
self:connectRooms()
self:convertEdgesToWalls()
local r1 = self.rooms[1]
local rcx,rcy = r1:center()
local tile = self:getTile(vec2(rcx,rcy))
self.player = Player(tile,self)
-- here it is:
self.monsters = self:createThings(Monster,5) -- < ---
for i,monster in ipairs(self.monsters) do
monster:startAllTimers()
end
self.keys = self:createThings(Key,10)
self:createThings(Chest,10)
self.buttons = {}
table.insert(self.buttons, Button("left",100,200, 64,64, asset.builtin.UI.Blue_Slider_Left))
table.insert(self.buttons, Button("up",200,250, 64,64, asset.builtin.UI.Blue_Slider_Up))
table.insert(self.buttons, Button("right",300,200, 64,64, asset.builtin.UI.Blue_Slider_Right))
table.insert(self.buttons, Button("down",200,150, 64,64, asset.builtin.UI.Blue_Slider_Down))
end
So that just creates 5 monsters, using this code:
function GameRunner:createThings(aClass, n)
local things = {}
for i = 1,n or 1 do
local tile = self:randomRoomTile()
table.insert(things, aClass(tile,self))
end
return things
end
That code just creates instances of whatever class is involved, using the assumed default creation method. Since we’re just calling this with Monster
, and since that’s the only kind we have, we’re calling this:
function Monster:init(tile, runner)
self.alive = true
self.tile = tile
self.tile:addContents(self)
self.runner = runner
self.standing = asset.builtin.Platformer_Art.Monster_Standing
self.moving = asset.builtin.Platformer_Art.Monster_Moving
self.dead = asset.builtin.Platformer_Art.Monster_Squished
self.sprite1 = self.standing
self.sprite2 = self.dead
self.swap = 0
self.move = 0
self.hitPoints = 2
end
We might as well use the local space for these, but I happen to know that there is an equivalent sprite in the pack I just loaded:
What I do for browsing in sprites is insert a line sprite(xxx)
and touch it, so that I can see the display of sprites. The asset
syntax will prompt for names, but if you don’t know what you want, it’s not much help.
The pink slime is the one I’ve been using, named differently in the platformer assets that come with Codea. Let’s first convert to this, then deal with generalizing.
function Monster:init(tile, runner)
self.alive = true
self.tile = tile
self.tile:addContents(self)
self.runner = runner
--sprite(asset.slime)
self.standing = asset.slime
self.moving = asset.slime_walk
self.dead = asset.slime_squashed
self.sprite1 = self.standing
self.sprite2 = self.dead
self.swap = 0
self.move = 0
self.hitPoints = 2
end
That works fine, no surprise there. Now let’s think about how we want to do this.
Most of the monsters in this kit have four or five sprite images. For example, the Bat has: Bat, Bat_Dead, Bat_Fly, Bat_Hang, Bat_Hit. Most of them have four: Bee, Bee_Fly, Bee_Dead, Bee_Hit. The “Hit” sprites are plain white. I suppose those are intended either to flash when you’ve hit the monster, or perhaps when it hits at you. We could tint that one two different colors if need be.
I think in general there will be a different number of states to draw when the monster is in motion. I’ve chosen to cycle the slime between its main view and the squished view, but we could imagine cycling between slime and walk, or slime and walk and dead.
I’m starting to think that we need a table of monster information, and that we’ll somehow select which kind of monster we want and look it up in the table. This will probably mean that I will have to either modify the addThings
method or create a new one for monsters.
Looking forward, we’ll surely want different levels of the dungeon populated with different monsters1.
For now, let’s imagine a table for a monster that looks like this:
function Monster:init(tile, runner)
self.alive = true
self.tile = tile
self.tile:addContents(self)
self.runner = runner
--sprite(asset.slime)
self.standing = asset.slime
self.moving = asset.slime_walk
self.dead = asset.slime_squashed
self.sprite1 = self.standing
self.sprite2 = self.dead
self.swap = 0
self.move = 0
self.hitPoints = 2
if not MT then self:initMonsterTable() end
end
function Monster:initMonsterTable()
local m
m = {dead=asset.slime_squashed, hit=asset.slime_hit, moving={asset.slime, asset.slime_walk, asset.slime_squashed}}
table.insert(MT,m)
m = {dead=asset.fly_dead, hit=asset.fly_hit, moving={asset.fly, asset.fly_fly}}
table.insert(MT,m)
end
Well, I think I’m getting ahead of myself here. Anyway let’s press forward. I’ll need to change the sprite1
sprite2
stuff to loop over a table of a few entries. And let’s make it be that the init method takes a table (one of these MT elements, and if it isn’t given one, picks one at random.
I’ll show the code in a moment but it actually works. I’ll try to get a movie of the fly flying:
So that’s nearly miraculous, isn’t it? The current code is this:
local MT = nil
Monster = class()
function Monster:init(tile, runner, mtEntry)
self.mtEntry = mtEntry or self:randomMtEntry()
self.alive = true
self.tile = tile
self.tile:addContents(self)
self.runner = runner
--sprite(asset.slime)
self.dead = self.mtEntry.dead
self.hit = self.mtEntry.hit
self.moving = self.mtEntry.moving
self.movingIndex = 1
self.swap = 0
self.move = 0
self.hitPoints = 2
if not MT then self:initMonsterTable() end
end
function Monster:randomMtEntry()
if not MT then self:initMonsterTable() end
local index = math.random(1,#MT)
return MT[index]
end
function Monster:initMonsterTable()
local m
MT = {}
m = {dead=asset.slime_squashed, hit=asset.slime_hit, moving={asset.slime, asset.slime_walk, asset.slime_squashed}}
table.insert(MT,m)
m = {dead=asset.fly_dead, hit=asset.fly_hit, moving={asset.fly, asset.fly_fly}}
table.insert(MT,m)
end
function Monster:chooseAnimation()
if self.alive then
self.movingIndex = self.movingIndex + 1
if self.movingIndex > #self.moving then self.movingIndex = 1 end
self:setAnimationTimer()
else
self.sprite1 = self.dead
end
end
function Monster:draw()
pushMatrix()
pushStyle()
spriteMode(CENTER)
local center = self.tile:graphicCenter()
translate(center.x,center.y)
self:flipTowardPlayer()
if not self.alive then tint(0,100,0) end
sprite(self.moving[self.movingIndex], 0,0)
translate(-center.x,-center.y)
popStyle()
popMatrix()
end
That works so well that I’m going to commit: two kinds of monsters, randomly selected. 90 files committed: all the assets are now in the zip file, even though I only use a few of them.
I’ll add in some more while I’m at it. Shouldn’t be too challenging.
function Monster:initMonsterTable()
local m
MT = {}
sprite(xxx)
m = {dead=asset.slime_squashed, hit=asset.slime_hit, moving={asset.slime, asset.slime_walk, asset.slime_squashed}}
table.insert(MT,m)
m = {dead=asset.fly_dead, hit=asset.fly_hit, moving={asset.fly, asset.fly_fly}}
table.insert(MT,m)
m = {dead=asset.barnacle_dead, hit=asset.barnacle_hit, moving={asset.barnacle, asset.barnacle_bite}}
table.insert(MT,m)
m = {dead=asset.bee_dead, hit=asset.bee_hit, moving={asset.bee, asset.bee_fly}}
table.insert(MT,m)
m = {dead=asset.ghost_dead, hit=asset.ghost_hit, moving={asset.ghost, asset.ghost_normal}}
table.insert(MT,m)
m = {dead=asset.snake_dead, hit=asset.snake_hit, moving={asset.snake, asset.snake_walk}}
table.insert(MT,m)
end
Let’s try to get some pictures and video.Maybe I can patch the program to always insert one monster in the princess’s room.
That’ll do for now. Commit: lots of monsters.
Sum Up and Let’s Get Outa Here
Well, this is pretty quick and dirty, but we have a decent table for monsters, that seems to bear its weight, and the monsters can have any number of poses while they move. The snake monster is pretty boring in green, doesn’t show up well on my flooring. We can deal with that in a number of ways. Maybe different levels will have different flooring, or maybe I can color the snake a different color. Or maybe not have a snake. Few of us are that fond of them.
I’m a little surprised, given that I just hammered this in to see what would happen, because it worked right out of the box. Must be my lucky birthday.
See you next time!
-
The YAGNI principle suggests that we should not assume things about the future. However, as “Customer” for this application, I assure you that I do in fact want the ability to create different monsters at different levels. Use that information however you wish. ↩