Let’s let the Mimic be oversize. Oh, that works nicely!

bite

I’ve determined that I can easily resize the sprites using Procreate, and of course we could associate a scale with each Entity and scale them all independently. But where’s the fun in that? Let’s see if we can let the Mimic be a bit large.

The issue with entity sprites larger than a tile is that each tile’s contents is drawn when that tile is drawn. Anything that spills over is likely to be cut off when the next tile is drawn. The fix I have in mind is to do as we do with the Player, who is drawn separately. I think we have a collection of monsters that we can draw.

But how will we avoid drawing them as part of contents? The code is this:

function GameRunner:drawMap(tiny)
    fill(0)
    stroke(255)
    strokeWidth(1)
    for i,row in ipairs(self.tiles) do
        for j,tile in ipairs(row) do
            tile:draw(tiny)
        end
    end
    self.player:drawExplicit(tiny)
end

function Tile:draw(tiny)
    pushMatrix()
    pushStyle()
    spriteMode(CENTER)
    self:drawSprites(tiny)
    if not tiny and self.currentlyVisible then self:drawContents(tiny) end
    popStyle()
    popMatrix()
end

function Tile:drawContents(tiny)
    local center = self:graphicCenter()
    for k,c in pairs(self.contents) do
        c:draw(tiny, center)
    end
end

The Player is in the contents. How do we avoid drawing her?

function Player:draw()
    -- ignored
end

function Player:drawExplicit(tiny)
...

The Player ignores draw, and draws herself when we call drawExplicit. If we do have a collection of monsters, we can do the same. If we don’t have such a collection, we can get one.

function GameRunner:setupMonsters(n)
    self.monsters = Monsters()
    self.monsters:createRandomMonsters(self, 6, self.dungeonLevel, self.playerRoom)
    self.monsters:createHangoutMonsters(self,2, self.wayDown.tile)
end

In GameRunner, we have an instance of Monsters, which is little more than a collection of monsters. It handles such things as moving the monsters:

function Monsters:move()
    for i,m in ipairs(self.table) do
        m:chooseMove()
    end
end

Let’s give it a draw function:

function Monsters:draw(tiny)
    if not tiny then
        for i,m in ipairs(self.table) do
            m:drawExplicit()
        end
    end
end

Now in Monster, we’ll have to put in the null draw and convert the existing one to drawExplicit. That looks simple, and it goes like this:

function Monster:draw()
    -- ignored
end

function Monster:drawExplicit()
    local r,g,b,a = tint()
    if r==0 and g==0 and b==0 then return end
    self:drawMonster()
    self:drawSheet()
end

Test. Well, it would probably work if I ever asked the Monsters to draw themselves. Duh.

function GameRunner:drawMap(tiny)
    fill(0)
    stroke(255)
    strokeWidth(1)
    for i,row in ipairs(self.tiles) do
        for j,tile in ipairs(row) do
            tile:draw(tiny)
        end
    end
    self.monsters:draw()
    self.player:drawExplicit(tiny)
end

lots of monsters

Well! I found a lot of monsters in that room. Looks good. Notice that overlap of the ghost behind the attribute sheet for the pink slime. I don’t think that’s new, we just hadn’t noticed it.

Now I’ll force all the monsters to be Mimics, to see how that looks.

mimic

I rather like the way that larger size looks. But it does seem that he’s always facing away from the Princess:

facing away

The monsters flip with intention to look at the player. But is there a setting to use in case their natural facing is reversed?

function Monster:flipTowardPlayer()
    local dir = self.runner:playerDirection(self.tile)
    if dir.x > 0 then
        scale(-1,1)
    end
end

Let’s see what we want. Let’s have a new variable that can appear in the monster table, facing. It will default to 1, and we’ll set it to -1 for the mimic:

    self.level = self.mtEntry.level or 1
    self.facing = self.mtEntry.facing or 1
    m = {name="Mimic", level=1, health={10,10}, speed={10,10}, strength=10, facing=-1,
        attackVerbs={"bites", "chomps", "gnaws"},
        dead="mw10", hit="mw02",moving={"mw01", "mw01", "mw03", "mw04", "mw05", "mw06", "mw07", "mw08", "mw09", "mw10"}
    }

Now in the flip method:

function Monster:flipTowardPlayer()
    local dir = self.runner:playerDirection(self.tile)
    if dir.x > 0 then
        scale(-self.facing,1)
    else
        scale(self.facing,1)
    end
end

We have to set it either way now, since the Mimic needs to be flipped normally. Test.

The Mimics work as advertised:

facing ok

face ok

Now we can allow them to appear. For now I’ll let them be level 1, but I think we’ll be changing that when we embed them in what looks like a chest.

One more thing: when we create monsters, we create only those of a given level:

function Monster:getRandomMonsters(runner, number, level, roomToAvoid)
    local t = self:getMonstersAtLevel(level)
    local result = {}
    for i = 1,number do
        local mtEntry = t[math.random(#t)]
        local tile = runner:randomRoomTile(roomToAvoid)
        local monster = Monster(tile, runner, mtEntry)
        table.insert(result, monster)
    end
    return result
end

function Monster:getMonstersAtLevel(level)
    if not MT then self:initMonsterTable() end
    local t = {}
    for i,m in ipairs(MT) do
        if m.level == level then
            table.insert(t,m)
        end
    end
    return t
end

Let’s change that == to <= and allow monsters at the current level and below to appear. That seems better to me somehow.

Should I commit this first and then do that? Yes: “Monsters can now be larger than a tile.”

Now the other:

function Monster:getMonstersAtLevel(level)
    if not MT then self:initMonsterTable() end
    local t = {}
    for i,m in ipairs(MT) do
        if m.level <= level then
            table.insert(t,m)
        end
    end
    return t
end

Commit: monsters at level N can be of levels 1-N.

No, I didn’t test that. I did review the code. Should have tested, this test fails:

        _:test("Can select monsters by level", function()
            local t = Monster:getMonstersAtLevel(2)
            _:expect(#t).is(3)
        end)

New answer is: 7. I counted them. four level 1s, 3 level 2s. We can expect this test to fail again sometime. No biggie, but there’s a lesson here about running the tests even for a one-character change.

Commit: fix failing test counting monsters.

A bit of gameplay shows me both level 1 and level 2 monsters at level 2, as expected. Double check, belt and suspenders, etc.

What Now?

I guess the right thing to do next is to prepare for the mimic being in chest mode and such. I think I’ll import all the sprite sheets for it. That’ll be tedious and I’ll spare you most of it.

I decide to bring in the “hide” sheet, which includes some simple chest-looking images, and the “attack” one. We’ll have to gin up some way to switch modes, but that remains to be figured out.

Then I’ll init some more named images:

    sheet = asset.mimic_spritesheet_walk
    names = {"mwalk01","mwalk02","mwalk03","mwalk04","mwalk05","mwalk06","mwalk07","mwalk08","mwalk09","mwalk10"}
    assert(#names==10, "bad count for mimic walk")
    Sprites:add(names,sheet, 0,0,0,0)
    sheet = asset.mimic_spritesheet_hide
    names = {"mhide01","mhide02","mhide03","mhide04","mhide05","mhide06","mhide07","mhide08","mhide09","mhide10"}
    assert(#names==10, "bad count for mimic hide")
    Sprites:add(names,sheet, 0,0,0,0)
    sheet = asset.mimic_spritesheet_attack
    names = {"mattack01","mattack02","mattack03","mattack04","mattack05","mattack06","mattack07","mattack08","mattack09","mattack10"}
    assert(#names==10, "bad count for mimic walk")
    Sprites:add(names,sheet, 0,0,0,0)

Let’s see what those sheets look like:

walk

walk

hide

hide

attack

attack

Nice. I think I’ll switch the existing mimic to the attack one just to see how it looks. I renamed the images to be a bit more mnemonic anyway.

Oh this looks good!

attacking movie

I think I’ll wrap this up, and maybe play offline with selecting images for dying and death. The spritesheets for the Mimic include a whole death sequence. We may have to figure out a way to allow for some histrionics when they are defeated.

One final commit: new sprites for mimic.

Summary

It’s good when changes like these go smoothly. It tells us that the objects we’re working with are in pretty good shape, and are willing to help out. This isn’t always the case, even in this tiny program, but this area seems pretty good.

The Monsters object paid off by giving us a single object to tell to draw all the monsters, which let us have monsters bigger than a tile. (We can’t currently support a monster that actually resides on more than one tile. If they get too large, we might want that.)

The SpriteSheet seems to have served well, letting us carve up those sheets and gain a set of addressable frames, which is what we need.

All in all a decent morning. Relaxing. No stress. I like that.

See you next time!


D2.zip