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:

assets

Tapping “Edit” at top right give you this:

select

Where you can select items you want to move. Then you tap “Add To” near bottom right, and you get this:

choices

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.

asset pic

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:

fly

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.

monsters

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!


D2.zip

  1. 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.