🎮 3D Games with BabylonJS: A Starter Guide
🎮 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. Theengine
'translates' the BabylonJS API to canvas painting. For example, when you create a sphere withBABYLON.MeshBuilder.CreateSphere("sphere", {}, scene);
, it is responsible for actually drawing thesphere
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 theengine
. - 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
https://storage.googleapis.com/replit/images/1560012861816_73694c5a7f3b945b6d62086a668314c1.pn
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!
https://storage.googleapis.com/replit/images/1560013151353_538243f989f87cb8811eed3c0c6b5463.pn
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 ablack
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 adding87
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 thesphere
Gravity
Creating gravity is pretty straightforward. To add gravity, we have to do three things.
- Choose a Physics engine
- Enable gravity on the
scene
- 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
to0
because we do not want theplane
to fall - Feel free to play with the
mass
,friction
andrestitution
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 of0.5
units and has a length and width of1.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!
hey whoever created this, thanks for creating this because i am an avid programmer and like to script games
but dont want to use unity, roblox studio or unreal engine while i find them too complicated. Thank for creating a tutorial.
k bye
Hey! Very cool, now I can make a game but 3D!
SyntaxError: Cannot use import statement outside a module
at /script.js:2:1
Awesome! i don't know js, but imma try make a game with this
Awesome!
@eankeen good repl!! :)
Too cool