Robinson – v0.1

Robinson is an open world survival roguelike. It focuses on exploration and crafting and doesn’t take itself too seriously. Fight creatures, build your camp, and survive, but most importantly – get off the damn island!

gameplay gif

You can track development on GitHub.

Try the v0.1 release.

Robinson-v0.1-2fe182f.zip

(24.2MB)

Status

Most of the game’s systems are in place.
Crafting
Weapons/wielding
Will to Live/hunger/thirst
Starting Kit
Food harvesting and plant identification
Raft-building
Fishing
Sleeping
Fire-building, beds, and shelters
Scoring

Changes from the 2015.09.10-22.43.39-99a8fbf release

Updated black background color to be slightly lighter.

Robinson – 2015.09.17

Robinson is an open world survival roguelike. It focuses on exploration and crafting and doesn’t take itself too seriously. Fight creatures, build your camp, and survive, but most importantly – get off the damn island!

gameplay gif

You can track development on GitHub.

Try the pre-release version.

Robinson-2015.09.17-19.16.01-7225330.zip

(25.4MB)

This version includes many updates and improvements. It is a release candidate for v0.1 version of the game.

Updates

Balanced early-game monsters.
Added new default font (Boxy).
Added ability to specify font by path.
Traveling and standing in water reduces will to live by a small amount.
Decrease item utility when killing a monster.
More rocks drop when harvesting.
Will to live decreases faster when outside at night without a campfire.
Player gets slightly more hungrier and thirstier during the game.
Ranged combat is more effective.
Fire and lava now self-illuminates at night.
Updated scoring formula.

Fixes

Fix for hunger and thirst increasing while crafting.
Fix procedural text generation at end of game.
Fix for truncated start text.
Fix description mode.
Fix searching.
Fix number of days calculation at end of game.
Allow spaces in player name.
Fix main menu image quality.
Fix end of game menu formating.

gameplay gif

gameplay gif

gameplay gif

gameplay gif

gameplay gif

Robinson – 2015.09.04

Robinson is an open world survival roguelike. It focuses on exploration and crafting and doesn’t take itself too seriously. Fight creatures, build your camp, and survive, but most importantly – get off the damn island!

gameplay gif

You can track development on GitHub.

Try the pre-release version.

Robinson-2015.09.04-08.29.55-49758f7.zip

(27.7MB)

Updates

Added quick-action menu (space).
Added pickup all shortcut (space) in pickup menu.
Add titles and descriptions to player abilities.

Fixes

Combat takes into account weapons again. It was temporarily broken.
Fix endgame message for pacifism.
Fix attack stats collection.

Robinson Map Generation

Robinson takes place on an island and it’s the player’s goal to get off the island. To do so they must explore. The setting should reinforce the game’s theme, but there are a few other constraints to consider. First I’ll describe the the goals of the generation algorithm, and then we’ll see how well the generation algorithm accomplishes them.

Goals

  • The island must have finite bounds.

Well, if the island is infinite, how can the player escape from it? There should be a space comprising the island’s landmass and it should be surrounded by water. Pretty straightforward. We’ll have to find a way to determine what points are water and what points are land.

  • The island should be procedurally generated because the player is going to die a lot and will quickly get bored if the content is the same.

Robinson is a roguelike with a lot of dying, so of course the island should be unique each playthrough. That doesn’t mean there can’t be some premade content, but the vast majority of the island should be unique. We will probably want to use a seedable random source, but more on that later.

  • The island should be interesting because it’s the backdrop for tactical combat and strategic planning.

The island should straddle the line between being completely random vs being completely predictable. The player should be able to make educated guesses about what they will encounter, but the details should elude the player. We’ll want to designate areas for water, sand, jungle, forest, mountains, and others. They should roughly conform to player’s expectations so they can make decisions based on incomplete information.

Great! These are relatively separate goals that sound reasonable. Let’s start building an island generator. We’ll take top down approach and start with the big ideas and then zoom in and fill in the details.

Building an Island

Let’s start with the outline of an island. We’ll designate the origin’s coordinates (0, 0) as the center of the island. The first step will be to create a height-map based on the euclidean distance from the origin.

Then let’s perturb the height-map based on signed vector noise. Signed vector noise defines a 2-dimensional vector for every point on the map, and offset operates like a translation function. But instead of translating the entire map by (x, y), it operates on a point-by-point basis and takes signed vector noise as an input to determine how much each point is translated. Offsetting an input based on noise gives a more interesting result rather than adding noise to an input.

Excellent! We have a hight-map to start sampling from. We can just define a function (and a helper) to sample from any (x, y) coordinate and get back a biome type.

(defn sample-height
  [n x y]
  ((rprism/coerce
             (rprism/offset
               (rprism/v+
                 (rprism/v*
                   15.0
                   (rprism/scale
                     15.0
                     (rprism/vnoise n)))
                 (rprism/v+
                   (rprism/v*
                     75.0
                     (rprism/scale
                       75.0
                       (rprism/vnoise n)))
                   (rprism/v*
                     150.0
                     (rprism/scale
                       150.0
                       (rprism/vnoise n)))))
               (rprism/scale
                 333.0
                 (rprism/radius)))) x y))

(defn sample-island
  [n x y]
  (let [h (sample-height n x y)]
    (cond
      ;; interior biome is just dirt for now
      (> 0.55  h)
         :dirt
      ;; shore
      (> 0.6  c)
        :sand
      ;; surf
      (> 0.68 c)
        :surf
      ;; else ocean
      :else
        :ocean)))

island image

The interior is just dirt, so let’s make that much more interesting. The naive way to generate biomes from noise is to specify a surjective function from a scalar value (generated from noise) to a biome type. It’s also not very interesting because it places biomes inside of each other like layers of an onion. Discounting that as an option, the next easy solution is to define two noise sources and use their values to determine a biome type. We will be comparing them a lot later on, so let’s store a variable that computes (> c1 c2) just once.

c1 ((rprism/coerce
      (rprism/offset [0.5 0.5]
                     (rprism/scale 22
                                   (rprism/snoise n)))) x y)
c2 ((rprism/coerce
      (rprism/offset [-110.5 -640.5]
                     (rprism/scale 26
                                   (rprism/snoise n)))) x y)
cgt (> (Math/abs c1) (Math/abs c2))

Then, instead of just returning dirt, let’s change the cond clause to this:

      ;; interior biomes
      (> 0.55  h)
        (cond
          (and (pos? c1) (pos? c2) cgt)
          :jungle
          (and (pos? c1) (pos? c2))
          :heavy-forest
          (and (pos? c1) (neg? c2) cgt)
          :light-forest
          (and (pos? c1) (neg? c2))
          :bamboo-grove
          (and (neg? c1) (pos? c2) cgt)
          :meadow
          (and (neg? c1) (pos? c2))
          :rocky
          (and (neg? c1) (neg? c2) cgt)
          :swamp
          :else
          :dirt)

island image

We can use the sample-island function to get a biome type for any (x, y) coordinate. The end result is that some biome types tend to spawn near others, and their placements feel natural. The code that calls it looks like this.

(vec
  (for [y (range y (+ y height))]
   (vec
     (for [x (range x (+ x width))]
       (let [biome     (sample-island n x y)
             t         (sample-tree n x y)
             cell-type (case biome
                         :ocean         {:type :water}
                         :surf          (if (< t 0.1)
                                          {:type :surf}
                                          (rr/rand-nth [
                                            {:type :surf}
                                            {:type :surf}
                                            {:type :surf}
                                            {:type :rocky-shore}]))
...

There are many more clauses, one for each biome type, but the idea is the same for each. Notice that there is another function sample-tree that behaves like sample-island. It’s just another noise function that will determine if a tree should be places at a given (x, y) coordinate. Using a consistent noise function for this tends to make trees in different biomes blend naturally into each other where they otherwise wouldn’t if a die was rolled for each cell instead.

Robinson includes a few more map features: a volcano at the center of the island and a lava flow extending from the center to the ocean. The implementation is straight forward. When the island parameters (seed, player starting position, etc.) are being generated, a volcano position and points along a random path from the volcano are stored in the game state. Whenever a portion of the island is generated, the cell generation function checks if the cell is should be part of the volcano or is near enough to a lava point to be considered part of the lava flow. If so, then the cell type is altered. If not, the cell type is based off of the biome.

Considerations

Robinson does not perform single-pass whole-world generation. The map is divided into 80×25 chunks and chunks are generated on demand and loaded/unloaded from disk and memory as the player moves around the map. The implication is that large-scale features must be generated during island initialization and the parameters stored in the gamestate so that when chunks are created, the chunk building function can refer to the parameters. If chunks are generated without taking this into account rivers wouldn’t line up and chunk boundaries would have discontinuities.

What’s next?

Two things that Robinson will include in the future are rivers and dungeons. Rivers will operate very similarly to how lava works now with the exception that rivers will be able to be forded. Dungeon types and locations will be generated with the other island parameters and be placed on the map like lava, and rivers.