Learn to Code via Tutorials on Repl.it

← Back to all posts
🎮 3D Games with BabylonJS: A Starter Guide
h
eankeen (821)

🎮 3D Games with BabylonJS: A Starter Guide

Babylon is a JavaScript framework enabling developers to create 3D graphics with ease. Popular games such as Shell Shockers use this framework to create 3D games. It's shinier than ever, now that Version 4.0 just released!

I'll show you how to use Babylon to create a simple 3D environment. We'll go over creating an environment, adding objects to the environment, adding gravity, and adding collisions.

Setup

First, create an HTML,CSS,JS repl

Then, modify your index.html file. We're going to be adding the BablyonJS script tag, and our main.js file.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <script src="https://cdn.babylonjs.com/babylon.js"></script>
  <title>Babylon FPS</title>
</head>
<body>
  <canvas id="canvas"></canvas>
  <script src="main.js" type="module"></script>
</body>
</html>

You may have noticed thetype="module" attribute on the main.js script tag. That means we're importing main.js as a JavaScript module. This means that we can use the import syntax to import other .js files inside of main.js. I like doing this because it makes the index.html file more readable. (you can learn more about JS Modules (ESM) here.

When you start the app, it should serve an empty page. Let's add the basic BabylonJS stuff now! 😄

Creating a Babylon Scene

Make sure you add the following to your main.js. I'll explain nit below

import { createGameScene } from "./gameScene.js";

window.addEventListener("DOMContentLoaded", start);

function start() {
  let canvas = document.getElementById("canvas");
  let engine = new BABYLON.Engine(canvas, true);

  let scene = createGameScene(canvas, engine);
  let sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {}, scene);
  
  engine.runRenderLoop(function() {
    scene.render();
  });

  window.addEventListener("resize", function() {
  	engine.resize()
  });
};
  • Inside the start, function, we're creating a BABYLON engine. The engine 'translates' the BabylonJS API to canvas painting. For example, when you create a sphere with BABYLON.MeshBuilder.CreateSphere("sphere", {}, scene);, it is responsible for actually drawing the sphere on the screen.
  • A scene contains all of our scene objects. Think of it as a room - it can contain lights, cameras, 3D objects and more
  • engine.runRenderLoop is a function that runs continuously. In this case, we want Babylon to continuously render the scene (scene.render())

You may notice that we're using the createGameScene function, and we're importing it from ./gameScene.js. So, create a gameScene.js file. Like the name says, we're going to be creating the game scene in this file. This is where we will be creating our lights, cameras, and our meshes (3D objects).

let createGameScene = function(canvas, engine) {
  // Create scene
  let scene = new BABYLON.Scene(engine);

  // Create camera
  let camera = new BABYLON.UniversalCamera("Camera", new BABYLON.Vector3(2, 2, 2), scene);
  camera.attachControl(canvas, true);
  camera.setTarget(BABYLON.Vector3.Zero());

  // Create lights
  let light = new BABYLON.HemisphericLight("myLight", new BABYLON.Vector3(1, 1, 0), scene);

  return scene;
};

export {
  createGameScene
};
  • First, we're creating a regular BABYLON scene. Whatever you put inside this scene will get rendered by the engine.
  • Then, we create a new UniversalCamera camera. BabylonJS automatically sets up your camera so you can look around with your mouse or move around with the arrow keys. Thecamera.attachControl()lets us interact with the canvas using our mouse. If we did not add that, then Babylon would not know when we would be moving our mouse.

If you're wondering what the export { createGameScene } is all about, it's a part of the ES6 Module syntax, also known as ECMAScript Modules (ESM). We don't have time to go over it in this post, but you can read about it here. In a nutshell, we're creating a reusable function that can be imported into any other file.

Anyways, we're getting results! :D But unfortunately, the canvas is not styled properly

Styling the Canvas

Let's create a style.css to fix the canvas styling

* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}

#canvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  touch-action: none;
}

Be sure to add <link rel="stylesheet" href="style.css"> to your index.html

And presto! Now the canvas fills up the entire screen!

View the code in this this repl

Loading Screen

Games usually have a loading screen. This improves the user experience since the user sees a loading bar rather than a blank page. Let's create one!

Inside of loadingScreen.js, add

let showLoadingScreen = function(canvas, engine) {
  let defaultLoadingScreen = new BABYLON.DefaultLoadingScreen(canvas, "Please Wait", "black");

  engine.loadingScreen = defaultLoadingScreen;
  engine.displayLoadingUI();
};

let hideLoadingScreen = function(engine) {
  engine.hideLoadingUI();
};

export {
  showLoadingScreen,
  hideLoadingScreen
}
  • The showLoadingScreen function creates a default loading screen. The words "Please Wait" is shown on a black background with a loading spinner.
  • The hideLoadingScreen function simply hides the loading screen

Learn more about loading screens and custom loading screens on the Babylon docs

The next step is to call showLoadingScreen and hideLoadingScreen from main.js. Firstly, import the functions from loadingScreen.js

import { showLoadingScreen, hideLoadingScreen } from "./loadingScreen.js";

Now we have to show and hide the loading screen at the right times. Let's use showLoadingScreen() right after we create the Babylon engine.

let canvas = document.getElementById("canvas");
let engine = new BABYLON.Engine(canvas, true);

showLoadingScreen(canvas, engine);

...

We can't forget to hide the loading screen when everything has been loaded! I'm adding the code right after I create my sphere. Note that this will not hide the loading screen after you create the sphere. Babylon will automatically know when to hide the loading the screen.

...

let sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {}, scene);

scene.afterRender = function() {
    hideLoadingScreen(engine)
};

...

Now your loading screen should work! It will look something like this

View the code in this repl

Making the Camera Move

Although we are able to move the sphere with the mouse, we still can't move the camera with the 'WASD' keys. Let's add some extra controls to the camera!

Inside of my gameScene.js, I added:

camera.keysUp.push(87);
camera.keysDown.push(83);
camera.keysLeft.push(65);
camera.keysRight.push(68);
  • camera.keysUp is an array that contain key codes. (for example, 87 is a keycode for pressing 'W'). We're adding 87 to the array, which means the camera will move 'up' when 'W' is pressed

Other Camera Properties

You can also mess around with other properties of the camera! You can change the speed, inertia, or the field of view (FOV). You can view the whole list of properties on the Babylon documentation.

These are the values of fov, speed, and inertia I'm using. You can change them whenever you want!

camera.speed = 2;
camera.fov = 0.8;
camera.inertia = 0;

Now it works! Below, I set the fov to 0.3.

View the code in this repl

Adding a Pointer Lock

Moving around is cool and all, but isn't it bothersome that we have to hold 'left click' if we want to look around? To fix this, we can use the Pointer Lock API. Think of it like this: when we click on the canvas, the mouse is 'locked' in the canvas. When this happens, moving your mouse doesn't move the cursor, but instead directly interacts with the canvas element.

I created a pointerLock.js file and added the following code

let createPointerLock = function(scene) {
  let canvas = scene.getEngine().getRenderingCanvas();
  canvas.addEventListener("click", event => {
    canvas.requestPointerLock = canvas.requestPointerLock || canvas.msRequestPointerLock || canvas.mozRequestPointerLock || canvas.webkitRequestPointerLock;
    if(canvas.requestPointerLock) {
      canvas.requestPointerLock();
    }
  }, false);
};

export {
  createPointerLock
}

You might be curious why we are trying to access requestPointerLock, msRequestPointerLock, mozRequestPointerLock, and webkitRequestPointerLock. This is because browsers have slightly different implementations and it's not completely standardized yet.

Inside of main.js, be sure to actually import the createPointerLock function and use it. I called the method right after I created the scene.

import { createPointerLock } from "./pointerLock.js"

...

let scene = createGameScene(canvas, engine);
createPointerLock(scene);

Adding Some Ground

Players can't really move around if there is no ground. Let's create one!

Babylon's MeshBuilder lets us do this really easily. Near the end of your gameScene.js file, create the plane:

let plane = BABYLON.MeshBuilder.CreatePlane("ground", {
    height: 20,
    width: 20
  }, scene)
plane.rotate(BABYLON.Axis.X, Math.PI / 2, BABYLON.Space.WORLD);
plane.position.y = -2;
  • I used plane.rotate() to rotate the plane so it is completely flat
  • plane.position.y decreases the altitude of the plane so it is below the sphere

Gravity

Creating gravity is pretty straightforward. To add gravity, we have to do three things.

  1. Choose a Physics engine
  2. Enable gravity on the scene
  3. Enable gravity on the sphere (you can add it to other objects as well!)

Choose a Physics Engine

CannonJS is a physics engine well supported by Babylon. In our case, the physics engine will calculate where the sphere falls and how the sphere will interact with the plane. We want the sphere to bounce.

So, let's add our physics engine: CannonJS

<script src="https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.min.js"></script>

Enable gravity on the scene

To enable gravity in the scene, we just have to set the gravity property to a vector. For the purposes of this tutorial, think of a vector as an arrow pointing in a direction. In this case, the arrow represents the force of gravity. The force of gravity is pointing down, at -0.4 units.

After creating gravity, then we enable CannonJS. I put the following code right after I created the scene variable (in gameScene.js)

scene.gravity = new BABYLON.Vector3(0, -.4, 0);
scene.enablePhysics(scene.gravity, new BABYLON.CannonJSPlugin());

// Enable regular collisions as well
scene.collisionsEnabled = true;
scene.workerCollisions = true;

Enable gravity on the sphere

After enabling gravity on the sphere, it doesn't look like it does anything. This is because the gravity "doesn't have anything to push". Indeed, we want gravity to make the sphere fall, but we have to tell CannonJS that. We're going to tell CannonJS that we want the sphere to fall by creating a physics imposter from it.

A physics imposter is a simplified model of an object, or mesh. For example, we are simplifying the shape of our sphere mesh into a cube (BABYLON.PhysicsImposter.BoxImposter). The simpler shapes makes doing physics calculations a lot faster. If you've every played an action games, physics imposters are very similar to hitboxes.

With that said, lets create a physics imposter the sphere and plane.

let spherePhysicsImposter = new BABYLON.PhysicsImpostor(
        sphere,
        BABYLON.PhysicsImpostor.BoxImpostor,
        {
            mass: 1, 
            friction: 0.1, 
            restitution: .85
        },
        scene
    );

let planePhysicsImposter = new BABYLON.PhysicsImpostor(
    plane,
    BABYLON.PhysicsImpostor.BoxImpostor,
    {
        mass: 0,
        friction: 0.1, 
        restitution: .7
    },
    scene
);
  • We set the mass of the planePhysicsImposter to 0 because we do not want the plane to fall
  • Feel free to play with the mass, friction and restitution values

Below you can see the sphere bouncing on the plane. Pretty cool, eh?

View the code in this repl.

Walking

So we have all the physics in place - wouldn't it be cool to interact with the scene directly? To do this, we can make the camera affected by gravity. Since we already have keyboard controls of the camera, we can simply walk around!

camera.ellipsoid = new BABYLON.Vector3(1.5, 0.5, 1.5);
camera.checkCollisions = true;
camera.applyGravity = true;

plane.collisionsEnabled = true;
plane.checkCollisions = true;
  • We first create an ellipsoid around the camera. You can think of this like the hitbox. It less collide with objects like the sphere. The ellipsoid has a height of 0.5 units and has a length and width of 1.5 units
  • If you don't seem to be moving, you may want to check camera.speed

If you want the camera to collide with the sphere, must enable collisions with the sphere

sphere.collisionsEnabled = true;
sphere.checkCollisions = true;

As you can see, I can now walk around!

View the code in this repl

🚀

Now we have a player moving around the scene, with gravity enabled! We can collide with some meshes in the scene, like the sphere. How cool is that? Hopefully you found this BabylonJS introduction tutorial to be helpful! Tell me what you think and post a comment!

Commentshotnewtop