Saturday, March 14, 2015

Asymmetry

Computers and humans aren't good at the same things; you can capitalize on that. For instance, let's say I have a game that involves flying a rocket through caves. I could try to make an AI that can also fly a rocket through caves and pit you against it, but that would be a lot of work that may or may not pay off. Instead I can put a bunch of dumb turrets in the caves to give you however much challenge you need.

Positive and negative feedback

Positive feedback is paying dividends on investments. The “rich get richer and the poor get poorer” as Jesus says (Matthew 25 and elsewhere). A lot of basic strategy game design is built around this, where you're accruing advantage over time.

The upside of positive feedback is that it drives things to a conclusion. For instance, in draughts when you get a pawn to the opposite side of the board it promotes to a king, with vastly increased mobility. In a first-person shooter each opponent you kill is one less person who can harm you, so the game gets progressively easier. If you weren't continually presented with new people to kill the game would rapidly lose its challenge.

The downside of positive feedback is that your early choices in a game are the most critical, with successive choices becoming less and less important because they're overwhelmed by the dividends from the early choices. For instance, you might have a critical sequence of things to do to get a Starcraft game started with optimal production. In a stealth game when you get discovered, a guard might call for help from other guards, who then arrive and encircle you making defeat a near-certainty. Game designs have to try to mitigate this.

Negative feedback is resistance to change. It's very useful for balancing games. For instance, you might have a set of items available for sale in a board game, and the ones that aren't bought get a little bit cheaper each turn, until they match someone's idea of their value. Another example is when you acquire territory but your troops become spread thinner and as a result it becomes harder to hold that territory. In my 7DRL the last goblin from one chest will run off to another chest; as a result chests you tackle later tend to have more defenders.

De-escalation

I like to ensure that the player can back away from situations when they get in over their head. A lot of times this means the player can run faster than anyone else, but usually you also need other ways to cool down a situation. This is a form of negative feedback, I suppose.

Designers need to spend a lot of time thinking about what happens when the player fails, and how to keep the game rolling through it. The coin-op approach was to just put up Game Over and let the next person have a turn, but with a bigger game played solo it's good to think about how to not have the game just reset when the player fails. Thief 3, for instance, gave you a jail level to bust out of if you got caught in the open-city part of the game. This served as a re-trainer for the skills you were lacking.

Mowing the lawn

This is what I call solitaire-style or match-three sorts of games, where you're not spending most of your time learning new skills; you're just trying to execute existing skills well. It's a low-intensity sort of fun.

Kill them fast

Every second an AI stays alive in a game is another chance for them to do something dumb that destroys the player's suspension of disbelief.

Friday, March 13, 2015

Last night I ran the game past a couple of my friends, which highlighted how poor the feedback was for monster movement and attacking. After trying various things to indicate without animation who had attacked, or where they'd moved from (I tried motion-blurred icons!), I decided I really needed to bite the bullet and do some animation. It's something I have not really figured out a good solution for in my roguelike library.

The technique I opted for here is dirt-simple. You can register a callback with the library for updates, which happen every 5 milliseconds. When time advances in the game, I set a frame counter and register an update callback. (It's 12 frames, I think right now, so pretty quick.) Each update, the frame counter is decremented and the screen is invalidated so it'll get redrawn. On redraw, the frame counter is converted into a percent-done amount, and the characters use this to position themselves as some simple functions of time. Once the frame counter hits zero, the update callback is unregistered, which causes the roguelike library to go to sleep until the next Windows message. Thus it doesn't use any CPU time. (I don't have ongoing incidental animation in the game.)

Sitting still the game looks about the same as yesterday:

Adding animation exposed a nasty bug in my movement code. The pathfinder was returning a set of coordinate pairs: the guy at this position moves to that position, etc. The problem is that the pathfinding code can return a pair of moves (A --> B) and (B --> C). If you process those in that order, you end up with two people at position B after processing the first order. The lookup is indeterminate for handling the second order; you might get the guy who was originally at A, or you might get the guy who was at B. In the non-animated version it didn't matter since all monsters are interchangeable, but with animation it was glaringly obvious when someone moved from A to C in one turn.

Besides animation, I did some other chores like putting in a help and credits screen. I also did some ongoing renaming of stuff in the code, from its origins as a sci-fi extravaganza to its current theme. (The level is still called a planet, though.)

It's been a fun week, and I'm hugely relieved that I got a fun game out of it. In the middle of the week I wasn't sure I'd be able to come up with something interesting. I'll probably talk more about this tomorrow, but it really has been a miniature version of a full game cycle. The wild ideas, the drastic cuts, the race to the finish, etc.

Thursday, March 12, 2015

I spent a huge chunk of the day trying to work some of the kinks out of the enemy movement. There were situations where two guys would be trying to squeeze into the same attack spot (between obstacles) and since they both couldn't get there, nobody would move.

I eventually solved this by having guys reserve attack spots, and the ninja-circle ring goal spots for everyone else omit any spots that aren't accessible without passing through the reserved attack spots.

Another fun thing I was able to do with the pathing: when you're down to one guy who is aware of you, he runs off to the nearest treasure. This helps lead the player toward treasure/battle, and is better than the previous behavior which was for the guy to follow you around (but stay out of your reach).

Other work today consisted of slight improvements to the user interface, and discussions with my beta tester Tom Elmer. The main problem with current gameplay is to train the player to use the wait key, which is critical to survival. You have to look for patterns of enemies to avoid getting hit back. They will take a swing at you if they can do so and have at least one buddy adjacent to the hero at the end of the turn; otherwise they retreat. (If it's not obvious, I'm a huge fan of Hoplite.)

Wednesday, March 11, 2015

I've been mostly working on trying to get a rush-attack model working with the enemies. The idea is that, since if you bump an enemy they die, it doesn't make sense for them to attack one by one. Rather, they should wait just out of range until there are two or more of them ready to rush in; that way they can be guaranteed of doing some damage.

It's been surprisingly challenging for me to work this out, so I've de-scoped the game radically. I grabbed the tiles from the excellent Dawnlike tileset and reskinned the game as a fight against goblins, since goblins typically use this mobbing tactic in games. Here's a screenshot of the work in progress:

I've thrown up a web page for the game, too, with a build in progress.

I think it's starting to be a bit fun, so that's good. It's slightly challenging to complete a map without dying.

The "transmitters" that I had with the signal-hunting mechanic are now hidden potions that restore one of your health hearts. I have no idea what the fiction for that is; it'll probably get changed up before release.

Tuesday, March 10, 2015

I'm finally getting something that might work for core gameplay, and I've been plowing ahead on some of the other bits as well.

One of the things I've been experimenting with this afternoon is having enemies hold their distance until there are enough of them to have a chance of damaging the hero. This, coupled with some tweaks to their pathing to penalize movement uphill or through forests, gives you a chance to escape. I'll be adding in forest cover, so if you get far enough away from the enemies and are under the forest canopy they'll lose sight of you.

In this animation you can sort of see the monsters holding up until there are two of them in range to rush the player. Unfortunately they don't always both successfully rush; if they are both aiming at the same spot, for instance. That needs fixing up. If an enemy finds himself solo in range of the hero he'll back up. This is all done with a lot of monkey business in the Dijkstra search.

Something that is not obvious is that the player is killing adjacent monsters (one a turn). I need to add some animation of some sort for that. It'll probably be as simple as showing reactions on button presses (and then going on to the final display on button release).

On other fronts, I've organized the code so multiple planets are generated and you can move between them. Eventually (tomorrow?) there will be a space mode for moving between planets, using the Triplanetary board game movement system.

The general theme of the game is shaping up to be some sort of undercover interplanetary freedom fighter. A mix of stealth and action, and maybe opposition will shift from planet to planet as you accomplish your objectives, to make the latter objectives more difficult.

Monday, March 9, 2015

Ugh. Gameplay eludes me, still. The monsters are either too smart (they avoid line of fire unless they have no choice) or too dumb (if you have yourself set up with a single line of approach, you can kill the entire lot with no further trouble).

I may end up transplanting my proven ThiefRL stealth gameplay to this game; have it be some sort of interplanetary heist. Wouldn't be the worst thing.

In the meantime, I've done some level generation stuff. This is based on Loop subdivision with noise added:

I got basic enemy pathfinding working. Rather than having each enemy path independently, they're all being moved as part of a single Dijkstra shortest-paths search, so monsters closest to the player (in the distance metric) move first. This ensures that nobody unduly holds up anyone else.

Working the distance metric out was an interesting problem. If you measure distance strictly based on grid distance from the player then monsters can get hung up because their desired destination is occupied by another monster. In this case they should go around if they can get closer somewhere else. So I made a distance metric that is two components, lexicographically ordered. The dominant component is how many occupied cells are between us and the goal, and the lesser component is how many unoccupied cells there are.

Ultimately I separated the impassable obstacles out into a third score component that dominates the other two. This ensures that monsters won't cluster right on the other side of a wall, because that wall is never going to move. You can see that in effect in the animation above.

The shooting mechanic is problematic. I've experimented with having enemies avoid the hero's line of fire until they get close enough to rush (more path score adjustments), but then you don't get a chance to shoot them at a distance.

I've created a simple signal-strength hunt mechanic. I'm also considering signal directionality. We'll see; I'll keep trying stuff for a day or two.

Sunday, March 8, 2015

Yesterday was devoted to getting the hex grid running properly, and to stripping out the parts of the SpaceRL game that I didn't need.

These maps are supposed to represent planet surfaces, so I have them wrapping horizontally but not vertically (kind of like a Mercator projection). It took a bit of head-scratching to get my viewport code to duplicate things properly when they are straddling the edge, but that's all working great now. You can run around and the view keeps you in view, and the window's resizable.

Today's task is to try to come up with a good core mechanic. I've begun work on having monsters path toward the player and I am going to experiment with various methods of dispatching them. If I can get a good rhythm for that then I'll look into a signal-hunting mechanic to encourage exploration of the map. There are lots of other things that can be layered on top, but the central player-versus-monsters thing needs to be settled.