• Building A Multiplayer Game With Three.Js + WebSockets

    We'll be using these tools/libraries to build a multiplayer game.

    • Three.js. A framework built on top of WebGL that makes it easier to create graphics in the browswer.
    • Cannon.js. A physics engine that pairs well with Three.js.
    • WS - A lightweight WebSocket client for node and the browser (Alternatives: Socket.io).
    • RxJs - a library for using observables (event streams like keydown events).

    A Quick-And-Dirty Intro to Three.js

    Three.js is a framework built around WebGL that makes it easy to create graphics in the browser. It uses the canvas element.

    Every three.js project has these basic elements:

    • scene
    • camera
    • renderer

    Scene

    This is the most global three.js namespace. When objects are added to the scene, they can be found in scene.children. It's initialized with:

    Objects are added to the scene with:

    Camera

    The camera is the vantage point the scene is viewed from.

    Here's how a camera is created:

    Renderer

    In three.js we create a new WebGLRenderer and then append the renderer.domElement to the document. The renderer.domElement is just an HTML canvas element.

    In addition to appending a canvas element to the document, you can use a pre-existing canvas element.

    The three.js renderer has a method called render(). After the renderer is created we call this method:

    requestAnimationFrame requests that the browser call the render() function to update an animation before the next repaint. A reference to the render function is passed to requestAnimationFrame as an argument.

    Adding Objects to the Scene

    After creating a scene, camera and renderer, you'll want to create some objects (meshes) and add them to the scene. A mesh is a composite of a material and a geometry. Properties like texture belong to the material.

    Terrain

    To create some basic terrain, we'll use THREE.PlaneGeometry(width, height, segments, segments) and then adjust the elevation of the vertices in the geometry.

    The first two arguments are the width and height of the plane, respectively. The latter two are the number of segments.

    The snippet below creates a 20 x 20 grid of tiles.

    We create a Three.js mesh object by combining geometry and material objects.

    Right now we've made a flat plane. For terrain, we need to adjust height of the plane at each vertex. You can find a reference to the mesh's geometry in mesh.geometry.

    So mesh.geometry.vertices is an array of vertices that comprise the plane. To create terrain we can loop through the vertices and adjust the altitude.

    Oh, and I forgot to mention that mesh.geometry.vertices is an array of Vectors. Vectors are special Three.js objects that store x, y, z coordinates. But they're also so much more; they come with some useful methods like clone(), add(), copy() and so forth. In the above code snippet, setZ() does what you'd expect: it sets the value of the z coordinate.

    Link to gist

    Mutating State in Three.js

    Most operations in Three.js mutate state. E.g., consider the following code:

    What does console.log(vec) output at the end of the loop? The answer is {x: 0, y: 0, z: 3}. Most operations mutate state. Three.js objects have a clone() method that returns a copy. You can use clone to avoid mutating the original.

    Moving A Hero With the Keyboard

    One of the first challenges of creating some games is figuring out how to move the hero.

    We can translate a sprite along the surface of the terrain by listening for onKeyDown and onKeyUp events and then adjusting the sprite's position.

    Here's a naive approach:

    • requestAnimationFrame does exactly what you would expect: it adds to the top of the stack.
    • We pass a reference to the enclosing function render() to requestAnimationFrame; render() will be a called again at a later time (a few milliseconds later).

    Tapping Into Keyboard Event Streams With Observables

    An observable is just a stream of events. Here are some concrete examples:

    • A sequence of clicks
    • A sequence of mouse movements
    • Requests to an API

    These are all just streams of events - aka - observables. RxJs.

    Observables are useful for onkeydown events because they can help simplify the control flow and pair down on the business logic.

    We might be tempted to write bulky switch statements for onkeydown and onkeyup events. But with RxJs we can manipulate the stream of keyboard events and then subscribe to changes.

    Doesn't look too bad right?

    In the above the snippet, we:

    1. Create an observable from the document.onkeydown event.
    2. Map the observable to the key
    3. Subscribe to changes

    So far, this doesn't look very different from the plain Javascript approach.

    Link to Gist

    Here's another approach using RxJs that uses a store to maintain application state.

    Earlier we mapped event streams to value, e.g., map(e => e.key). With the stateful approach we map the stream of events to a state changing function.

    So far we can move a hero around on a flat plane using a keyboard. Our hero only stays on the surface because we only adjust position on the x and y axises. But terrain has height. There are a few ways to tackle this problem.

    Getting Z

    For each new x and y position our character we could get the magnitude of the z-dimension (height) of our terrain.