Skip to main content
The cover image for "Ruben's Virtual World Project"

Ruben's Virtual World Project

Sidebar

Ruben's Virtual World Project was a multiplayer top-down shooter with base-building elements that was in development for five years, from 2015 to 2020. It was a hybrid between a top-down shooter and a base-builder, allowing the player to switch between Hero and Architect modes at any time. Whilst I started the project to learn about multiplayer networking at a low level - including latency mitigation, reliability, and prevention of cheating - it quickly turned into a project to make a game and game engine.

Ruben’s Virtual World Project was a multiplayer top-down shooter with base-building elements that was in development for five years, from 2015 to 2020. It was a hybrid between a top-down shooter and a base-builder, allowing the player to switch between Hero and Architect modes at any time.

Whilst I started the project to learn about multiplayer networking at a low level - including latency mitigation, reliability, and prevention of cheating - it quickly turned into a project to make a game and game engine.

Gameplay #

In Hero mode, the player directly controls the actions of their character. They can explore freely, recruit, and fight. If the player’s character dies, then they would need to make a new character and join the same world again. Eventually, I planned on having a mission system requiring players to leave the base to achieve certain narrative goals.

Hero mode, showing multiplayer
Hero mode, showing multiplayer

In Architect mode, the player isn’t tied to any particular character and is able to issue orders. RVWP was heavily inspired by the colony simulation elements of RimWorld; I had been playing it quite a lot in 2015 and it remains my favourite game to this day. In RVWP’s Architect mode, you can place blueprints for walls and queue jobs like cooking food. NPCs will build things and do jobs for you.

Architect mode
Architect mode

While I never got around to developing the narrative or world of the game, the idea was for the game to be about managing a base of rebels hiding from the authorities, and doing missions to further their cause. I thought that a group of magic users seeking freedom from prosecution might be a good setting for this.

Early development #

I wrote an article about the first two years of development here: RVWP: Multiplayer Topdown Sandbox Game in C++.

By the mid-2017, I had a game with the following features:

  • Infinite map
  • Multiplayer
  • Tile placement
  • Multiple map z-levels (can go up and down stairs)
  • Doors and stairs
  • Player chat
  • Game controller support
  • Inventory and dual-wielding
  • Weapons (guns and melee)
  • Main menu

Tech dive #

World, lighting, and rendering #

RVWP’s world was a 3D near-infinite voxel world rendered in 2D. It had voxel lighting based on a 3D flood fill algorithm outlined in a Seed of Andromeda blog post (no longer available).

The world is 3D drawn in 2D
The world is 3D drawn in 2D

Each tile was lit based on the direction of the light source. This was done using a normal map for each tile. I wrote an article about how RVWP renders the world.

Diffuse
Diffuse
Normals
Normals
Voxel lighting
Voxel lighting
Result
Result

Lua scripting #

RVWP had support for Lua scripting. All the content in the world, including furniture, walls, and moving things, was defined in Lua scripts. Whilst I wasn’t aiming to make a fully customisable engine, I wanted to keep as much gameplay in Lua to make content creation and modding super easy.

The Lua scripting API supported hot reloading. This means that it is possible to reload all mods at runtime, without restarting the game. This is incredibly convenient for development but does come with some drawbacks - you need to be much more careful about how you store state. RVWP mods need to be written in a way that they do not store any important data themselves, they instead use RVWP APIs to store and persist data in the engine.

I learned a lot from the mistakes of Luanti’s API here. One example is that the entity API is much cleaner, with a clear separation between players and objects.

print(player:get_player_name())
print(player:get_pos())
print(player:get_name())
local entity = player:get_entity()
print(entity:get_pos())
Whilst longer, separating players and entities makes the API much cleaner.

Another example is a much better tile timer API:

rvwp.register_tile("stove", {
    on_cook = function(pos, layer, tile)
        rvwp.chat_send_all("Stove cooked something!")

        local timer = rvwp.get_timer(pos, layer, "cook")
        assert(timer:start(5))
    end,
})

--- somewhere else
local function start(pos)
    local timer = rvwp.get_timer(pos, rvwp.layers.tile, "cook")
    assert(timer:start(5))
end

You can read RVWP’s Lua API documentation online.

Entities and NPCs #

RVWP allowed Lua scripts to define custom entities - moving things in the world. Entities are fully dynamic and can be changed at runtime. Non-Player Characters were implemented in Lua, using engine APIs like pathfinding and tile searching.

The only difference between a player entity and a Lua-created entity is which code controls the entity. Both are instances of the Entity class; the former is controlled by PlayerEntityController and the latter by Lua code. Using a single class like this rather than inheritance made networking much simpler. Other clients don’t even know whether an entity is a player or Lua-controlled!

NPC AI was implemented using Behavior Trees. They have the benefit of avoiding complicated state transition logic, allowing any state to respond correctly to threats or other interruptions. The code for this in RVWP is custom, but the trees themselves are edited using a GUI tool called owl-bt. I wrote an entire article about how the NPCs follow orders.

A behaviour tree
A behaviour tree

Immediate-mode debug annotations #

When working on implementing Non-Player Characters, I wanted a tool to make it easier to see what is going on. I introduced an immediate-mode API to add debug annotations to the API.

Immediate mode means that the annotations are added by calling a function every tick rather than needing to make and persist objects for each line and label.

if rvwp.debug then
	for i = self.path_i + 1, #self.path do
		rvwp.debug:draw_line(self.path[i - 1], self.path[i], "#999")
	end
end

I used this tool to show planned paths and the internal AI state.

Immediate mode debug UI
Immediate mode debug UI

Cancellation #

In 2021, I became disillusioned with using C++ and SFML/SDL/OpenGL to make games. Working at such a low level requires you to take on the task of implementing a game engine as well, which is a huge disadvantage when you want to make a game. I decided to cancel RVWP to focus on making games faster by using engines such as Godot. I wrote an article about this decision titled Make games not engines: why I’ll be using Godot engine

The following ideas were never implemented:

  • Semi-realistic combat, with knowledge of cover and hit stats.
  • Procedurally generated missions to further your cause.
  • Recruitment and loyalty.
  • Traffic, pedestrians, and vehicles.
  • Surveillance, AI police investigations, law and order.

Conclusion #

Whilst I am disappointed that RVWP never made it to be a game, it was an excellent project for learning technology. It improved my knowledge of C++, graphics, networking, and game engine development in general.

No builds are available, but you can compile yourself from RVWP’s source code.

rubenwardy's profile picture, the letter R

Hi, I'm Andrew Ward. I'm a software developer, an open source maintainer, and a graduate from the University of Bristol. I’m a core developer for Luanti, an open source voxel game engine.

Comments

Leave comment

Shown publicly next to your comment. Leave blank to show as "Anonymous".
Optional, to notify you if rubenwardy replies. Not shown publicly.
Max 1800 characters. You may use plain text, HTML, or Markdown.