Apr 7, 2016 - A Review of Overhead 8-Directional Shooters

Comments

To make better games, one can look for similar games (not only in the same genre), and see what worked and what didn’t. One game I’m making is C-Dogs SDL, an overhead 8-directional shoot-em-up. A while ago I started looking for similar games, looking for cool features I can steal, but also for where they weren’t fun, and why. Bertram25 (of Valyria Tear and OpenDungeons) encouraged me to elaborate on some of my conclusions in a blog post, so here it is :smile:!

What are Overhead 8-Directional Shoot-em-ups?

8-way

This mouthful of a sub-genre doesn’t get mentioned a lot. In a nutshell, it’s where you can move and shoot in 8 directions, from an overhead perspective. Sounds awfully specific, so people simply describe these games as top-down shooters or 2D shooters, which lumps them together with other sub-genres like twin-stick shooters (Geometry Wars), scrolling shooters (Gradius, most of the Touhou games), run-and-guns (Contra), even games like Asteroids (which have tank-controls, and includes the Strike series or even the on-foot sections of 2D GTA). Fans of those sub-genres will understand how different they are; each has different design lessons to learn.

Small differences in game mechanics can mean big changes in gameplay. I think overhead 8-directional shoot-em-ups strikes a sweet balance between movement and shooting, between tactical play and arcade action. The 8 directions means some, but not complete, freedom in engaging enemies around you. As a player, you will:

  1. Move into an ideal firing line
  2. Shoot in one of the 8 directions

But also:

  1. Make tactical decisions whether to stay back and shoot in one direction, or charge in and shoot in many directions
  2. You can freely move to dodge incoming fire, but doing so will affect your line of fire

It is this close relation between moving and shooting, and limitations in both, that creates very dynamic gameplay. Great games in this sub-genre understand this, and design accordingly: map design, to weapons, to enemy AI. Let’s compare with its sibling sub-genres:

  • Twin-stick shooters: here you can aim in any direction, not just 8. Movement is much less important, since you can shoot at any target from any position. The implication is, for 8-directionals, indirect-fire weapons like shotguns and seeking weapons are much more powerful. The limits in aiming also affects map design; maps with walls that aren’t in the 8 directions are more awkward for 8-directionals.

    crimsonland

    Because twin-stick shooters often use analog sticks for aiming, they are imprecise over long range, and often feature auto-aim. Some twin-stick shooters use the mouse to aim, which is extremely precise, and instead feature recoil. This means that twin-stick shooters’ primary tactical consideration is range. For 8-directionals, there’s a greater emphasis on positioning; range can also be a factor, but often produced by projectile speed. Recoil and auto-aim are less important.

  • Scrolling shooters: only being able to shoot in one direction has a huge effect on game design. Map design is minimal; many scrolling shooters have no walls or obstacles at all. Instead, it’s all about enemy design, including their movement patterns, firing patterns and so on. The challenge is to shoot at enemies moving and firing unpredictably before they overwhelm the player.

    xevious

    A lot of 8-directionals go the tactical, emergent gameplay route, where AI is relatively simple and independent. This has its perks, for example you can play with crowd control with simple, predictable AI, but scrolling shooter’s patterns have a lot to offer too. Bosses really benefit from complex firing patterns.

  • 4-directional shooters: the most similar to 8-directionals. What does the lack of diagonal directions mean? More movement, faster action, and enemies that can shoot in the 4 directions are much more dangerous. Weapons that can shoot diagonally or seek are much more powerful.

Early arcade/console games

Overhead shooters have a long heritage; one of the first arcade games ever, Gun Fight, had players duelling with a two-stick control scheme - one to move, the other to aim and shoot. Through the 80’s game makers added features - powerups, different weapons - and tweaked game mechanics, learning valuable design lessons.

Front Line

Front Line

An early Taito game, this is a really underrated one with some neat ideas. You play as a soldier who can shoot a gun or throw grenades, the animations are hilarious, and you can even drive tanks! Nevertheless, it has significant flaws too:

  • Bullets come out of your gun barrel on your right hand, which makes sense but makes aiming really hard, especially since enemies can come from all around
  • Enemies are really hard to pre-empt. This is long before game designers learned to use antics. Instead, the enemy AI literally performs actions at random, including shooting at you.
  • There’s not enough meaningful difference between the gun and grenade; most of the time they are both useful. I think shmups with their screen-clearing “bombs” got it right.

Some of these flaws may be intentional, given that it’s a quarter-munching game at heart. It seems that the arcade version is much more superior to the NES port. The pacing in the NES port is really slow too.

Super Contra and Jackal

Super Contra Jackal

This duo of Konami-made classic NES games share many similarities, and are distinctive, quality entries in the subgenre. Not only are these games heaps of fun, they are also super polished, with great music as evidenced in the covers and remixes for Konami games in general.

These games really do a lot with the subgenre, with a great variety in enemies, powerups and weapons:

  • Enemies include weak-and-slow infantry (that, in Jackal, can be run over!), fixed turrets, vehicles of various speeds and strength, even fixed-path planes. This gives the player a lot of choice in how to engage. The action is frantic so making these choices really puts pressure on the player.
  • The weapons that both you and the enemies use are interesting. There are slow-moving but accurate bullets, faster but fixed-axis bullets, indirect-fire grenades, spread shots, even homing missiles. Some of these keep you away, some keep you moving, some force you to stop and evade. The games are at their hardest when multiple bullet types are in play, where their combination is much more lethal.

One downside, especially compared to their contemporaries, is that the pace is slow. Since both games have 1-hit-kills, stray bullets often send the player running in the opposite direction. Two possible improvements are: auto scroll, and removing 1-hit-kills.

Another significant flaw is that your player is quite close to the direction of scrolling, making you vulnerable to new enemies that appear suddenly. This is especially a problem in Jackal. Perhaps this is due to the arcade heritage (cheap, sudden deaths means more quarters), or to the NES’s low resolution.

Heavy Barrel

Heavy Barrel

A late NES game, less well known but not for its quality, because it’s really awesome. Coming after seminal titles like the aforementioned Konami games and the influential Commando, Heavy Barrel benefited from the design lessons from those games. As a result this game sports many cool and powerful weapons, virtually all accessible in the first level. In fact the first level is a real tour-de-force demonstration of how awesome shooters can be.

  • Less than half a minute into the first level, you are accosted by two enemies: fixed machine gunners and fast-running soldiers. This sets up an interesting tactical challenge, as the machine gunners zone you out, whereas the fast runners can approach and threaten you. There are at least two ways to deal with this: quickly take out the machine gunners so you have space to evade the runners, or patiently take out the runners while staying out of the machine gunners’ zones.
  • All weapon pickups in the game (including grenades, which are cool in theory but overshadowed by the main weapons) have a fixed amount of ammo (think Metal Slug). It’s an effective system that discourages player spamming, and keeps them moving with the promise of more ammo.

Mercs

Mercs

This game is basically a more polished version of the earlier Commando game (and similar ones like Ikari Warriors and Gun.Smoke). As a result it’s pretty straightforward and repetitive: lots of enemies, lots of shooting. A few points are worth mentioning:

  • 3 player co-op. Shooters, especially multi-directional ones, are great for co-op because you can help each other out by shooting at enemies threatening the other players - that’s more ways to interact and co-op.
  • Lots of enemies use grenades. Grenades are interesting weapons - because they take time to land and explode, they give the player a bit of time to react thoughtfully, keeping them on the move and the pace of the game up.

90’s Innovation

Overhead shooters really blossomed in this era. Enabled by technical advancements and more discerning players, game makers looked for all kinds of ways to enhance shooters, taking ideas from other genres and games, as well as simply adding more polish to what’s become an established genre.

Meanwhile in PC land, shooters saw lots of innovation from diverse game makers. Not only were these shooters creative, many were made with loving care; games like C-Dogs and its ilk were made by people who grew up with and genuinely adored these games.

Smash TV

Smash TV

Perhaps the quintessential 8-directional twin-stick shooter; this enhanced remake of Robotron: 2084 distils everything great about overhead shooters into a no-nonsense, crazy-hectic arena shooter with a fantastically-absurd premise. Shoot endless hordes of enemies, get big powerups, fight ridiculous bosses, collect unending piles of cash. It’s simple and unsophisticated: the weapon selection is pretty standard, the enemies even more so - there are ones that run towards you, ones that run towards you faster, some that occasionally shoot you, others that run towards you in funny patterns.

Unfortunately the game suffers from a big flaw: uneven pacing. In between the ceaseless enemies and unpredictable powerups, it’s very hard to control difficulty and player experience. Sometimes normal levels with unfortunate powerup placement can be way more brutal than boss fights. This is a problem we’ll see again in a closely related game: Crimsonland. I think the game could really benefit from something like the AI Director; unfortunately this wouldn’t be developed until a decade later.

Tapan Kaikki + Assault Trooper

Tapan Kaikki Assault Trooper

These two games share more than a few things in common. They were both made by Finns, were released around the same time (1995, 1997), have tank controls, and are chock full of 90’s shooter goodies. Tapan Kaikki has some of the most elaborate gore effects of any game: enemies explode in gibs, which leave blood trails, which you can kick (and leave more blood trails), and did I mention you can get your feet covered and leave bloody footprints? Assault Trooper on the other hand is full of interactivity and cool ideas; everything’s destructible, even the trees and walls. Both are very interesting examples to study.

Both games are unfortunately hampered by their tank controls - that is, players mainly move forward and rotate via keyboard controls. Both have strafing functions but they are quite awkward to use. Like other games with tank controls, aiming, especially at far targets, is challenging. Navigating twisty passages is likewise difficult. Both games feature lots of tight, indoor areas which are bad with tank controls; I remember dying endlessly in Assault Trooper because my grenade didn’t go where I wanted it to, and bounced back from a corner. My opinion is that tank controls should be avoided in indoor, fast-paced shooters for these reasons.

But don’t let that colour your perception of these games; they are flawed gems that are seriously underappreciated.

Strike Series + Fire Fight

Strike Series Fire Fight

Compared to the last two games, these adapt well to tank controls. Recall that the two problems with tank controls are moving around in tight spaces and aiming at far targets. Both are about flying aircraft in wide-open spaces, rather than tight indoors. Both also solve the problem of low accuracy with interesting weapons and mechanics. In Strike’s case, there is a limited amount of auto-aim, which is cleverly explaned as having a “gunner”. In Fire Fight, there’s the cool “swarmer” weapon which fires a spread salvo of homing missiles.

Strike also has the interesting fuel mechanic, which is basically a draining health meter and you must scour the map for extra fuel to stay alive. It’s a good way of countering an otherwise slow-paced game, but this sort of resource mechanic has its problems too.

Crimsonland

Crimsonland

Coming after an era of gory shooters, Crimsonland plays like an unpretentious distillation of late-90’s game design sensibilities. It’s a twin-stick shooter with tons of everything: weapons, powerups, simple swarm enemies, an interesting perk system, and lots of blood decals. It’s great fun and uneven too, and it offers plenty of lessons:

  • Overhead shooters are fun! The huge range of weapons, explosions and sounds make shooting endless zombies satisfying, all the time.
  • There are so many weapons and they are unique. From the standard shotguns and machine guns, to rockets with explosions, homing weapons, piercing weapons, even ones that bounce around. Crimsonland proves that 2D shooters have the prettiest and most interesting weapons, because you can see and appreciate all the bullet patterns. Balance is very poor though.
  • The powerups, even though they are even more unbalanced than the weapons, are lots of fun, and are at the core of its gameplay. Once players have mastered the basic game, it becomes more about desperately negotiating overwhelming swarms of enemies to get to crucial, game-changing powerups, only to die inches from them. Critics say this makes the game luck-based, but there’s no denying its excitement.
  • Unfortunately, the game is severely lacking in content. Its huge range of weapons, powerups, perks and enemies serves as its content, via its quest mode, and while that’s lots of fun, after the whole game is unlocked there’s not much else to do. To me this proves that there’s only so much you can do with shooting things in an open arena; you really need content in the form of maps to create interesting spaces for unique gameplay.

Ultimately, I think Crimsonland is a great case-study on how less-is-more, at least for balance. There are so many weapons, powerups, perks and enemies that balance is simply impossible. Games that don’t rely on the number of weapons for content would do well with less, if they want to be balanced, for example. One type of game that thrives on variety however is roguelikes, and sadly Crimsonland missed this boat on becoming a game like Binding of Isaac or Nuclear Throne.

Modern Overhead Shooters

The core design of overhead shooters has been rather stagnant after the 90’s. There has been a lot of innovation at the fringes, like cinematic plots, but the basic mechanics are much the same. Titles like Alien Shooter, Shadowgrounds, and the recent Helldivers are all great, polished games, but their gameplay is often considered “retro”, and in many ways are less ambitious in their design as some of the creative entries from the 90’s.

Binding of Isaac + Nuclear Throne

Binding of Isaac Nuclear Throne

One exception is the games that marry overhead shooting action within a rogue-like framework. Exemplified by Binding of Isaac, but in this regard surpassed by Nuclear Throne, the marriage does a lot of justice to shooters - the myriad combinations of weapons and enemies typically seen in roguelikes makes these shooters more enjoyable than many traditional shooters, much as Crimsonland’s numerous weapons had.

This mixed-genre benefits the rogue-like aspect too; just as different weapons and enemies forces you to play the game differently, each run-through is a unique experience due to the items and powerups you happen to get.

The Future?

I believe we’ll continue to see a steady trickle of great overhead shooters, because the genre is simple unadulterated fun. But there’ll always be room for innovation, whether that’s taking ideas from other, even unrelated genres, but also revisiting old games that had neat ideas but didn’t explore them to their fullest, or for whatever reason weren’t influential.

The same could be said for any genre, really. We ought to look back on games from the past, because they have a lot of lessons to teach, both what to do and what not to do.

Jan 27, 2016 - How to Write a LAN Server

Comments

Recently I had to write a LAN game server/client for C-Dogs SDL. I was surprised at how little information was available, so I thought I might document my solution here.

The Problem: Which IP to connect to?

Most people who have made, or attempted to make, network-multiplayer games will know that there are two ways to get two game instances to connect to each other:

  1. Directly, by entering the IP address of the other game instance, or
  2. Using an online “server list”, which is basically a list of IP addresses constantly updated

Most “game server browsers” actually download lists from several known locations, a fancy way of doing no. 2. But no. 2 boils down to picking an IP from a list, which the game then uses to connect to directly anyway - no. 1. The point is: to connect two games, you need the IP address.

In ages past there were dial-up modems where you could connect using phone numbers, or IPX which is similar to IP but has different addresses. The protocols have changed but the problem is the same.

But how do games see each other on a LAN? Somehow many LAN-enabled games can scan and find servers running on a LAN, and avoid having to enter IP addresses manually. They also do this without having to access a remote server list.

Broadcast Address

Fortunately, the solution is provided by the network itself: in IP networks, you can send messages to the broadcast address (255.255.255.255) and the network will pass the message on to all hosts on the network, that is, the LAN. Servers that understand the broadcast message can then reply back, alerting the broadcaster to their presence and address.

This is very simple in concept but takes a surprising amount of code to implement; IP broadcasts are only available for connectionless, datagram sockets, and most game network services either use TCP or a connection-based scheme to make things easier, so chances are you can’t just shoehorn the broadcast functionality into your existing network handler. Therefore, the LAN “scanner” service will have to run on a different port, using UDP. The procedure is this:

  1. The game server itself runs on port A, but a “listen” socket runs on a fixed port B.
  2. The client scans for LAN servers by broadcasting a UDP packet on port B.
  3. Servers receiving the broadcast scan reply back, optionally including the information that a game server is running on port A.
  4. The client, upon receiving the reply, connects normally to the IP and on port A.

Full credit for this solution goes to Lee Salzman, of Sauerbraten and ENet.

To illustrate, I’ll provide a code walkthrough by implementing a basic chat service using C and ENet, an excellent cross-platform game networking library. But the same approach will work for your chosen language/framework, because we’re dealing with socket networking for the most part. A full working server/client code example is available here: https://github.com/cxong/ENetLANChatServer

The Server

The chat server will run the following:

  1. Bind a UDP port for listening to broadcast scans
  2. Bind another socket for the chat server itself
  3. In a loop, we’ll check for:
    • Scans: we’ll reply with the chat server port
    • Chat server events: as we’re a chat server, we’ll just resend the text message to all clients

First, the initialisation:

enet_initialize();

A common mistake is to forget to initialise ENet itself; you’ll run into mysterious errors with the other ENet functions if you forget. Of course, like a good programmer you should always check your error values, but I’m omitting those in this post for brevity.

Next, start the listening socket, on port 34567 (pick any port that is likely to be unused):

ENetSocket listen = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
// Allow the port to be reused by other applications - this means we can run several servers at once
enet_socket_set_option(listen, ENET_SOCKOPT_REUSEADDR, 1);
ENetAddress addr;
addr.host = ENET_HOST_ANY;
addr.port = 34567;
enet_socket_bind(listen, &listenaddr);

Now start the chat server itself (supporting up to 16 clients):

ENetAddress addr;
addr.host = ENET_HOST_ANY;
addr.port = ENET_PORT_ANY;
ENetHost *host = enet_host_create(&addr, 16, 2, 0, 0);

Finally, in a loop, check for scans and also perform chat server logic. First the listener: since we’re dealing with raw sockets, we’re using the old select()/recv() combo to do non-blocking I/O.

// The following code belongs in a function; we're using return to step out

// Use select to see if there is data to read
ENetSocketSet set;
ENET_SOCKETSET_EMPTY(set);
ENET_SOCKETSET_ADD(set, listen);
if (enet_socketset_select(listen, &set, NULL, 0) <= 0) return;

// Construct a data buffer (ENetBuffer) to receive the data
// Because we know exactly how big the scan packets will be (1 byte),
// we'll only set aside enough memory for that.
// If you want bigger payloads, adjust this to suit.
// If you want dynamic payloads, or you want to receive any payload just for the heck of it,
// use a suitably big buffer.
ENetAddress addr;
char buf;
ENetBuffer recvbuf;
recvbuf.data = &buf;
recvbuf.dataLength = 1;
if (enet_socket_receive(listen, &addr, &recvbuf, 1) <= 0) return;
// At this point we've received a packet; it would be a good idea to check its contents
// to make sure it's what we're looking for (like a magic number) and not random data,
// but all we're doing is sending a small reply so let's just do it, hooray for laziness!
// Reply to scanner client with the port of the server host
// We know the client address from the enet_socket_receive function
ENetBuffer replybuf;
replybuf.data = &host->address.port;
replybuf.dataLength = sizeof host->address.port;
enet_socket_send(listen, &addr, &replybuf, 1);

Then the chat server itself; as we’re going for maximum laziness simplicity, we’ll just broadcast the client’s data packet right back at all clients. Since there’s no validation or bounds checking, in a production environment this would be a gaping security hole :smile:

ENetEvent event;
if (enet_host_service(host, &event, 0) > 0)
{
	// Whenever a client connects or disconnects, broadcast a message
	// Whenever a client says something, broadcast it including
	// which client it was from
	char buf[256];
	switch (event.type)
	{
		case ENET_EVENT_TYPE_CONNECT:
			send_string(host, "New client connected!");
			break;
		case ENET_EVENT_TYPE_RECEIVE:
			sprintf(buf, "Client says: %s", event.packet->data);
			send_string(server.host, buf);
			break;
		case ENET_EVENT_TYPE_DISCONNECT:
			send_string(host, "Client disconnected :(");
			break;
		default:
			break;
	}
}

Don’t forget the helper function, send_string():

void send_string(ENetHost *host, char *s)
{
    // +1 for the null-terminator
	ENetPacket *packet = enet_packet_create(s, strlen(s) + 1, ENET_PACKET_FLAG_RELIABLE);
	enet_host_broadcast(host, 0, packet);
}

Finally, don’t forget to tear everything down at the end:

enet_socket_shutdown(listen, ENET_SOCKET_SHUTDOWN_READ_WRITE);
enet_socket_destroy(listen);
enet_host_destroy(host);
enet_deinitialize();

The Client

The client is conceptually simple:

  1. Send UDP broadcast scans to look for servers
  2. Once we’ve received a reply, connect to that IP as a normal ENetHost
  3. In a loop:
    • Get keys typed by the user; when they press enter send what they typed
    • Check for messages from the server; if there’s any print them out

It turns out that getting keyboard input and performing network I/O together is pretty hard; we’ll use rlutil, a sweet header file that contains some utility functions for console programs. We’ll only be using nb_getch(), to get keyboard hits in a non-blocking manner.

First, as always, initialisation:

enet_initialize();

Scan for the server, on port 34567 (where the server is listening)…

ENetSocket scanner = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
// We need to set a socket option in order to send to the broadcast address
enet_socket_set_option(scanner, ENET_SOCKOPT_BROADCAST, 1);
ENetAddress addr;
addr.host = ENET_HOST_BROADCAST;
addr.port = 34567;
// Send a dummy payload; you can make your own (larger) payload
// but make sure to update the server code if you do
char data = 42;
ENetBuffer sendbuf;
sendbuf.data = &data;
sendbuf.dataLength = 1;
enet_socket_send(scanner, &addr, &sendbuf, 1);

…and wait for the reply:

// Note that enet_socket_receive is blocking;
// for a non-blocking version use enet_socketset_select to check before receiving
ENetAddress addr;
enet_uint16 server_port;
ENetBuffer recvbuf;
recvbuf.data = &server_port;
recvbuf.dataLength = sizeof server_port;
enet_socket_receive(scanner, &addr, &recvbuf, 1);
// If the message is correct, we should have received sizeof(enet_uint16) worth of data
// Once again, error checking would be nice here, but omitted for brevity
addr.port = server_port;
// Now addr holds the exact host/port to connect to

// But first, shut down the scanner because we're done with it
enet_socket_shutdown(scanner, ENET_SOCKET_SHUTDOWN_READ_WRITE);
enet_socket_destroy(scanner);

Now that we have the server’s address (addr), start our chat client host:

ENetHost *host = enet_host_create(NULL, 1, 2, 0, 0);
ENetPeer *peer = enet_host_connect(host, &addr, 2, 0);
// Wait for 5 seconds for connection to succeed
ENetEvent event;
if (enet_host_service(host, &event, 5000) > 0 && event.type == ENET_EVENT_TYPE_CONNECT)
{
	printf("Connected\n");
}

The chat client is now connected; in a loop, check for the user’s typed input, collecting the keys and sending them off on enter/return:

// Keep a keyboard buffer outside the main loop
char buf[256];

// ... inside the loop

int k = nb_getch();
if (k == KEY_ENTER || k == '\r')
{
	// If we have something to say, say it to the server
	if (strlen(buf) > 0)
	{
		send_string(peer, buf);
	}
	memset(buf, 0, sizeof buf);
	printf("\n");
}
else if (k > 0 && strlen(buf) < 255)
{
	// Hold on to our message until we press enter
	buf[strlen(buf)] = (char)k;
	// Print the typed character out; otherwise the user doesn't know what was typed
	printf("%c", k);
}

The send_string() helper function is different for the client because we’re only sending to the server, but otherwise it’s exactly the same:

void send_string(ENetPeer *peer, char *s)
{
	ENetPacket *packet = enet_packet_create(s, strlen(s) + 1, ENET_PACKET_FLAG_RELIABLE);
	enet_peer_send(peer, 0, packet);
}

We also need to check for server messages, and print whatever we get:

ENetEvent event;
if (enet_host_service(host, &event, 0) > 0 && event.type == ENET_EVENT_TYPE_RECEIVE)
{
	printf("%s\n", event.packet->data);
}

Don’t forget to tear down everything at the end:

enet_peer_disconnect_now(peer, 0);
enet_host_destroy(host);
enet_deinitialize();

The End

Phew! Hope that all makes sense. As I’ve said, the concept is simple but the code is verbose because we’re pretty deep into socket code, with all the select()s, recv()s and socket options, thinly wrapped in ENet which makes it slightly less verbose. There’s a working server/client project available here, with the proper error checking mess for you to get started. Good luck!

May 4, 2015 - Ammo is a Resource

Comments

The latest release of C-Dogs SDL is ostensibly about the addition of deathmatch mode, and although it was the original goal and most notable feature, development took twice as long as previous releases, and largely for one reason: ammo. Before this, C-Dogs didn’t have ammo, or in other words, C-Dogs had infinite ammo. How could ammo be that hard to implement; surely it’s just an extra counter? I though the same, but I soon realised this:

Ammo is a resource.

And that leads to a heap of things related to resource management. The player must now contend with many more decisions:

  • Which gun should I use? (some guns have rarer ammo)
  • How should I engage enemies? (close-up saves ammo but is riskier; farther is safer but wastes ammo)
  • Should I run away altogether and save ammo?
  • Should I explore, and gamble on finding more ammo or having to use it up on new enemies?

And as a designer, you now have to contend with:

  • Making sure the player is not too starved, or over-supplied
  • Resetting the player with the right amount of ammo at key points
  • Being careful not to railroad the player into a few play styles due to ammo supply

Of course there are great rewards for implementing ammo. It adds a layer of depth via the decision-making. It gives players an incentive to use different guns, as overusing their favourites will make them run out of ammo. But it’s also an entire game mechanic, and for one that’s so old and common, it’s surprisingly hard to get right.

doom

Games like these are too easy with lots of ammo, too hard without. Ammo placement can make or break a map.

Cyberdogs had ammo!

Fortunately, ammo in games is older than dirt, so there’s plenty of examples of how ammo can work, when it doesn’t, and what to watch out for. Most C-Dogs fans know that the game is actually a sequel to the very similar Cyberdogs, and it had ammo! But the two also play differently. Cyberdogs is slow, tense and methodical; enemies are very dangerous up close, pop out unexpectedly and you had to make your shots count. C-Dogs is sometimes like this too, but it is also fast and frantic, owing to new mechanics like sliding, open spaces, and of course - no ammo. Many players prefer Cyberdogs over C-Dogs for this reason.

cyberdogs armoury

How does Cyberdogs solve the ammo resource problem, that is, make sure the player’s getting enough ammo, and not too much? Armoury. In between levels, the player can spend cash to buy guns and ammo, cash that is earned from completing objectives and killing enemies in the previous level. In a way it is an elegant solution, by fobbing off the problem to the player, who is now responsible for how much ammo to buy. This also acts as a meta-game: too little ammo is dangerous, but too much and you don’t have enough cash to buy the goodies like extra lives and bigger guns. This mechanic is quite widespread too, from shops in RPGs to loadout screens in mech games.

But herein lies the problem: we’re attempting to balance one mechanic by piling on more mechanics! Wasn’t the point to make sure the player gets the right amount of ammo? Now we’ve added a store, buying/selling, cash and maybe more. Wouldn’t this just make things more complex and hard to balance? Let’s look at the ammo resource cycle here:

  • Use ammo to kill enemies
  • Kills earn cash
  • Use cash to buy ammo

Can’t we just cut out the middleman - cash - and keep things simple?

Sources and Sinks

Not quite. The cycle misses some important details: you can earn more money by completing more objectives, and you can also spend money on (expensive) guns. These are crucial to keeping the Cyberdogs ammo/cash system balanced. In other words, sources make sure the economy is never low on cash since players can always go out of their way to obtain more, and sinks make sure there’s never an overabundance of cash, by having big expensive items that the player can save up towards.

Once again, we can find examples in other genres, notably in RPGs, where there are plenty of sources and sinks to keep the cash economy healthy. If the player is short on cash, she can always farm for more gold, or explore dungeons more thoroughly to find expensive loot to sell. Many RPGs also have inventory limitations, which limits the amount of loot you can carry back to a store and sell; if the player is low on cash then she could make repeated trips to sell all the loot. On the other hand, if the player has too much cash, she could upgrade her equipment, which tends to be quite expensive. Most RPGs also feature magical or rare equipment that is obscenely expensive but provide marginal improvements over regular equipment. This Gamasutra article on Diablo’s item system shows how all these aspects work together to create a sophisticated economy. In summary, most of your gear was from loot, shop equipment was extremely expensive, but still worthwhile since the loot was random and often made the player miss out on a particular kind of equipment.

So it is important to have multiple sources and sinks in your economy, and also to make these alternates less desirable, otherwise they distract from the main game. For example, grinding, farming and shuttling loot are reliable money-makers but they are often tedious and less fun than fighting enemies or completing objectives.

ff6 shop

No matter how much you grind, you will barely be able to afford everything here. Inflation really messes with savings.

There’s another lesson from RPGs too, and that is to grow prices superlinearly. That is, for bigger and better items, increase their price much more than usual. Ever wonder why the wooden sword costs 10G, the iron sword 100G, and the silver sword 1000G, even though each one is only a little better than the last? This is to stop players from hoarding cash and making later segments of the game too easy because they can buy the best gear in one go. It’s a neat trick to prevent snowballing and make balancing much easier, by making those upgrades reliable sinks.

A Faustian Bargain

It seems we are stuck between two imperfect options. If we want a simple, ammo-only system, it is prone to shortages or surpluses, one that must be fixed by limiting player freedom, railroading them into a golden path. It is no accident that modern shooters rarely have ammo supply problems, but are also linear experiences. Alternately, if we want player freedom, non-linear gameplay, exploration, then we are forced to contend with the supply problems, which are neatly balanced with an economy. In return, we end up with an entire system which both the developers and players must manage.

But is this necessarily a bad thing? Players often enjoy managing resources as a meta-game, and there’s no denying the satisfaction of earning and spending these virtual trinkets, the clickety-clack sound when picking things up, watching virtual counters increment. When deciding on game features, it is important not to avoid complexity for its own sake, and to keep the greater goal in mind: making fun or enjoyable games. So the take-home message is this: be careful about adding features, because you may end up adding an entire system and meta-game!