Thursday, October 2, 2014

Monetization Post-Mortem

Lost Crypts is fast-paced coop dungeon crawling web game. Think "Gauntlet" meets "Rogue" in your browser. A lot of players have found it fun, but a solid fun game doesn't necessarily equal $$$. The following is a post-mortem of the monetization for the game looking three reasons why Lost Crypts monetized poorly as a web game.

Problem 1: Non-ideal gameplay for free-to-play monetization.

When I first started work on the game, monetization was not the most important factor in the design-- I had wanted to make a Gauntlet-like web game for some time. I had a somewhat naive idea that players having fun leads to players spending money. But I had played very few games that used microtransactions and not spent money on any. During development I continued to research monetization options and adjusted the game design accordingly.
One thing I learned from several sources was that the game play model I was going with was likely to face an uphill battle in free to play monetization. Successfully monetized webgames tend to be non-arcade, social and revolve around a large number of short sessions. One monetization expert I talked with had some great suggestions, but warned me that, "roguelike, shooter, and real-time party multiplayer are honestly all warning signs for us that a game is going to have trouble monetizing."
Another source, a Playnomics report on social, web and mobile games, had a large amount of game data and gave similar warning signs for an action-oriented game: Arcade game players have a lifetime average of 1.4 sessions and 23.9 minutes of total game time. Non-arcade game players average 17.9 sessions and 516.9 minutes of game time.

Problem 2: Weak retention

Most of the revenue for free to play games comes from a few highly engaged players over a long period of time. A great free to play game that entertains players for an hour or two will have much lower revenue than a good game that entertains players for tens of hours over a period of months. I'm generalizing here, but this presentation from Kongregate is fairly convincing:
Look at slides 14 and 16, and note how much revenue comes from long engagement-- more than half of revenue comes from players who play more than 500+ sessions and 90% comes from players who play at least 50 sessions.
Shooter/arcade style games have difficulty retaining players for those durations and Lost Crypts has not been an exception-- most players, even those who play 10+ minutes in their first session, never return for a second game. Furthermore, even highly engaged players who spend an hour or more in the game frequently seem to exhaust themselves on the game after only a handful of sessions. Lost Crypts' most dedicated player has played fewer than 50 games, despite having exhausted most of the game's content in the 17+ hours he has logged in the dungeons.
I have continuously incorporated features into Lost Crypts to improve player retention, but it currently seems unlikely that some incremental changes will push a significant number of players into the many hours of play where many successful free-to-play games generate most of their revenue.

Problem 3: Players satisfied with the free experience

Players have the reasonable expectation with free to play games that they can play your game for free. And not just a demo where players must pay to unlock the rest of the game. Players expect they should be able to play all of the game's primary content without spending a dime. F2P games get players to open their wallets in several ways: by selling items that make the game easier, by limiting the rate of free play, or by selling additional content that isn't necessary for the core game experience.
Lost Crypts uses Soulstones as its premium currency. Every player starts with 3 and they recharge over time, but only to a maximum of 3. Initially, Soulstones had just one use: resurrecting when you died so you could keep playing from where you were. After the introduction of magic items and some stronger persistence mechanics, they gained a second use: letting you protect items from being lost if you died. Now there are additional uses: starting at deeper levels (you can start at half your deepest unlocked level for free) and creating private game instances (this is the only feature that requires players to make a purchase).
Players are using Soulstones, but the vast majority of players are getting lots of playtime using just the free ones. The most dedicated player managed to consume most of the games content and even was exploring depths of the dungeon I had not thought reachable during early beta using the free stones.
I have since adjusted game play to make progression slower with the free stones, but currently it is still too easy to reach mid to late game content in a few days play and there is a limit to how much the difficulty can be dialed up before it becomes either frustrating or perceived as a "pay to win" game.
So if the game is fun, can't you make money on the small percentage of players who do pay?
This is essentially the case with almost all free to play games, as discussed in Problem 1. However there are some financial realities:
  1. I can't work for free forever. I started the game from a comfortable position of having saved enough money over the years that I can afford to take some time off to do something that isn't necessarily profitable, particularly something that makes an impressive portfolio piece. So I can't continue to add features and make iterations indefinitely in hopes that one of them will make the game massively profitable.
  2. The game has ongoing costs.
Here's the back of the envelope math using ballpark figures:
The technical design makes it relatively easy (although not automatic) to scale the number of servers in response to demand using Amazon ec2 instances. I estimate that a medium ec2 instance can support approximately 50-100 concurrent players. Let's say the number is 75.
Players do not distribute themselves evenly throughout the day, so if I want the server to support the maximum, data shows that the maximum is between 2.5 and 3 times the average number of users. So a medium ec2 instance can support an average of 25 users, about 18,000 user hours per month.
A medium ec2 instance is 12 cents an hour, or $90 a month, plus bandwidth. That means that if the game makes $0.005 per user hour, it is guaranteed to lose money under ideal circumstances. If the game is making that much or less, I could improve profitability by preventing people from playing it.
Reality is less forgiving than the ideal scenario: instances will often be under utilized, plus there will be additional costs related to the game excluding development. So let's say that $0.01 per user hour is the cost per player for hosting.
The good news is that the game currently steps over this conspicuously low bar. The bad news is it still ignores many additional costs that are either fixed or do not scale linearly with the number of players and making enough money for me to live on.


Tuesday, July 16, 2013

Map Generation

My original idea for Lost Crypts was to have all the levels be designed by hand. I had a number of good reasons for this:

  • Hand-made levels allow for greater control and more interesting design;
  • Less development work;
  • Easier to test.
But there are significant downsides. The main one is that they have a very high "design time" to "play time" ratio. Once you've played a level, playing the exact same level is less rewarding-- there's no sense of mystery of what's around the next corner.

After Alpha 5, I decided to change direction and implement a procedural map generation system I had been toying with. Instead of having 100% designed levels, levels would be assembled using pre-fab encounter areas, connected together like puzzle pieces. 

In this post, I'll outline in detail how the map generation system works as of Alpha 6.

Step 1 : BSP the Map


I got the idea of using a BSP Tree from this article on rogue-like map generation. You start with a rectangular region and recursively divide it into smaller regions. By varying the division size and adding a probability of early termination, you end up with a set of irregular rectangles that perfectly cover the map.

I had experimented with a similar system a while ago and knew that there were two features of this system I didn't like:
  1. A lot of very skinny rooms. 
  2. Rooms are connected in a tree graph. At first glance, the maps look like good dungeon maps. But well-designed levels have some method to them-- players come in at one point, are looking for the exit and probably want to visit as much of the map as possible with minimal backtracking. With a BSP tree players will spend a lot of their time backtracking along paths they've already covered.
The first issue is easy to eliminate-- only split rooms along their longest axis.

The second issue is problematic if you use the BSP node connections to define the room connections. So I decided to throw out the tree once the map was divided and fill out the map in a more directional pattern.

Step 2: Choose the Rooms


First, pick a node/region at random and call this the start. This is where players will first spawn when they enter the level.

Now choose a room (I'm using the term room to describe any prefab area that can be placed in a node). The generator uses the following criteria to decide whether or not a room can be placed, and how likely the room is to be chose from the set of possible rooms:
  • The room must fit in the node. 
  • Some rooms might have minimum or maximum dungeon levels. For example you probably don't want Cthulhu's Lair showing up on level 1.
  • If this is not the first room, then the room must have an entrance on the side facing the previous room.
  • We may set a minimum and maximum number of exits that the room must have, depending on how complex of a level we want. The first room must have one exit. The room editor allows the designer to specify which sides of the room can be used as an entrance, exit or both. For example if there's a door that can only be opened by stepping on a pressure plate in the room, that door should only be used as an exit, i.e., leading away from the start.
  • Each room has a preset "frequency" that allows some designs to show up more often than others. Generally, the more distinctive or special the room, the lower the frequency. A square room with a couple random monsters and a random treasure can appear frequently. A distinctive octagon room with a treasure chest on one wall surrounded by traps should be fairly rare.
  • A room's frequency is scaled up relative to the square root of its area. This tends to selector for rooms that are a better fit for their node, without this small rooms would tend to dominate the map because they could be placed in any node, but large rooms can only be placed in large nodes.
Recursively apply Step 2 for each node neighbor that is a valid exit direction.

A room with three Trogdors and a lunchbox.
Above is an example room created in the map editor. It contains three random type 1 monsters, one type 1 treasure and a torch. All four cardinal directions can be used as entrances or exits (this is set when the designer saves the room). 

Step 3: Connect the Dots and Clean-up

(A recent addition to the generator is a "loop" creation step, where there's a small probability that neighboring nodes will be joined together. This allows for random short cuts to appear in the map, creating the possibility of circular loops.)

Once the rooms are placed, the generator connects the rooms together with corridors. Each doorway that connects to another room is extended to the edge of the node. If the entrance/exit corridors don't line up, a "zig" is added to connect them.

The generator infers the size of doorways based on the outermost tiles of a room. In the example shown above, the room can connect in any direction with a corridor 2 tiles wide. After rooms are connected, the generator removes unused doorways, so this room would become a 4x4 square.

Finally, map is "hulled" with walls. Any empty tile that borders a non-wall tile has a wall placed in it to keep entities from escaping into the Nothing, aka NullPointerException land.

Example of a finished map. The generator can output an ASCII "roguelike" depiction for debugging, showing the nodes, some of which were unused in this map. "*" represents player start locations, "%" represents the exit stairs, and "?" represents some other feature.

There are quite a few inefficiencies in my implementation of the above system, but even on a moderately powered machine a large map can be generated in 10-30 milliseconds. Given the infrequency of map generation, I've decided to leave off any optimizations for now.

Entity Selection

Rooms and maps are defined by a set of entity prototypes. Entity prototypes are like entities in a component entity system. Each prototype has a collection of components and a numeric id. Unless otherwise specified by the map, all entities of the same prototype are identical except for position and rotation. This allows game data to be stored compactly and sent to clients quickly. Some entities require extra customization data, for example a list of targets to respond to triggers, but this is a rare exception and most entities can be sent over the wire in 16 bytes.

For increased dungeon variety, there's an EntitySelector component. An EntitySelector component is sort of like a treasure type or wandering monster table from Dungeons & Dragons-- when the game first starts, it randomly creates an entity of a new prototype and then disappears. Entity selector tables consist of lists of entities for each level and their assorted frequency. For example the Level 1 monster table contains "None" with a frequency 500, "Goblin" with a frequency of 400 and "Javelineer" with a frequency of 100. So 50% of the time, the selector disappears and spawns nothing, 40% of the time it spawns a goblin, and 10% of the time is spawns a Javelineer.

Since this all happens at the start of the map, if a room contains several entitty selectors of various types, it may look like a randomly generated room with creatures and treasure appropriate to the level, even though the actual room data doesn't change.

Random, but not too Random

The generation of the map involves a lot of "randomness" to ensure that each map is unique. But it would nice if we could replicate a map after the fact to debug issues with a particular configuration. So whenever I say "random" I really mean "pseudo-random." The generator uses a Mersenne Twister implementation of Random, which is a pretty fast pseudo-random number generator. Given a particular seed number, it will always produce the same random-looking sequence of numbers.

Addendum: Wait, that's too Random


After implementing the generator, I discovered a pernicious bug: it wouldn't always generate the same map from the same seed. It usually would, but if I ran the algorithm 3 or 4 times from the same seed, it would sometimes produce a different map.

This kind of "heisenbug" is the worst kind to track down, complicated by the fact that the generator is supposed to produce random looking maps--  a slight variation at any step may produce drastically different results.

Because computers are pretty deterministic, non-deterministic behavior is the exception rather than the norm. Since the generator is single threaded, a race condition seemed unlikely. And since Mersenne Twister is well tested, I also ruled out a bug in the random number generation.

Eventually I tracked down the problem. After partitioning the map into nodes using BSP, I calculated each node's neighbors so I could later choose connections. I stored neighbors in a set, specifically Java's HashSet. HashSet does not guarantee order for iterating over its members, so the neighbors were coming back in different orders depending on how HashSet indexed them.



Monday, May 20, 2013

Game Forum

I want to have a place where players can discuss the game with other players and directly submit feedback to me, the developer.

So I started researching forum software. I spent a day trying out MyBB and got so far as to install it locally using the lab.wx3.com database server.

A major requirement was the ability to automatically register players in the forum when they register for the game. Unfortunately, MyBB doesn't play well with CodeIgniter, the framework I use for the user authentication gateway-- it appears that MyBB's reliance on global variables runs into trouble if CodeIgniter uses a variable of the same name.

Now I'm moving on to the next forum candidate, SMF, which has integration hooks, suggesting it was developed to support the integration features I'm looking for.