Dungeon 65
Mostly some thinking and planning about behavior. Maybe some code. Now: with movies!
This morning I want to begin with some thinking about where this game might go. I still have no intention of completing a game product, certainly not converting it to XCode, unless someone steps up to help me. I haven’t the time or inclination to produce the kind of finished art that it would take to make this into a polished game product.
What I would like to do, however, is to do enough work so that we can readily see that most of what’s left to be done is elaboration and polishing, that all the fundamental facilities are in place to produce an enjoyable game. That is to say, I want to do the first 90 percent, leaving only the second 90 percent to be done.
Maybe it’s only the first 10 percent, leaving the last 90 percent to be done.
Be that as it may, there are still plenty of interesting things we can work on, things that are necessary to interesting game play. This morning, I want to sketch what some of those are and how we might go about them.
- Narrations
- The existence of the new Floater has me thinking about how to use it1. There are already two distinct uses of it. The first is a static scroll at the game beginning. The second is a dynamic description of a battle between the player and a monster. The notion of a fixed narration could be applied frequently. Perhaps every time the player encounters a new treasure or even a new monster, a short narration could run telling what has happened. “You have found a Jewel of Great Beauty. When worn, this Jewel increases your Charisma by 2d20, causing most monsters below level 5 to worship you and do your bidding.”
- Behavior
- The flip side of the Floater notion is that the coroutine that drives the Encounter narration is embedded logic that takes place over time. It is behavior, although very simple behavior. Perhaps we can apply that in interesting ways. We might have creatures in the dungeon that behave more interestingly than the present ones, who will attack you if you get too close to them.
- A simple example might be that a monster whose strength has been depleted in battle might run away. More complex monsters might band together and attack as a group. There might be dungeon creatures that wander around collecting treasures that you might desire, so that you have to fight them, or rob them by stealth, to get what you need. Maybe there are friendly monsters who will lead you to good things if you feed them tacos.
- Attributes
- Our monsters and player presently have attributes of strength, health, and speed. (Speed is a constant but is written to be variable.) We might want additional attributes. The current ones are programmed in. If we want the game to be extendable without much programming, maybe there should be a way to add attributes, and use them, without programming, or at least without much programming.
- Inventory
- So far, all you can find in the dungeon are keys, and chests containing health stickers. Keys are counted, and have no purpose at present. Health adds one to your health. I have in mind that keys might be needed to open chests. Chests might have other things in them, potions, magic items, weapons, tacos. There might be useful items lying about.
- It would be extra nice if game play involving some new item was easy to specify. But I can live with it requiring a bit of Lua code, especially if the code itself were easy to plug in.
- Levels and Experience
- For a game to be interesting for more than a very short time, there probably needs to be some kind of story arc, if only a simple one where you and the monsters get bigger and stronger. We’re probably not trying to build Minecraft here. But we do probably want our player to grow in experience and power, and it would be good if there could be more and more interesting things to encounter as they delve deeper into the dungeon.
- Much of this comes down to creative art and story telling, but the underlying software has to be able to support that kind of thing.
- Puzzles
- Right now the biggest puzzle we could have would be a door or chest that wouldn’t open unless you have a key, and even that would require code we haven’t written. What if we wanted a door that would only open if you stepped on certain tiles in a certain order? What if we wanted a treasure that you could only get if you placed something of equal weight on a particular stone?
Suffice to say, there are many things we could imagine our creative team inventing to go into our game, and our job as the core developers is to prepare the engine to allow all those things to be done as easily as possible. We might not invent a new programming language for them, but we might at least provide a way to write tiny bits of Lua code that could be plugged together to make interesting things happen.
The big question is:
How Do We Do It?
How do we approach this kind of generality? We could sit down and design an amazing game engine that can see all and do all. We would of course study all the work that has gone before, and choose “best of breed” ideas and “best practices” and “best tools”, and build up the “best game engine ever” and give it to the creative team and tell them to have at it.
That trick never works. First of all, “they” won’t let us take the time and money to build the engine. They’re in a hurry to get the game on the market, and they have finite resources.
Even if we did get the chance to build a great engine, the odds are that the minute someone tried to use it to actually do something, they’d be right back at our door, asking
How can I make the player cosplay as a werewolf to get in with the other werewolves, so that she can mate with the alpha werewolf and her son can defeat him and become Lord High Werewolf and lead the werewolves to defeat the Vikings that Jack is creating, and by the way, how do we create a ship in this environment and where is the water anyway?
We’d be like “What??”.
We do things incrementally here. Sometimes what we do to add incremental capability takes more than a couple of hours. The encounter coroutine took eight, maybe even ten hours to build the infrastructure and make it work. But we don’t get months to do things, we get days. Maybe a week or two if it’s something really wild.
We therefore work with our creatives to take bite-sized steps toward the capabilities we need. We work with them to do real stories, not just to build engine components. We try to build small things that can be used, fairly easily, to build many interesting game bits.
The hard part with this approach is that we dare not code ourselves into a corner. We have to build the system so that we can revise it and extend it, but we never have to start over and rebuild it from the ground up. And there is no reason to believe that that’s always possible.
And yet, I believe that it is always possible, or at least close enough to always possible. I believe that producing product and engine together is always going to turn out better than producing engine first and product later. And even if we did get to product 0.5 and have to do something major to the engine, we’d still be better off than if we had engine 1.0 and product nothing.
This is kind of the fundamental basis of “agile software development”, the notion that we can build good software incrementally, leading to a product being delivered sooner, in a fashion that allows for continual growth and improvement.
And the point of my many Codea game articles is to show that we can in fact do it, and how we do it, and what it feels like to do it.
So very soon now, I’ve got to get down to improving the game again. But it’s important on some mornings to sit back a bit and think about the road ahead.
Let’s get a bit more specific.
We Might …
- Improve Test Facility
- Much as it isn’t game play, the testing framework is beginning to get in the way more than to help. Two things are bugging me: first, when tests break, it’s not obvious unless I look carefully at the console. Second, because of the way the console produces output, it’s hard to spot the errors.
- A day’s work could improve these things. I’m reluctant to do it, because it’s not really all that much fun.
- Music
- Some background music would be nice. It should get dramatic if there are monsters near by. Different areas could have different music. This would be a facility that the creative team would like.
- Sounds
- The monsters could have different sounds. Battles could make better sounds.
- Array Narratives
- If there are to be narratives for the first time you discover something, making those into simple arrays of text will be convenient. (Maybe even files if this were a real system.) The Floater should accept arrays.
- Placement
- Monsters and treasures are placed randomly. We should place them more sensibly. Things we can’t step over should be kept out of hallways.
And …
Behavior Trees
I’m almost certain that we need behavior trees. Let me explain. No, there is too much. Let me sum up.
A behavior tree, as I understand the idea, is a data structure that represents, well, a tree of behavior, or a plan of action. The leaves of the tree are actual behaviors, like “go south” or “attack player”. They can succeed or fail. The other nodes come in a few flavors, including
- Sequence
- Do each item under this node, one after another, so long as they all succeed. Fail if one fails.
- Control
- Do each item in series until one succeeds.
The tree is intended to be “executed” repeatedly, on every “tick”. At each tick, leaves can return success, failure, or running. Generally speaking, the tree only runs until a node succeeds or returns “running”, so that little bits of behavior are executed, adding up over time to an interesting overall bit of action.
In principle, the current encounter coroutine implements a behavior tree around battle, since it does a bit of battle behavior and then stops executing until it’s asked for more results. But in practice, the implementation, as a coroutine, is different.
I believe that our creative team will soon come up with a desire for some other form of behavior, some monster, that will require us to build a real behavior tree. I expect that to be a somewhat difficult implementation, but that it will be a free-standing component and that it will be easy to plug in even if the first implementation takes a few days.
Or, it might be easy.
Enough planning. Let’s code something.
Battle Graphics
The original impetus behind the Encounter coroutine was to synchronize the text crawl with visual activity on screen, so that the monster or player didn’t flash dead while the battle still seemed to be raging on. That much is accomplished: now the monster only dies when the text line comes out:
~~lua function attackStrikes(attacker,defender, random) local damage = rollRandom(attacker:strength(), random) if damage == 0 then yield(“Weak attack! “.. defender:name()..” takes no damage!”) if math.random() > 0.5 then yield(“Riposte!!”) firstAttack(defender,attacker, random) end else yield(attacker:name()..” does “..damage..” damage!”) defender:damageFrom(attacker.tile, damage) if defender:isDead() then yield(defender:name()..” is dead!”) end end end
We know by how the coroutine is used that if fatal damage is rolled, we'll first yield "Princess does 5 damage!", and then after that scrolls up, we'll apply the damage, which may kill the monster, and if it's dead, we'll yield "Ghost is dead!". The death graphic and text appear at the same time.
We can't really do better than that without a new internal function like "will this much damage kill?", which would let us defer the actual death longer. But it looks OK now, as you can see here:

But let's see whether we can do something else clever. When the monster or player is hit, maybe we could color them differently, or make some kind of other display. Let's type something into the code that expresses what we want to do:
~~~lua
function attackStrikes(attacker,defender, random)
local damage = rollRandom(attacker:strength(), random)
if damage == 0 then
yield("Weak attack! ".. defender:name().." takes no damage!")
if math.random() > 0.5 then
yield("Riposte!!")
firstAttack(defender,attacker, random)
end
else
defender:displayDamage(true)
yield(attacker:name().." does "..damage.." damage!")
defender:displayDamage(false)
defender:damageFrom(attacker.tile, damage)
if defender:isDead() then
yield(defender:name().." is dead!")
end
end
end
I have no real idea how to do this, I just know that I want it. I’ll need to put that method in both Player and Monster. (This is making me think they might be subclasses of something.)
function Player:displayDamage(boolean)
self.showDamage = boolean
end
function Monster:displayDamage(boolean)
self.showDamage = boolean
end
Now to display something. In Monster:draw
:
function Monster:draw(tiny)
if tiny then return end
pushMatrix()
pushStyle()
spriteMode(CENTER)
local center = self.tile:graphicCenter()
translate(center.x,center.y)
self:flipTowardPlayer()
if self.alive then
sprite(self.moving[self.movingIndex], 0,0)
else
tint(0,128,0,175)
sprite(self.dead)
end
translate(-center.x,-center.y)
popStyle()
popMatrix()
self:drawSheet()
end
We can put something inside the self.alive
. Let me look for something interesting to display.
function Monster:draw(tiny)
if tiny then return end
pushMatrix()
pushStyle()
spriteMode(CENTER)
local center = self.tile:graphicCenter()
translate(center.x,center.y)
self:flipTowardPlayer()
if self.alive then
sprite(self.moving[self.movingIndex], 0,0)
if self.showDamage then
sprite(asset.builtin.Tyrian_Remastered.Explosion_Huge,0,0)
end
else
tint(0,128,0,175)
sprite(self.dead)
end
translate(-center.x,-center.y)
popStyle()
popMatrix()
self:drawSheet()
end
Let’s see if this works.
That’s excellent! I’ll put the same thing into the Player, but look for a different graphic.
Truth be told, I like the explosion better, but this should show us that we can do special things graphically during the encounter.
Commit: graphical action when attacker scores damage.
Let’s call it a morning, and sum up.
We could, of course play a sound as well. Shall we?
Summary
After a bit of thinking, we got down to adding behavior in the Encounter, which was the point of the coroutine approach. We were able to quickly indicate the need for a special display of damage being done, and to make that display during the time that the damage message was at the bottom of the crawl. You could imagine other such displays, showing a specific quick effect, or even some cumulative display, such as a ghost fading as its health declined.
To do that, we’d need to have behavior that was unique to the individual monster type. But we have those monster entry tables, and we can put whatever we like into them. Maybe we’ll do something like that soon.
Then again, maybe we won’t. We’ll keep trying to find things that add to the game while requiring us to show how easy–or how hard–it is to do things.
Stay tuned!
-
Small boy with hammer something something … ↩