Openplanet is a containerized network-accessible procedurally generated 3D world.
The purpose of this project was to learn how to build a multiplayer procedurally generated 3D world. The result is a constallation of microservices tied together using docker-compose that together generate such a browser-based, network-accessible world. The project is currently deployed at https://www.openplanet.com but may also be run locally in development mode, as described below. This readme includes setup instructions, an overview of the architecture, and things to experiment with in the codebase.
- Install Docker Desktop.
- Clone the code.
$ git clone [email protected]:oppenheimj/openplanet-public.git
- In
websockets/
andwebpage/
folders, copy the.env_example
file and rename.env
. - Build and launch using docker-compose.
$ docker-compose build $ docker-compose up
- Browse to http://localhost:8080.
Each of the four microservices are fully contained in the four top-level folders, nginx/
, skins/
, webpage/
, and websockets/
. What follows is a brief description of each microservice and then a description of overall code flow.
Nginx is responsible for routing incoming traffic to the proper microservice. The production environment uses HTTPS and WSS, enabled by a TLS certificate from Let's Encrypt. There are separate nginx.{dev, prod}.conf
files and secure protocols aren't used when running the code locally, for ease of development. The nginx.prod.conf
file is excluded from version control for security.
This is the first microservice hit by the client's browser. This is a simple Node.js Express API with one endpoint, which returns the bundle of html and javascript. It is bundled using webpack. Terrain is efficiently loaded using multiple Web Workers.
The skin server is another simple Express API used for fetching mesh data. It contains a subdirectory called /skins
, which is where various .obj
files live.
This is the primary game server and has two responsibilities. First it maintains websocket connections with clients and handles receiving and forwarding look and position vector updates to other clients. Second, handles terrain generation and forwarding of terrain chunk data to clients. Terrain is generated procedurally using two dimensional fractal brownian motion, by many threads.
- The user points their browser to http://localhost:8080.
- Nginx forwards the request to the webpage microservice, which return a bundle of html and javacript to the browser.
- The client begins executing the browser code, which makes requests to the skins server for various skins and to the websockets server to establish a connection.
As the player moves around in the world, their position and look vector updates are sent to the websockets server, which forwards those updates to other clients. Terrain chunks are generated by the websockets server and sent to the client as needed. The server maintains a cache of all terrain chunks that have ever been generated and keeps track of which clients have received which terrain chunks. The client also caches terrain chunks it has received, but only renders those chunks in the immediate area. So a terrain chunk is only generated once, and only sent to a particular client once.
- Parameters for the FBM can be modified in
/websockets/server/chunk.go
- Constants
DIM
,RES
, andNRENDER
must agree between/websockets/server/terrain.go
andwebpage/src/TerrainManager.js
. This is tech debt.
Shaders are located in webpage/src/shaders.js
.
- Add
.obj
file toskins/skins/
- Add new endpoint to
skins/src/api.js
- Add skin inside file
webpage/src/main.js
and functionloadSkins()
. - Create new file inside
webpage/src/{SkinName}.js
at model it after one of the others, likeCow.js
. - Modify the
otherPlayer.setGeometry()
call insidewebpage/src/main.js
to use the new skin.
What follows are examples of neat parameter combinations to modify the terrain.
fbm2D(xPos/4096, zPos/4096, 0.95)
fbm2D(xPos/16384, zPos/16384, 0.98)