Dungeon 155
Let’s let the Mimic be oversize. Oh, that works nicely!
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
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.
I rather like the way that larger size looks. But it does seem that he’s always facing away from the Princess:
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:
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
hide
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!
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!