Ben Hallett

Game developer & web programmer

Projects

This is a brief, incomplete list of projects that I have worked on in my spare time, mostly for my own edification and amusement. The source code for most of them is visible in my various GitHub repositories.

World Generator

World generator is a Unity project I made partly to learn about how the Unity Terrain asset works, but also to experiment with procedural terrain generation techniques. It randomly generates continents, surrounded by ocean and divided up into biomes. Here are some examples:

Controls

WASD or Arrow keys
Move the camera around.
Middle mouse button
While the button is held down, rotate the camera with the mouse, like in an FPS.
Scroll wheel
Zoom in/out.
Esc
Quit.

The terrain is randomly generated when you first load the application, but if you don't like what you see or become bored of exploring one world, you can click the "Regenerate" button in the top left to generate a new world.

You can click anywhere on the terrain to mark a location and bring up some information about the region / biome that the location is in, including its name, area, rainfall and temperature. More information about what those parameters mean and how they're generated is described in the Method section, below.

The "Minimap" below the Regenerate button cycles between three modes:

Heightmap
Displays a graphical representation of the heightmap that was applied to the terrain. Higher elevations are depicted by whiter pixels, while lower areas are darker. North is at the top of the image.
Rainmap
Displays a graphical representation of the rainmap that was applied to the terrain. Wetter areas are depicted by whiter pixels, while drier areas are darker. North is at the top of the image.
Biome map
Displays a graphical representation of the Rainfall/Temperature graph used to determine biome mix values for a given terrain point. This won't change between generated worlds, but it is generated at runtime from an array of values hardcoded into BiomeCalculator.cs , making it useful for debugging and explaining why certain pixels end up with certain Biome assignments.

Method

Heightmap generation

The first thing the generator does is to create a random heightmap that dictates the shape of the terrain. While figuring out how to approach this, I created an AbstractValueMap class that let me swap in various possible approaches for generating heightmaps. I experimented with a few different options:

  • Perlin noise, which produced undulating landscapes that were both unrealistic and too uniform.
  • Perlin noise multiplied by values in a flat-topped cone centered on the middle of the map, in an attempt to force the undulations to conform to the shape I wanted to generate: a central continent surrounded by sea.
  • Diamond square noise, which produced much better results, giving rugged mountains and jagged coastlines when I added a plane representing "sea level".
  • Diamond-Square noise constrained by some preset values for the first few steps of the algorithm which force the border of the map to be at altitude 0 and randomly generate heights of 9 equidistant points in the middle of the map. This finally produced the rough continent shape that I wanted with enough internal variation to still be interesting. The drawback to this method is that some combinations of the randomly-generated 9 values can be very obvious - particularly when they produce peaks - revealing the underlying grid structure. Unfortunately, due to the way that the Diamond-Square algorithm works, there's no good way to peturb the locations of these preset values and still produce consistent terrain.
Sample heightmap
A sample heightmap produced by the generator. In this example, three of the preset values have formed obviously-aligned peaks.

Biome generation

The next step is to assign biomes like "Taiga" and "Desert" to the map, ideally in a consistent and broadly speaking realistic way. My implementation of biome generation is inspired by the ideas described in this article on Gamasutra , although my own implementation differs slightly. It is still, however, fundamentally based on Whittaker's biome type classification scheme and uses a combination of rainfall and temperature values to determine what biome a given area of the map should have.

Determining rainfall

Rainfall across the map is determined with a map of values produced by Perlin noise. In this scenario, the smooth, rounded blob shapes produced by Perlin noise actually work well, producing contiguous wet and dry areas that translate into biomes of a good variety of sizes.

Determining temperature

The temperature value of a map tile is itself based on two values: its height above sea level and its latitude (effectively, its y and z co-ordinates). The precise calculation (visible here) is a weighted average of the two values after some tweaking to try to bring the coldest and hottest latitudes nearer the middle of the map so they have a better chance of being on dry land. This means that the North of the map should be colder and thus have snowier biomes than the jungled, tropical South, but that mountaintops should also be colder than land on the coast.

Assembling biomes

Each map tile is then assigned biome weights, based on where its temperature and rainfall values lie on this chart:

Biome graph

That chart is actually generated at runtime from values specified in this 10*10 array (hence the blocky shape of the biome definitions), with values that fall between those specified in the array determined by bilinear interpolation to give smooth transitions between neighbouring biomes. This means that a tile might fall at the intersection between biomes and be (say) part Taiga, part Snow and part Plains, with varying weights.

There is also a special biome which doesn't feature on this chart: Ocean. That gets applied on top of the temperature/latitude biomes based solely on the tile's height above sea level, meaning that anything underwater is entirely "Ocean" and that the coasts blend (rapidly) into the land biomes as the terrain rises above the waves.

The full calculation for the biome weights for a tile is visible here.

Each map tile is then assigned a "parent" biome, or region, by using a flood fill algorithm to group together neighbouring tiles with the same top-weighted biome type (my implementation of this can be viewed here). These regions are then given a name, based on their biome type and how big they are: a large Forest region will be called a "Forest", but a smaller one might be a "Copse" or a "Grove". The actual names given to regions are assembled randomly by combining vowels, consonant-vowel pairs and a predefined list of other pronounceable bigrams like "sh" and "nd".

Putting it all together

Once all the biome information has been generated, it must actually be applied to the Unity Terrain object for the user to see. The terrain is painted with a collection of textures, one for each Biome type: mud for swamps, grass for plains, seabed for oceans and so on. These textures are blended using an alphamap (generated here) that describes with what opacity each biome's texture should appear at a given point. This is where preserving the weight values assigned to all biomes for every map tile is useful: we can say that a given tile on the edge of several biomes should appear to be fading between, say, grass, mud and leaves.

There's one additional texture that gets applied to the alphamap, as well as all the biome textures: a rocky "cliff" texture. That gets blended in on top of the relevant biome textures for tiles located on particularly steep areas of the map, which helps to give the impression of rocky mountainsides and coastal cliffs, even if the way that the heightmap works means that one can never have truly vertical cliffsides.

Finally, some trees get scattered onto the map. Unity's Terrain object handles all the rendering and LODing for the trees: all the generator needs to provide is a list of tree locations, types and scales. To build that (the code for which is here), it follows the frankly fairly inelegant method of just picking 50,000 randomly chosen locations on the map and, if it happens to be in a Forest, Taiga or Jungle biome, placing a tree there of the appropriate type (deciduous, pine or palm, respectively). Trees are scaled based on the biome weight, so trees at the edge of a forest should appear to be smaller than those in the middle.

Attribution

Pretty much all art assets used by World Generator - the terrain textures, the trees, the water shader, the skybox - are from free packages on the Unity Asset Store.

The location icon (the one that appears when you click on something) came from the ever-dependable game-icons.net.

Thoughts / Criticism

In the current implementation, the entire generation process takes place on the main thread, meaning that the entire game "freezes" until it completes (this usually takes less than 5 seconds on a typical PC). Obviously, that's not an ideal user experience (and would be unacceptable in a fully-produced game), so it'd be nice to arrange the code to perform the generation process on a separate thread, only writing the completed data to the terrain object after completion.

As I mentioned at the start, this project was at least partly intended to help me learn the features of the Unity Terrain object. It also served to teach me its limitations. In particular, it's based on the assumption of a 2-dimensional heightmap with vertices arranged in a regular grid. While it's therefore not feasible as a modification or extension of this project, in a future landscape generator, I'd like to experiment with more sophisticated models for terrain shapes, like the method of using Voronoi regions to place vertices discussed here, which would allow more irregular biome shapes with fewer vertices (and thus better performance). Still better (though still more complex) would be to use a voxel-based approach like Minecraft or No Man's Sky, allowing for caves, overhangs and bridges.

I'm particularly unhappy with the place names generated by the current implementation. If it were to be used in an actual game, I'd want much more flavourful place names - particularly if I were to add towns and cities to the map. One approach would be to assemble them from a predefined grammar using a system like Rant (or Phable!) to create names that would fit in a typically tropey fantasy story: Mount Shardfrost, The Greyleaf Forest, Chokemist Marsh. Alternatively, for names that are less hackneyed (and arguably less accessible), but still consistent, I could use a system like the one described here to generate a fictional "language" that is then used to name all the biomes and settlements.

Fuel me once

Fuel Me Once is a game of triangulation*, wonky driving and oil extraction that I made in Unity in 72 hours for Ludum Dare 29, which ran from April 25th-28th 2014. It was both my first (non-tutorial) Unity project and my first Ludum Dare entry.

The aim of the game is to use detector beacons to locate oil deposits beneath the desert sands, so that you can plant a drill atop them and deliver the oil they extracts back to your central base to sell it.

* - Well. Strictly speaking, it's trilateration, not triangulation.

Controls

An Xbox 360 controller will probably improve your experience with Fuel Me Once, but it's not required.

Left Stick
Drive the truck with absolute controls (like a twin stick shooter)
WASD or Arrow keys
Drive the truck with relative controls (like the old GTAs)
Right Stick or Mouse
Position targeting reticule
A Button or Q or LMB
Launch detector ball
B Button or Spacebar
Trigger scan pulse from all detectors
X Button or E
Launch drill
Left Trigger
Brakes, or reverse if you're already stopped
Back or R
Restart
Start or Esc
Quit

Mechanics

Detectors will emit a "ping" if their scan pulse passes over an oil deposit, and the pulse will leave a green ring behind indicating the range of the deposit from the detector. By combining the information from multiple detectors, you can pinpoint exactly where to target your drill.

A drill that is dropped correctly onto an oil deposit will start to produce oil! Carry oil in your spare inventory slots back to the shop to sell it for cash. The price of oil generally increases over time, and drops slightly every time you sell some of your stocks, so pick your moment to get the best price.

You start with 3 detectors, 1 drill and $50. Detectors can be bought in packs of 3 for $5, but another drill will set you back $100. If you miss with your first drill, you'll not be able to afford another, so make sure you're certain before picking your first drill site.

Make as much cash as you can before the time runs out!

Attribution

The inventory icons (ie. the only thing in the game that's obviously not programmer art) came from game-icons.net

Most sound effects were just noise produced by the ever-useful sfxr, though the cash register chime came from freesound.org.

Thoughts / Criticism

In general, players have praised the concept of Fuel Me Once, although the process of using three or more detectors to pinpoint the location of oil deposits turned out to be much less intuitive than I expected.

The most obvious problem in Fuel Me Once was the controls. I decided early on to make the player character a vehicle, to save me having to animate any of my hastily-produced SketchUp models. I then got bogged down in the physics almost immediately, trying to find some values for Unity's Wheel collider that felt as fun to drive as the buggies in Renegade Ops. Although the final result is (mostly) fine after a little practice, I never quite found the sweet spot.

It's possible, however, that the reason people complain about the controls actually has more to do with the layout of the central platform, to which the mechanics demand that you return to shop for gear and sell your spoils. While driving up a ramp at full speed to leap into the air over the platform and sell all your oil is a genuinely satisfying interaction, navigating your vehicle with its large turning circle to the precariously-placed shop items in the corner of the platform is surprisingly fiddly and frustrating, especially when approaching the platform from the same side as the item you want.

During the jam, I did add a self-righting feature which, if you manage to flip your truck onto its back or side, should after a three second delay unceremoniously right your vehicle and fling it into the air. Even so, far too many playthroughs end with the truck stuck between the corners of the platform or bottomed-out on the lip of a ramp.

Phable

Phable is my attempt to port Kate Compton's Javascript procedural story-generation library Tracery into PHP. It takes a JSON file that specifies a "grammar" containing all the sentence fragments that are used to generate an entire story and generates random "traces" (paths) through that grammar. The result is a story (or a paragraph or a sentence) that's different every time.

I've written a few demonstrations of Phable in action:

  • Otherspace scanner [ grammar ] - A scanner that surveys another, uglier plane of existence congruent to our own, and reports the results. This demo uses Geolocation to set the seed used by the random story generation process, meaning that you'll always get the same results from the same location, although locally occurring events may slightly alter over time...
  • The Next Big Thing [ grammar ] - Generates humorous pitches for new freemium videogames, complete with lists of emotions that the design is expected to evoke.
  • International super-spy codephrase generator [ grammar ] - A tool for Cold-War-era spies to generate secure codephrases to authenticate their first contact with a friendly agent in hostile territory without alerting nearby civilians that anything unusual is going on. This was a tool I built for a (now-defunct) narrative game project that would require two players to "pair" their accounts in order to play through the story together. The intent was to generate one of these phrases for each player, which they could then give to their counterpart to enter on the website to pair their accounts. The grammar already provides a fairly high (55,013,376 possibilities) level of information entropy, although it might need a few more additions in order to guarantee picking a unique passphrase for every player if the game were extremely successful.
  • Carmack Tweet generator [ grammar ] - A completely accurate simulation of the Twitter posts of programming uber-genius John Carmack.
  • Lord name generator [ grammar ] - Another aborted feature, this was a prototype for a system where new players joining an online game would not initially be prompted to set a name for their character, instead being randomly assigned a unique lore-friendly name, allowing them to skip straight to the fun part of actually playing the game rather than trying to think up a cool name.
  • Macguffin generator [ grammar ] - Who needs complex writing and story structure? Certainly not MMORPG players.