January 04, 2005

The Ramble Chronicles: On Visibility

Visibility: What can the player see, and when can he see it?

The simplest answer is everything, all the time--what I'll call complete visibility. The entire world (or, at least, the entire level) is visible all at once. You can see your character, all terrain features, traps, treasures, monsters, etc., et al. This is dead simple to implement (provided the world is small enough to fit on the screen all at once), but generally speaking it's boring. A big part of the fun of playing a tile-based game is exploring the game world, and that requires limiting visiblity.

So what alternatives are there? The simplest is to use a limited viewport. For example, put your character at the center of an 11*11 square of tiles, but let him see absolutely everything within that square. You'll be able to see the skeleton that's lurking on the other side of that wall, waiting to attack you if you go around the corner...but unless you attack the skeleton and go down the path behind its hiding place, you'll never know that just beyond the edge of the viewport, just six tiles away, there's a room just full of treasure.

A limited viewport can be quite effective, and the technique has been used in lots and lots of games; I first encountered it in Ultima, but Final Fantasy I also uses it to good effect. Its primary advantage, as with complete visibility, is that it's cheap. You simply display the tiles around the character, and you're done. The disadvantage is that you never feel like you get the big picture--because you never get the big picture.

A game can't be said to use a limited viewport just because you can only see part of the world on the screen; it's essential that the viewport be used to hide things that are really quite close by from the player's point of view. Angband, for example, only shows you part of the level at once--but that's only because the level is bigger than will fit on the screen at once. If you've got a big enough screen, you can resize the Angband window until everything fits.

The next alternative is line-of-sight visibility. Some tiles are opaque (like walls) and block line-of-sight, and some are not (like floor tiles). In its most extreme form, the player can see only tiles to which he has an unblocked line-of-sight; all other tiles appear black. One correspondent sent me a link to a classic Macintosh game called TaskMaker that combines strict line-of-sight visibility with a limited viewport; here are three consecutive screenshots of the character walking by a pillar:

I confess that I find it disconcerting to have all that black flashing around as I walk; and if TaskMaker didn't use a limited viewport it would be even worse. Also, by the third frame I've seen every tile in the room, and I know perfectly well what they all look like. Granted, a monster could creep into my blind spot, but failing that I'd be really surprised if the tiles had changed when next I looked at them.

Let it be said: Ramble won't use strict line-of-sight.

Angband uses a scheme I'll call persistent line-of-sight (though it has a wrinkle I'm going to ignore for a moment).
In this mode, your character can initially see only those tiles to which he has an unblocked line of sight. But as he moves around, permanent terrain features are remembered, as are any objects that are lying around. Monsters remain volatile--you can only see monsters when they are in direct view. I like this mode; you have to explore, and (since there's no limited viewport) you end up getting to see a lot of the world at once. And there are no awful sweeping black triangles as you walk around. And you can do some fun things like detection spells (have objects in the black unexplored areas magically appear) and ESP (the player sees all monsters within a certain radius, whether they are in line-of-sight or not) and clairvoyance (the player sees everything within a certain radius). But there are some implications.

The first is a minor philosophical problem. Consider the map below, which I typed in by hand but which could easily have been part of a game of Angband:

#########
#....@..#
#.#######
#..!.....
#########

Our hero (the "@") has just walked into a dead-end, passing a potion (the "!") on the way. He knows that the wall below the potion is there because he saw it as he walked by; and he knows the potion is still there because potions are inanimate objects and don't walk around by themselves. But what if some monster sneaks in and takes the potion while he's not looking? In Angband, the potion (the "!") will disappear as soon as the monster takes it. This effectively gives the player a mild form of ESP--he can easily tell when objects he'd previously seen are moved. In practice this turns out to be less disconcerting than the alternatives: having objects vanish immediately when they are out-of-sight, or having moved objects vanish only when you come back to where you can see them.

Another implication is that you often can't see monsters until they are right on top of you, which can be a big deal. Ramble has precisely one monster at the moment, a skeleton; and if the skeleton touches your character, that game's over. There's no combat model yet, so the only way the player can avoid being killed by the skeleton is by avoiding the skeleton altogether. And that's very difficult to do if you can't see it. At present, consequently, Ramble gives the player complete visibility.

Finally, computing line of sight can be very costly, especially if the visible area is large. This is another advantage of persistent line-of-sight over strict line-of-sight. In strict line-of-sight you must compute the visibility of every tile in the viewport after each and every move the player makes. In persistent line-of-sight, you only need to compute visibility for tiles that haven't been seen yet, and for monsters; this is generally an easier problem. Moreover, it scales to the case where the viewport is large and the visible region within the viewport is also large: visibility is computed once for the terrain; after that, all you need to consider are the monsters.

As I hinted above, there's a wrinkle in Angband's implementation: the Dungeon generally isn't lit. You have to bring a light source with you, and different light sources give off different amounts of light. A torch will light up the tiles no more than one tile away from the character. A lamp will light up the tiles up to two tiles away from the character. In addition, rooms in the dungeon can be lighted or dark. In a room that's lighted, all of the tiles are already marked visible before you walk in. In a room that's dark, none of them are, and you must rely on your light-source. In general, you can only see monsters at a distance in a room that's lighted; otherwise, monsters outside your torchlight are invisible.

This has the effect of making the dungeon more mysterious and spooky; it also has the effect of limiting line-of-sight computations. You only need to compute visibility for tiles within the radius of torchlight, and for tiles that are lit permanently. The radius of torchlight effectively becomes a tiny viewport.

At present, as I said above, Ramble gives the player complete visibility. Ultimately, I'd like to implement persistent line-of-sight, possibly combined with a limited viewport (if necessary for speed). Whether I'll implement light or not, I don't know. But it's clear that I can't implement any kind of line-of-sight unless I either get rid of monsters or provide a combat model. Combat, however, is another topic.

Ramble is going to evolve slowly as I model and implement different bits, and might end up leaving a stream of distinct and separate games behind it as I work on particular techniques. More on that later.

Posted by Will Duquette at January 4, 2005 07:01 PM