Monday, April 28, 2008

Refinements to guard behavior

A little more work on my thief-like/rogue-like hybrid.

The next big feature I need to put in is for guards to recognize when things in the environment are amiss: doors ajar that should be closed, lights out that should be lit, and of course missing treasure! This should beef up the difficulty of the game substantially.

I haven't had the time or energy to do that yet; hopefully this week. In the last couple of weeks I've done some small refinements to existing things.

Guards used to get completely discombobulated when a group of them would try to enter a narrow alleyway in pursuit of the thief. This was because they considered other guards to be permanent, static obstacles. The first guard would enter the alley, whereupon the rest would be forced to find paths going clear around the buildings. I fixed this by making guards into a slight penalty for movement into the occupied square. This ensures that if a guard can get around another by simply stepping around him, he will. Otherwise, he'll attempt to move into the other guard. If the other guard hasn't gotten out of the way he will stand still. My guard states already have timeouts so if this happens for two or three turns in a row the guard will give up.

Guards' attacks were a side effect of trying to move into the player, which was a problem when the player was on a square that was inaccessible to the guard. Formerly I'd prohibited guards from moving onto water or tables. I've kept the water restriction, but guards can now attack the player if he's adjacent and they can see him. In order for this to work with tables and bushes I had to add a special case to visibility: if a guard is adjacent to the player, and looking, they will see him even if he's hidden inside a bush or under a table.

Tables are now accessible to guards, but instead of a binary pass/block for each terrain type, I've now got integer-valued movement costs. This allows for some refinements to guard movement. They will avoid moving over chairs or tables, and they really try to avoid moving through bushes or windows.

Guards alerting other guards is slightly refined as well. Formerly, when a guard would sound the alarm, any guards that had not yet been updated that turn would hear the shout on that turn, while the guards that came before would not hear it until the next turn. I fixed this by running the alert status through a holding variable, so that all guards will hear alerts on the turn after they are made. This makes the speech pattern more sensible too.

I still need to add specific guard states for alerts, so their speech will be more specific. I'd like for a guard to say something about where the player is, along the lines of “He just ran behind that pillar!” This should make the player feel much more like the guards are paying attention to what he's doing. Also, I'll change the goal spot for alerted guards from the position of the alerting guard to the actual reported position of the player, which should toughen things up some.

Finally, I did some initial experiments with a ghost. The Thief games have always been supernatural tales at their core; it's part of how they sustain a feeling of dread, since you don't know what rules supernatural beings play by.

My ghost inspiration comes from Barry Hughart's book Bridge of Birds. In the book there's a bit of a trick to seeing ghosts, which the heroes learn in order to solve the central mystery of the story. I've been trying to think of interesting tricks for seeing ghosts. One is that you can only see them out of the corner of your eye. As such, I implemented a ghost which is only visible when you move away from it. It's also invisible in direct light, and invisible when it's very close to you. This ensures that, when a player first encounters a ghost, their natural inclinations to move toward it or wait to see what it does will render it invisible.

It's mildly interesting but I'll need to figure out some way to actually interact with the ghosts for them to really work. Some of the Bridge of Birds ghosts delivered a cryptic speech after they were spotted, whereupon they would vanish. Another ghost was stuck endlessly reenacting a doomed rendezvous with her lover; the protagonists had to engage her in order to break her out of it.

In Thief the various undead creatures were actually fairly straightforward modifications of the standard guards. A big part of their scare factor came from the fantastic sound design.

Monday, April 14, 2008

Look Around and Listen (The Problem With State Machines)

At the last Game Developer Conference I attended a talk about enemy design in Naughty Dog's latest game Uncharted. Having not played the game, one statement really struck me. An adversary's average lifetime after being activated was measured in seconds. Most of their animation budget was devoted to deaths.

Good game design (as with many things) is all about capitalizing on your strengths and hiding your weaknesses.

This week I did a bit more work on my thief-like/rogue-like hybrid. The thief, not being homicidally inclined, is incapable of killing the guards. The longer they live (as with anybody) the more likely they are to do something stupid.

At first, guards could only see, and all they cared about was seeing the thief. They had a simple state machine, like this:

When a guard spotted the thief he would leave his patrol state and enter a brief “look” state. This gives the player one turn in which to get back into hiding. If they don't, the guard gives chase. (This may turn out to make the game too easy; if so I may need to do something additional, like having a guard go instantly to chase if they are in a state of alert.) When the guard lost sight of the thief he would enter a “search” state which consisted simply of moving to the last place he saw the thief. After a timeout, the guard would return to his patrol.

To add the sense of hearing, the first thing I did was to simply include it in the “Can I see the thief?” calculations. The obvious problem was that a guard would hear the player and say “What's that I see?” or something similar.

One solution would be to implement some sort of memory regarding whether the guard has seen and/or heard the thief in the current “interaction”. I thought it might be interesting to make the guard behave more aggressively based on a sighting than on hearing, so I opted to just add more states to my state machine. It wound up looking like this:

Investigate is the state in which a guard moves to the point where he last heard the thief. Search is moving to the point where he last saw the thief. Distinguishing between these allows the guard to say different things when he gives up and returns to patrol (“Where'd he go?” versus “The noises have stopped,” for instance). Chase is like search except that the thief is in sight. I can't remember if I really need it, but I think I'm using it to stop updating the last-seen position one turn after the thief goes out of sight (to simulate being able to see which direction he moved to get out of sight).

This is obviously getting kind of crazy, though. The next thing I'm working on is for guards to be able to alert other nearby guards. Currently I'm putting alerted guards into the search state and setting the last-seen position to the position of the guard who called out. This causes the alerted guard to move toward the guy making noise. If he doesn't spot the thief in the process, he'll eventually give up and return to patrol. Of course, inherent in the search state is the idea that he saw the thief at some point, so he might say something to that effect when he gives up, which makes him look odd.

State machines are nice when they have a handful of states; they help you to think about all the transitions and to encapsulate all state into single nodes. I find that they don't scale very well as the complexity and number of states grows, though. I don't feel like I've got a great solution to this yet.

Monday, April 7, 2008

Code Puttering

“Blah,” as Toad might say.

I started puttering yesterday in my ThiefRL code to get back up to speed on it. I'm trying to decide how best to organize sound propagation. There's a “push” model, where the sound emitter broadcasts to nearby listeners, and a “pull” model where listeners search for nearby sound emitters. I think the broadcast model might be more efficient. Either way I'd like to hide that detail as much as possible from the AI code.

In general the programming languages I'm familiar with don't do a good job of supporting abstractions of this sort. Another sort of abstraction that isn't well-supported is deciding how to link related data: whether to embed it together in the same structure, or to use a map (dictionary) to connect them more loosely.