While learning Node.js I wanted to create a browser based multiplayer game using a procedurally generated landscape. This post describes the steps I took to integrate three example projects I found on the web.
The Node.js project is based on Getting Started on Heroku. I wanted to be sure I could create and modify a Postgres database to keep track of users and rooms. Next I made routes and handlers for socket.io to let players emit JSON strings with state and control updates every 100ms.
The landscape is generated using pseudorandom number generators with 8 seed values provided by the server. Several sine waves at different wavelengths are combined to create a raster height map and a color map sized 512×512. I added a finish line graphic from a png to the color map and some ramps to both.Next createGeometryFromMap(), adapted from Three.js Cookbook, converts the raster images into a mesh. Each player gets the same seed values and an identical landscape of half a million polygons is generated.
Putting the Smooth On
The world altitude data is 512x512x256 which is very blocky. The triangle mesh masks the steps and smooth movement over the grid is achieved by calculating a weighted average altitude of nearest data points. Opponents’ updates come in every 100ms, which is about as long as the echo on a rockabilly record, and would normally look jerky. To smooth the network latency the simulation runs every 16ms for each player in parallel. On average control changes have ~50ms latency which results in some janking.
For now the physics are incomplete, there is no reconciling when latency causes a meaningful discrepancy in the outcome, and simulation steps are not added when frames drop. Another enhancement would be to simulate a few frames ahead for incoming updates to account for network latency.
Start Your Engines!
The WebAudio effects are adapted from the NoiseHack Monotron. This demo already has portamento which I would need to smoothly control the motor sound. I refactored the constructor to dispense instances that could be controlled independently. I added noise and panning but performance started to suffer. Once I moved the noise generator out of the instances and reduced the buffer size I was able to keep a good frame rate with lots of voices.
In the testing process I built a demo: WebAudio API Generative Polyphonic Synth
Do you want to play a game?
The QR code is how players join each other in the same game. The design is for interaction with people in the same physical location. Registration, login and traditional matchmaking are omitted. Scan or click the QR code to play the game now.
The responsive UI is calculated from a square 6×6 grid with controls pinned to the corners. Touch and keyboard control are meant to be self explanatory.
The most interesting problem to solve is the drifting. Say the airboat is going 30kph Northwest but has turned to the East and is accelerating at 5kph per second for one second. How do you calculate the resulting speed and heading? Use the Cosine and Sine functions times speed to get the x and y components of both the starting motion and acceleration. Add x to x and y to y to sum the magnitudes of both components. Use the Pythagorean Theorem to get the speed and the arctangent function for the heading.
Note: heading in geometry functions is measured in radians, of which there are ~6.28 in a circle, or 2 times pi. 0 radians is pointing right. In a graph up and to the right are positive so y is reversed relative to a screen coordinate. When you get to the 3d world as I set it up y and z are switched, meaning x is West and East, z is North and South, and y is up and down.
You can get a copy of this project from my repo: https://github.com/tschubring/herokunode
Contact me at LinkedIn if you have any questions or if you want help building something cool.
Updated Landscape and Vehicle
Small craters on the moon have an depth/width ratio of .2 and are partially below the original surface level. Placement is again from a pseudorandom number generator so all players in the room are on an identical board.
My height map starts flat at #808080. I used a transparent radial gradient to composite multiple layers of craters. Where a smaller crater is on the slope of a larger crater the geometries are smoothly combined. The raster image of the moon surface gets a less pronounced highlight gradient so the detail isn’t obscured.