PlayCanvas is a popular 3D WebGL game engine, originally created by Will Eastcott and Dave Evans. It is open sourced on GitHub, with an editor available online and good documentation. It is free for public projects with up to two team members, but there are also paid plans if you'd like to run a commercial private project with more developers.
Games and demos
PlayCanvas has a few well-known demos published that showcase its possibilities.
- Tanx is a multiplayer tank game where you can drive your tank around, shooting at other players as you go.
- Swooop is a flying game where you pilot your plane around a magical island, collecting jewels and fuel as you go.
- Visualizations like the Star Lord and BMW i8 also take advantage of the engine and showcase what's possible.
Note: Check out the list of featured demos to find more examples.
Engine vs editor
The engine itself can be used as a standard library by including its JavaScript file directly in your HTML, so you can start coding right away; in addition the PlayCanvas toolset comes with an online editor that you can use to drag and drop components onto the scene — a great way to create games and other apps requiring scenes if you're more of a designer than a coder. Those approaches are different, but work equally well in terms of achieving end goals.
PlayCanvas engine
Built for modern browsers, PlayCanvas is a fully-featured 3D game engine with resource loading, an entity and component system, advanced graphics manipulation, collision and physics engine (built with ammo.js), audio, and facilities to handle control inputs from various devices (including gamepads.) That's quite an impressive list of features — let's see some in action.
We will try putting together a simple demo first — a cube rendered on the screen. If you have already worked through our Building up a basic demo with Three.js article (or you are familiar with other 3D libraries) you'll notice that PlayCanvas works on similar concepts: camera, light and objects.
Environment setup
To start developing with PlayCanvas, you don't need much. You should start off by:
- Making sure you are using a modern browser with good WebGL support, such as the latest Firefox or Chrome.
- Creating a directory to store your experiments in.
- Saving a copy of the latest PlayCanvas engine inside your directory.
- Opening the PlayCanvas documentation in a separate tab — it is useful to refer to.
HTML structure
Here's the HTML structure we will use.
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>MDN Games: PlayCanvas demo</title> <style> body { margin: 0; padding: 0; } canvas { width: 100%; height: 100%; } </style> </head> <body> <script src="playcanvas-latest.js"></script> <canvas id="application-canvas"></canvas> <script> var canvas = document.getElementById("application-canvas"); /* all our JavaScript code goes here */ </script> </body> </html>
It contains some basic information like the document {{htmlelement("title")}}, and some CSS to set the width and height of the {{htmlelement("canvas")}} element that PlayCanvas will use to 100% so that it will fill the entire available viewport space. The first {{htmlelement("script")}} element includes the PlayCanvas library in the page; we will write our example code in the second one. There is one helper variable already included, which will store a reference to the {{htmlelement("canvas")}} element.
Before reading on, copy this code to a new text file and save it in your working directory as index.html
.
PlayCanvas application
To begin developing our game we have to create the PlayCanvas application first (using the given {{htmlelement("canvas")}} element), and then start the update loop. Add the following code to the bottom of your second {{htmlelement("script")}} element:
var app = new pc.Application(canvas); app.start();
The pc
global object contains all the PlayCanvas functions available in the engine.
Next, we'll set the Canvas to fill the window, and automatically change its resolution to be the same as the Canvas size. Again, add the following lines at the bottom of your script.
app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW); app.setCanvasResolution(pc.RESOLUTION_AUTO);
Camera
Now when the setup code is in place we need to think about implementing the standard scene components: camera, lights and objects. Let's start with the camera — add these lines to your code, below the previous ones.
var camera = new pc.Entity(); camera.addComponent("camera", { clearColor: new pc.Color(0.8, 0.8, 0.8) }); app.root.addChild(camera); camera.setPosition(0, 0, 7);
The code above will create a new Entity
and add a camera
component to it with the light gray clearColor
— the color will be visible as the background. Next, the camera
object is added to the root of our application and positioned to be 7 units away from the center of the scene on the z
axis. This allows us to make some space to visualize the objects that we will create later on.
Note: The distance values (e.g. for the camera z position) are unitless, and can basically be anything you deem suitable for your scene — milimeters, meters, feet, or miles — it's up to you.
Try saving the file and loading it in your browser. You should now see a gray window. Congratulations!
Geometry
Now the scene is properly rendering we can start adding 3D shapes to it. To speed up development PlayCanvas provides a bunch of predefined primitives that you can use to create shapes instantly in a single line of code. There are cubes, spheres, cylinders and more complicated shapes available. Drawing everything for given shape is taken care of by the engine, so we can focus on the high level coding. Let's start by defining the geometry for a cube shape — add the following new code below your previous additions:
var box = new pc.Entity(); box.addComponent("model", { type: "box" }); app.root.addChild(box); box.rotate(10, 15, 0);
It will create an Entity
with the box
model component and add it to the root of the application, our scene. We also rotate the box a bit to show that it's actually a 3D cube and not a square.
The cube is visible, but it is completely. To make it look better we need to shine some light onto it.
Lights
The basic light types in PlayCanvas are directional and ambient. The first type is a directional light placed somewhere on the scene while the scond one reflects the light from the first type, so it looks more natural; this can be set globally. Again, add the new code below your previous additions.
var light = new pc.Entity(); light.addComponent('light'); app.root.addChild(light); light.rotate(45, 0, 0);
It will create a light Entity
component and add it to the scene. We can rotate the light on the x
axis to make it shine on more than one side of the cube. It's time to add the ambient light:
app.scene.ambientLight = new pc.Color(0.2, 0.2, 0.2);
The code above assign a dark grey ambient light for the whole scene. The box look better now, but it could get some colors to look even better - for that we need to create material for it.
Material
The basic PlayCanvas material is called PhongMaterial — add the following lines below the previous code.
var boxMaterial = new pc.PhongMaterial(); boxMaterial.diffuse.set(0, 0.58, 0.86); boxMaterial.update(); box.model.model.meshInstances[0].material = boxMaterial;
By diffusing the light on the object we can give it it's own color —we'll choose a nice familiar blue.
Note: In PlayCanvas, the color channel values are provided as floats in the range 0-1
, instead of integers of 0-255
as you might be used to using on the Web.
After the material is created and its color is set, it has to be updated so our changes are going to be applied. Then all we need to do is set the box
's material to the newly created boxMaterial
.
Congratulations, you've created your first object in a 3D environment using PlayCanvas! It was easier than you thought, right? Here's how it should look:
And here's the code we have created so far:
{{JSFiddleEmbed("https://jsfiddle.net/end3r/cqs6pg3x/","","350")}}
You can also check it out on GitHub.
More shapes
Now we will add more shapes to the scene. Let's move the cube 2 units to the left to make space for some friends — add the following line just below the previous code:
box.translate(-2, 0, 0);
Now let's add a new shape — how about a cylinder?
Cylinder
Add the following lines at the bottom of your JavaScript code:
var cylinder = new pc.Entity(); cylinder.addComponent("model", { type: "cylinder" }); app.root.addChild(cylinder); cylinder.rotate(15, 0, 0);
This looks very similar to the code we used for creating a cube, but instead of the box
component we are adding a cylinder
. It is also rotated around the x
axis to show it's actually a 3D shape. To make the cylinder have a color, let's say yellow, we need to create the material for it, as before. Add the following lines:
var cylinderMaterial = new pc.PhongMaterial(); cylinderMaterial.diffuse.set(1, 0.58, 0); cylinderMaterial.update(); cylinder.model.model.meshInstances[0].material = cylinderMaterial;
Cone
Creating a cone and its material is done in almost exacly the same way as we did for the cylinder. Add the following code, again, at the bottom of your script:
var cone = new pc.Entity(); cone.addComponent("model", { type: "cone" }); app.root.addChild(cone); cone.translate(2, 0, 0); var coneMaterial = new pc.PhongMaterial(); coneMaterial.diffuse.set(0.9, 0.9, 0.9); coneMaterial.update(); cone.model.model.meshInstances[0].material = coneMaterial;
The code above will create a new cone
, add it to the app
and move it by 2 units to the right so it's not overlapping the cylinder. Then the material is created, given a gray color, and assigned to the cone Entity
.
Here's how it should look right now:
This works, but it is a bit boring. In a game something is usually happening — we can see animations and such — so let's try to breathe a little life into those shapes by animating them.
Animation
We already used translate
or rotate
to adjust the position of the shapes; we could also change their positions directly with setPosition
, or scale them. To show actual animation, we need to make changes to these values inside the rendering loop, so they are updated on every frame. There's a special update
event that we can use for that — add the following code just below the previous additions:
var timer = 0; app.on("update", function (deltaTime) { timer += deltaTime; // code executed on every frame });
The callback takes the deltaTime
as the parameter, so we have the relative time that has passed since the previous invocation of this update. For time based animations we'll use a timer
variable that will store the time that has passed since the start of the app by adding the deltaTime
to it on every update.
Rotation
Rotating is quite easy — all you need to do is to add a defined value to the given direction of rotation on each frame. Add this line of code inside the app.on("update")
callback function, right after the addition of the deltaTime
to the timer
variable:
box.rotate(deltaTime*10, deltaTime*20, deltaTime*30);
It will rotate the box
by deltaTime*10
on the x
axis, deltaTime*20
on the y
axis and deltaTime*30
on the z
axis, on very frame — giving us a smooth animation.
Scaling
We can also scale a given object — there's a function for that called setLocalScale
. Add the following, again into the callback:
cylinder.setLocalScale(1, Math.abs(Math.sin(timer)), 1);
Here we are using Math.sin
to scale the cylinder in a cycle, bigger and smaller again. We're wrapping the y
scale value in Math.abs
to pass the absolute values (greater or equal to 0); sin
varies between -1 and 0, and for negative values the cylinder scaling can render unexpectedly (in this case it looks black half the time.)
Now onto the movement part.
Moving
Beside rotation and scaling we can also move objects around the scene. Add the following code to achieve that.
cone.setPosition(2, Math.sin(timer*2), 0);
This will move the cone
up and down by applying the sin
value to the y
axis on each frame, with a little bit of adjustment to make it look cooler. Try changing the value to see how it affects the animation.
Conclusion
Here's the final code listing, along with a viewable live example:
{{JSFiddleEmbed("https://jsfiddle.net/end3r/auvcLoc4/","","350")}}
You can also see it on GitHub and fork the repository if you want to play with it yourself locally. Now you know the basics of PlayCanvas engine; happy experimentation!
The PlayCanvas editor
Instead of coding everything from scratch you can also use the online editor. This can be a more pleasent working environment if you are not someone who likes to code.
Creating an account
The PlayCanvas Editor is free — all you have to do to begin with is register your account and login:
When you first sign up, you are taken straight into the editor and given a simple starter tutorial involving editing a 3D rolling ball game. You can finish this before you continue our tutorial if you like. When you are ready to continue with our tutorial, go to your canvas homepage — for example mine is https://playcanvas.com/end3r
. Here's what the page looks like — you can create projects and manage them, change their settings etc.
Creating a new project
Start a brand new project by clicking on the New button:
The resulting dialog box will show a few different options. There are starter kits available, but we don't want to load models or start a platform game.
- We want to start small, so we will use the empty project — click on the Blank Project option and enter a name for it (we are using "MDN Games demo".)
- Enter a description if you want — it is optional.
- Click Create to have it created.
Next you'll see your project's page — there's not much yet. By clicking the Editor button we'll launch the online PlayCanvas editor where we'll create our scene with the shapes. Do this now.
Creating the scene
Here's how the scene looks initially in the editor. Even though it's a blank new project we don't have to start entirely from scratch — the camera and directional light are prepared already, so you don't have to worry about them.
Now onto the creative part. To add an entity to the scene you have to click on the big plus button located in the top left area of the editor, next to the Hierarchy text. When hovering over that button with your mouse the label will say 'Add Entity' — that's exactly what we want to do. An Entity is any object used in the scene — it cna be an object like a box, cylinder or cone, but it can also be a camera, light or sound source. After clicking the button you'll see a dropdown list containing a lot of various entities to choose from. Go ahead and click Box — it will be added to the scene.
The box is created with the default values — width, height and depth are set to 1, and it is placed in the middle of the scene. You can drag it around or apply new values in the right panel.
To add some colors to the scene we need a new material that will be used on the newly created box. Click on the plus button in the Assets tab, and click on the Material option in the dropdown list that appears to create a new material.
Click on your new material in the assets tab and its entity inspector will appear on the right hand side of the display. Now edit the Name text field to give it a unique name (we've chosen boxMaterial). A unique name will help us remember what this material is for — we will add more later!
To change its color we'll use the Diffuse option in the entity inspector. Click Diffuse, then select the colored box next to the Color label — it will open a color wheel. From here you can click your desired color or enter it in the bottom text field as a hex value. We've chosen a blue color with a hex value of 0095DD
— enter this code in the text field and press return for it to be accepted.
Note: Yes, you read that right — you need to enter the hex value without the hash/pound symbol.
Now we can apply the colored material to the shape by clicking and dragging its icon from the bottom part of the screen (the little dot on the left hand side of the material's name — it can be a bit fiddly to select; just persevere) onto the box on the scene.
So, at this point we've created a blue box. Click on the box to bring up its entity sidebar — you'll see options for changing its position, rotation, and scale. Try applying the rotation values X: 10 and Y: 20.
Now click on the play arrow in the top right corner of the scene to launch and render the scene — it will be opened in a separate browser tab.
This looks great! Let's add more shapes to the scene to make it look more interesting.
Adding more shapes
To make way for more shapes, move the box to the left to make some room for the next shape. You can do this by giving it an X position value of -2.
Adding other shapes involves a very similar process to adding the box. Click on the Root folder in the hierarchy panel (to make sure that the new shape appears in the root, and not as a child of the Box) then Click on the big Add Entity (plus) button and select cylinder from the dropdown list — it will add a new cylinder shape to the scene.
Now follow the same steps as we did before when coloring the cube:
- Create a new material using the Add Asset (plus) button.
- Make sure the New Material in the Assets panel is selected, to bring up the entity inspector.
- Give the material a new name, along the lines of
cylinderMaterial
. - Click diffuse, then click the color picker — give it an orange color (we used FF9500.)
- Drag and drop the
cylinderMaterial
icon onto the cylinder object on the sceene to apply that color.
Follow the same approach again to add a cone to the scene, giving it a grayish color (we used EAEFF2.) You should now have three shapes on your scene, something like the below screenshot.
Animating our scene
Animating 3D models might be considered an advanced thing to do, but all we want to do is to control a few properties of a given object — we can use a script component to do that. Click on the plus button in the Assets panel, select the Script option, and name your new script file boxAnimation.js
.
If you double click on it, you'll be moved to a code editor. As you can see, the file contains some boilerplate code already:
pc.script.create('boxAnimation', function (app) { // Creates a new BoxAnimation instance var BoxAnimation = function (entity) { this.entity = entity; }; BoxAnimation.prototype = { // Called once after all resources are loaded and before the first update initialize: function () { }, // Called every frame, dt is time in seconds since last update update: function (dt) { } }; return BoxAnimation; });
The most interesting part is the update()
function, which is where we can put any code that we want repeated on every frame. Add the following line inside this function, to rotate the cube on every frame:
this.entity.rotate(dt*10, dt*20, dt*30);
In the line above this.entity
refers to the object to which the script will be attached (the box); using the dt
variable, which contains the delta time passed since the previous frame, we can rotate the box by a different amount around all three axes.
- Save the changes using the Save button in the top right of the code editor, then return to the main editor tab. Here, follow these steps:
- Be sure you have the box selected on the scene.
- Click on Add component, then Script in the entity inspector.
- At the bottom of the screen you can see the list of scripts available — for now there's only
boxAnimation.js
— clicking it will add the animation script to the box object.
The cylinder
Now we'll do the same steps for cylinder. First:
- Create a new Script asset.
- Name it
cylinderAnimation.js
. - Double click the script icon to launch the code editor.
This time instead of rotating the object we will try to scale it. For that we'll need a timer to store the total amount of time passed since the start of the animation. Add this code to the initialize()
function:
this.timer = 0;
And those two lines to the update()
function:
this.timer += dt; this.entity.setLocalScale(1, Math.abs(Math.sin(this.timer)), 1);
The setLocalScale()
method applies the given values to the X, Y and Z axes of the object. In our case we're modifying the scale of the cylinder on the Y axis, giving it as a value the Math.sin()
of the timer, with Math.abs()
applied to the result of that to have the values always above zero (0-1; sin values are normally between -1 and 1.) This gives us a nice scaling effect as a result.
Remember to add the cylinderAnimation.js
file to the Cylinder object to apply the given animations.
The cone
Time to play with the last object — the cone. Create a coneAnimation.js
file and double click it to open it in the editor.
Next, add the following line to the initialize()
function:
this.timer = 0;
To move the cone up and down we will use the setPosition()
method — add the code below to the update()
function:
this.timer += dt; this.entity.setPosition(2, Math.sin(this.timer*2), 0);
The position of the cone will be animated on each frame by being passed the Math.sin()
value of the timer
at each point in time — we have doubled the this.timer
value to make it move higher.
Add the coneAnimation.js
script to the cone object, as before.
Test the demo out
Launch the demo to see the effects — all the shapes should animate. Congratulations, you've completed the tutorial!
Summary
Of course it depends on your approach — designers may favor the online editor while programmers will prefer having the full control over the coding environment and will probably use the engine's source files. The good thing is that you have a choice and can pick whatever tools suits you best.