Make a Game With PlayCanvas in 2 Hours
- Intro3 minutes
- About PlayCanvas Editor5 minutes
- Set up PlayCanvas33 minutes
- Animating the player11 minutes
- Adding the Ball with Physics8 minutes
- Templates and Spawning more Balls13 minutes
- Adding Score UI and Logic with Events16 minutes
- Adding Sound Effect25 minutes
- Improving the Gamehalf a minute
- PlayCanvas Useful Links2 minutes
From Author:
In this workshop, we’ll build a game using the PlayCanvas WebGL engine from start to finish. From development to publishing, we’ll cover the most crucial features such as scripting, UI creation and much more.
Table of the content:
- Introduction
- Intro to PlayCanvas
- What we will be building
- Adding a character model and animation
- Making the character move with scripts
- 'Fake' running
- Adding obstacles
- Detecting collisions
- Adding a score counter
- Game over and restarting
- Wrap up!
- Questions
Workshop level
Familiarity with game engines and game development aspects is recommended, but not required.
This workshop has been presented at JSNation 2023, check out the latest edition of this JavaScript Conference.
FAQ
Backplane Canvas is a web-first engine focusing on maximizing web capabilities, including support for WebXR for AR and VR experiences and gLTF standards for models and materials. It features an open-source engine runtime under the MIT license, an online editor for building experiences, and support for Draco Mesh Compression for optimizing model sizes.
Developers can contribute to Backplane Canvas by adding features and fixes to its open-source engine runtime. The project accepts contributions from both its core team and the developer community at large.
Using WebXR with Backplane Canvas allows developers to create immersive AR and VR experiences directly on the web, which can be enabled by devices like the Apple Vision Pro. This integration enhances the accessibility and reach of AR/VR applications.
Backplane Canvas documentation uses ES5 for PlayCanvas, an older version of JavaScript. Developers should be aware of the differences when mixing tutorials that may use newer JavaScript versions like ES6, which includes features like classes and modules.
The MIT license allows developers to use, copy, modify, merge, publish, distribute, sublicense, and sell the software and its associated documentation freely. This makes Backplane Canvas an attractive option for developers looking for a flexible and open engine runtime.
In Backplane Canvas, applications are made up of entity objects in a scene. Each entity has components that drive its behavior, such as sound, physics, or custom scripts. Entities can be manipulated through a PC application class that manages all systems and provides access to the scene graph and assets.
Draco Mesh Compression is a feature in Backplane Canvas that allows developers to compress highly detailed meshes significantly, reducing them to a tenth or even a twentieth of their original size. This compression improves loading times and performance of 3D models in applications.
Video Transcription
1. Introduction to Backplane Canvas
Backplane Canvas is a web-first engine focused on using the web to the fullest. It embraces open standards like WebXR and supports gLTF. The engine runtime is open source under the MIT license and has a small footprint. PlayGamers offers an online editor for building experiences visually. The latest features include GLTF support with Draco Mesh Compression. Many users have created multiplayer games and experiences for platforms like WeChat and Snapchat. We're building a small game to control a player character and prevent balls from hitting the floor.
Okay, so Backplane Canvas is a web-first engine, which means they are very much focused on using the web to the fullest. This includes embracing open standards such as WebXR for AR and VR experiences, which I'm very happy that the Apple Vision Pro is enabling at some point, and supporting the gLTF standards for models and materials. The engine run time, the code that has published experiences are ran on is fully open source under the MIT license, and they've had many developers contribute features and fixes over the years as well as core team.
In terms of size, it's got a footprint of less than 400k after gzip and the developers are looking to support tree-taking from the editor to make it even smaller so it could strip out code that from the engine that you're not using. It uses a and the core product from PlayGamers is an online editor which you can see here and we can build experiences visually, add models, upload textures, modify materials through the browser, which is what we'll be using today. And here's a selection of the latest features. I won't go into all of them now, but the ones I do want to call out is the GLTF support with Draco Mesh Compression which allows developers to take really high detail meshes and compress them down into a tenth or twentieth of a size. So almost like one-fifth of the size. It's something they've recently added and is super impressive.
And so many of the users for PlayCanvas have made some really great multiplayer games, games for multiplayer. So let me just step back a bit. So basically what I have today, who I am, features. Many of the users have made great multiplayer games and games with meshed platforms such as WeChat, Snapchat, Facebook, Metaverses, Shopping Experiences, and Product Visualizations and Configurators. You can find trailers for this under MaplePlayCanvas and linked here. And this is what we're looking to build today. We're looking to build a very small game where you control your player character and to stop the balls from hitting the floor. So basically try and knock them away from the floor because when they hit the floor, your health will fall. Okay.
2. JavaScript and Play Canvas Gotchas
Our plan is to go through as many features as we can, including the foundation features. Before that, let's talk about some gotchas with JavaScript and Play Canvas. Play Canvas uses an older version of JavaScript, and it doesn't have a build step to transpile code. JavaScript has no value types and allows dynamic property addition at runtime. Functions are objects and can be powerful but require careful scope management. We'll also cover the Play Canvas engine architecture and how to navigate the API documentation.
So, what's our plan is to basically go through as many features as we can. the foundation features. But before that, I also want to talk about how so what talk a little bit about some gotchas with JavaScript and Play Canvas. So all documentation for Play Canvas use the EDSA uses jest ES5 which as you everyone knows is quite an old version of JavaScript. If you're looking for like if you're new to JavaScript especially and you're looking at tutorials around JavaScript, you'll find that they're gonna be written in using ES6, which is classes and modules. Generally is not a problem as long as you're kind of aware of the differences, but bear this in mind when you mix tutorials from outside of Play Canvas and the stuff from Play Canvas itself. And this is because like Play Canvas doesn't really have a build step yet, where it transpiles JavaScript down to ES5. What it does, it takes the code that you write, and runs it directly into the browser. So, technically as long as the browser can run the code, you can use it within the Play Canvas editor. And I don't know if there's any people who are new to JavaScript here, but if you are, there's some people who come from Unity and C Sharp. There's a few gotchas that people run into when they first use Play Canvas and not use JavaScript before. Which is, JavaScript has no concept of value types, so objects are always parsed by reference. And there's dynamic language where you can add properties dynamically at run time. So you don't have the static type checking that you normally have with languages like C Sharp and C++. And also, more importantly, functions are objects. So you can parse functions as objects across the code, which makes it incredibly powerful, but it's also something to be aware of when you're dealing with scope. And I want to also cover the Play Canvas engine architecture, at least very broadly. This is something that we're going to come back to throughout the workshop, and also use this to help show how to navigate the API documentation especially.
3. Play Canvas Application Structure
At its core, a Play Canvas application consists of entity objects in a scene, each with components that drive their behavior. Entities are transforms with the option to add components like sound, physics, or custom logic through scripts. The PC application class is the heart of PlayCanvas, providing access to systems and managers. PlayCanvas assets are wrappers around data, allowing dynamic loading and unloading at runtime. PlayCanvas is event-driven, with events fired for engine actions and the ability to send custom events within the application.
So at its core, when you make a Play Canvas application, it's made up of entity objects in a scene, and these entity objects have components which drive the behavior of the entity. An entity by itself is just a transform. It's just a position in space with rotation and scale, and if you wanted to have it allow it to play sound, you add a sound component. If you wanted to have physics properties, you add rigid body and collision components. And if we want to have it do custom logic, we can give it a script type, and that script that script type is effectively some custom code that we have that's hooked into the lifetime of the object. So there's an initialization stage that updates a function that's called every frame, and this is what we generally going to use to drive the application logic. There's also a PC application class that gets created automatically by you, by the editor, and that is pretty much the heart of PlayCanvas. It contains all the systems, the managers, for getting to that keyboard input, be able to access the assets, and also it gives you direct access to scene graph. The application object itself as well is pretty much globally accessible, so you can access it from anywhere, making it very easy to effectively get whatever you need from the Engine through the application object. And assets. So PlayCanvas assets are wrappers around data. And the data is things like model data, animation, textures, materials, audio effects, etc. And the reason why it's wrapped is so that Code can have a handle to the... How do I say it? A handle of what the data would be. So, for example, I can reference a sound asset, but... But I can load the data or unload the data dynamically at run time. And this allows us to do things like preloading the page for only the assets we need, and later loading only what we need for what the user goes through. And finally, PlayCanvas is fully event-driven. It's mostly event-driven, where anything that happens within the engine, such as physics collisions, input, asset loading results, any sort of async operation will be fired as an event back through PlayCanvas objects. You'll see this towards the end of the workshop. And we can also use the same system to send events across the application within our own code as well.
4. Setting up the Project and Scene
Open chat and ask participants to fork the base project. Introduce the project editor and its main features. Explain how to move and rotate objects in the scene. Provide tips for navigating the 3D view. Discuss the asset panel and the importance of setting up the scene. Explain the concept of image-based lighting and how to select a skybox asset. Mention the launch button.
I'm going to open up chat. Sorry, I just realized I've got zoom chat. Okay, so, everyone, now that you've signed up to PlayCanvas, I would like you to fork this project that we can use as the base. So I'm going to put that into chat now. And I'm going to post. There you go. So this is... So we're going to click on this. This is the base project. So this is what we're going to use to start our workshop from. If we click on fork, this will take... When you fork a project or public project, that will take a copy of the project that's on the main branch and create a new one... and put it under your ownership. So I'll call this, let's see, Jetsnation Workshop. That can't spell. Okay. If we click on editor, this will take us to the project editor and give us a scene select. So we click on the untitled scene, and this puts us into what you normally start off with when you create a blank project in PlayCanvas. So it's got you through the editor.
Here, this is our main view, our main 3D view, where you actually see what's in our scene. This will be populated with entities when we go through the workshop. On the left-hand side, we've got our scene hierarchy. So these are made up of PlayCanvas entities, and as you can see, if we select each one, some of these already have components or logic — sorry, components added to them. So the camera has a camera component, which obviously is used to work out what to render from position and to — sorry, work out what to render — to render the scene. So we can move the camera around and render different aspects of the scene, or we can move the camera at runtime. We have a light, which has a light component that has — where we have different types of light — direct, spot and omi. And from here we control how bright the light is, what colour is, and other properties, which we'll go through later. And finally, we've got the plane object, which is an entity with a render component. So render components are used to effectively render meshes into the scene. So in this case it's a plane, which is a flat — is effectively a flat poly. And that is — sorry, I'll say that is it. And what we can do here, we can either modify these values, move them around, or add new entities to the scene. So right-click on this and go to 3D box. We're going to create a 3D box into the scene. And from here, we can use the transform tools to move and rotate the object. So on the left-hand side, we've got transform, rotate, scale, resize element component, which is about UI, which we'll go to later. And we've got snap, which we've been enabled to snap to a grid. Focus, so if we click on that, we'll focus our editor view onto the object that we selected. We've got undo, redo, lightmap, which we're going to go into today, the coded cell, which we'll be touching in the next step, and also the publish and download steps we'll go through last. To move around the 3D view, you can hold down the left mouse button to rotate the camera, scroll wheel to zoom in and out, middle mouse button to pan, and the right mouse button is used to rotate the view like a first-person sliding camera, and from there you can use WASD to move around the scene. If you're on a laptop and you find zooming in and out is a bit sensitive, if you go to settings in the bottom left, go to editor, you can change the zoom sensitivity from to better suit your hardware. Normally I set it to two, because I find that I find the default a bit too sensitive for zooming. So if we left-click to select an object in a 3D view, we will can use the translate transform to move it around the scene. So if I hover over these arrows, I can move it along any axis, in and out. Or we can hover over the squares to move it across that particular flat plane. So for example, if I hover with this, I will move over the green and blue plane. So it's never gonna move in and out. And the directions that they represent are the world X, Y, up, and Z for forward. And the best easy way to remember is RGB X, Y, Z effectively. Okay. Let's see. And also below here, so I've talked about the asset panel. The asset panel is what is all the data that we uploaded. So this will be, represents our texture. So this is where we upload our textures, models, our GLB, FBXs, PNGs, audio assets and whatnot. We're going to skip the importance step. It's like the uploading step because I figured that would take too long at the workshop. So this, all these assets are already set up for the workshop. What we're going to do here is set up the scene, set up the project to make it up a bit more because right now it's a bit boring. It's a black scene and we've got basically one line. What I commonly do with any new project is I put in a skybox and what they're allowed to do is it will give us ambient lights of the colour of the skybox. This is known as, what do we call that? Image, oh, image-based lighting where effectively the skybox colours will emit ambient light across the entire scene, which basically makes it look a lot nicer than having one harsh directional light into the scene itself. So if we go into settings on the left and go to rendering, what we've got here is the Skybox option here. So we can select, if we click on the pencil button, we can go and find our skybox asset, which will be from the root assets, Belfast Sunset HDR. And we've got a type cubemap. If we select that, you can see now, it generally looks a lot brighter. It looks a lot more natural than having a single color in the background. And what I tend to do as well is rather than having BRSC's, the cloud and whatnot, I tend to change a bit to two, which basically blurs out the skybox. Okay, cool. And finally, we've also got the launch button.
5. Setting up Floor, Camera, and Player Character
We launch the scene and set up the floor and camera for gameplay. We frame the scene with the camera and adjust the angle. Then, we change the floor color to green using a material. Next, we change the light angle and improve the resolution of the shadows. Finally, we add a player character and write a script to control its movement.
So the launch button will open the scene as an end user would run the game. So let's hit launch, and we can see here from where our camera is, is currently, what direction it's looking at, and the skybox, you can see how it looks. But what we're gonna do next is what, sorry, the next step we're gonna do is set up the floor so that we can see it better from the background, and also set up the camera ready for gameplay. So having it set at the right angle where we can put our player character down and effectively frame the scene.
So first, we're gonna frame the scene with the camera, and what we can do if we go to, where is it, focus, focus, focus, focus, we can see our camera. Oh, sorry. So I just saw Alex's message about, is it frozen for everyone? Anyone else having the same issue with the 4v4? Louis says it's fine. Karen, sorry Karen, says it's fine. Okay. Alex, would you wanna try rejoining the Zoom call and see if that helps? Okay. I'm gonna give them a few minutes to try that. Okay. Of course, okay. The other thing is, if you do get stuck with the steps of following, please do give me a shout out and we can look at the issue on stream. Or what you can do is, you can fork the project that I forked. So you can restart from where I currently am in the workshop. So this is my fork. I'm gonna place that into both a Zoom chat and Discord.
Okay, so what we're gonna do is frame the camera up. So what we can see up here is a preview of what the camera is looking at. What we're gonna do is directly change it so that it's facing pretty much the platform head-on. So we set the X position to zero, set this to 2.65, which is Y position, and set that to, I think, eight and set the rotation to zero, zero. And we can see here, this is how I would want to frame the game, where you're looking straight on, down the path with a slight angle down, and that gives us enough room to see the floor that the player can walk around and also gives enough room to see the boards on top. Now that's done, we're gonna launch that and take a look at what that looks like, and this is like the expectation. Cool. What I wanna do now is, change the floor colour to green, again to help it stand out and pop out. So what I'm gonna do here in the, go back to the assets panel. I'm gonna set a material to apply to the mesh, and what materials do is control how the mesh is rendered, in terms of what colour it is, whether it will render properly or not, and we can control that here. So in terms of what colour it is, whether it will have an image as a texture on top of it, whether light affects it and whatnot. In this case, we're just gonna make it green, which is nice and easy to see. So it goes to assets, go to models, floor, and we're gonna right click, go to new asset, select material, and this will create a new asset of material type. Rename this to floor. Go to the diffuse. So diffuse is the material channel that is going to control what the mesh looks like when also affected by light. So in this case, we're gonna change to the green. And what I tend to do is use a site called flat UI colors and just grab one of these colours because they generally look okay. So copy that. I'm gonna put that on the floor. Paste it into there. Remove the hash. And now we've got a green floor. And what we're gonna do now is when we select the floor, which should look plain and we click on the pencil button to select the mesh, sorry, to select the material. And we've got now a nice green floor. And because the Launch tab and the editor are connected together via the server, any updates we do, as you get reflected back in the Launch tab almost immediately. One last thing I wanna do is change the light angle. So it's fixing straight down. And that way, when the balls appear at the top, we can see where they're coming from, from a gameplay perspective, if the shadow is directly underneath the ball. So what I wanna do is go to the light and change the angle to be 0, 0, 0. And now we can see, there we go. Now we can see the light angle, so pointing straight down. And this is what it looks like. The other thing I wanna do is also make those shadows a lot nicer. Because at the moment, they look a bit low resolution. So we can go here, back to the light, and change the resolution of the shadow map. The shadow map is, effectively, a texture of what shadows are being cast by the light. The larger the area of the shadow map, the less, sorry. Ideally, you want to have the shadow resolution as high as possible that you can support on your device, which generally is about 4K for most mobile devices. And also, have the light only affect a small area of the world as possible. And to keep it high res, so the larger the world, the larger the area that the light affects, the lower resolution the shadow will be on the shadow map, so that's 4K. And you can see there now, they're a lot sharper. Okay, so that sets up for step one in terms of setting the scene and the edit intro. Go back to here. Oh, sorry, by the way, if you do want to, just like later online again after workshop and look into more details about anything I've talked today, I've left links at the bottom of each slide of the other tutorials or documentation that is relevant to this area that I'm talking about.
Okay, so what we're going to do now is add a player character, that yellow character we saw earlier, into the scene and also write a script to control the movement of it so you can go left and right. And normally what we do is import an FBX or GLB into the assets, but I've done that already to save time. So we go to models and look at the Kkit animator character. So this is a CC0 asset. So we can use it freely. And what normally happens is when you import your FBX or GLB, it gets created as a model source, and that would then create other assets including the mesh data, which is container, the materials from the FBX, any animations that was part of the FBX, and also more importantly, a template. So I'll talk template into a bit more detail a bit later in the workshop, but for now the template is a data set which represents the hierarchy of nodes in the model.
6. Character Template and Script
When importing a model, the template includes nodes for different body parts. To add it to the scene, right-click on the entity, go to template, and add an instance. The template asset will be imported with its hierarchy. Delete the render mesh and zoom out to see the scene. The next step is to add a script to move the character left and right.
So if we're talking about a character, for example, there'll be nodes for like the head, the shoulder joints, the arms and knees. And this is pre-configured in the template when we import the GLB and FBX, when you import a model. And the way to add it to the scene is if we right click on the entity we want to create it under, so under root, right click, go to template, add instance. And then we select the template asset. And we can see here if I focus on it, it will import the template with all its hierarchy. So we've got the body, the head, the arm, et cetera. And also a render mesh, which is used to effectively render into the scene. Let's delete that box while we're at it, delete the box, right, click delete. Now if we zoom out a bit, and again, we'll go back into the launch lab. This is what it looks like. What was the next bit? Add a scene, okay, adding a script to move it left and right.
7. Writing Custom Script Type
We're going to write our own script type to detect keyboard input and move the NC around the scene. In the assets panel, we create a new script called player. This script type gets attached to entities in the engine, allowing us to control their custom logic. The script boilerplate includes an object that gets instanced on every entity with this script type attached.
So what we're gonna do here is write our own script type to have custom logic where we can detect keyboard input and then move the NC around the scene. So what we're gonna do here is go to assets, sorry, go to assets panel, scripts, right click, go to new script, and we call this player. And this will create a new JS file for us to edit. So we double click on that. And we open up a code, it's up. So play games is co-editor is Monaco, it's based on Monaco. So I have all the shortcuts from Visual Studio that used to, sorry, Visual Studio code that used to also work here, including command shift P or control shift P, and that will give you all the operations that you could do within the editor. And also all the shortcuts come with it. Also if you want to go to edit preferences, you can change the font size, you can change, let me send this out a bit, you can change the theme. So if you want, we'll go to, yes, default, for example, that is a work on daylight. Sorry, I'm hearing beeps. So yeah, you could customize this to how you want and one of the common themes that we have. And if you go through, it's like what the script is. This is like the default boilerplate that gets created every time you create a script asset. And what this does is it will create a script, so when this code is run, it will create a script type within the engine called the player. So this is the string that gets passed in here is the name of the script type within the script registry in the engine. And what it returns is an object that gets instanced on every entity that has this script type attached to it. So if we attach this to, I'll go through this a bit later, but when we attach this script type to one of the entities, it will look in the engine for the script name, which is player and will create an instance of this, of this object onto that entity, which means that we can control that logic, which will last control, sorry, control, write custom logic for that entity all around it. Well, I'm going to.
8. Script Initialization and Keyboard Input
The initialize function is called once the script type is enabled for the first time in the scene. The update function is called once every frame. We access the keyboard manager from the application object and use it to detect button presses. We move the entity left and right using the translate function. The script component is responsible for updating and initializing all attached script types.
So by default, it will create, the boilerplate also creates an initialize function and an update function. So the initialize option, so initialize function will get called once the script type is enabled for the first time in the scene. So for example, if the entity is disabled on start, the initialize function won't get called until the entity gets first enabled and one web code be put in here and update will get called once every frame. So in play canvas, that would be once every request animation frame in the browser and will get passed DT, which is the time since the last frame. Let's say. And what we're gonna do here is we are going to access the keyboard manager from the application object. So if we go back to here, so go get back to documentation and the application class, we have a keyboard device that we can access and under that, we can use that to detect to say whether a button was pressed, whether it's currently being pressed and obvious pieces. Do you comment the last frame... So go question on Discord. The time since last frame is not in minute seconds, it is in seconds. So on a 60 heart screen, that'd be one over 60 effectively. Okay, where was I going? So yeah, and what you can do from each script type is access the NT that's attached to. So go to, let me type in. This dot NT, let's move it up. So that allows us to access NT that script type is attached you, but could also access the application object as well, which basically means that from any script, you could basically access anything else within the application or engine. So for this, what we're going to do is if this dot app dot keyboard and is pressed, and then we need to pass in a key code, which we have accounts which play count as constants for PC dot key. We're going to use the curse keys with left. Then we're going to do the same again for the right mouse button, but my thoughts are right right closer. Keep it right. So anytime we press this press yes it's time every frame we're holding down the left mouse button is anything within this statement will get called, everything will do the right. Cutlery so I thought left mouse button left mouse left puisque and right courtesy. Just get called well and from here we want to do is move the Nc left and right so under the. NCA Pie. Um, there's functions to do a lot of stuff so you can access any of the components under the NT as you can see here. But what we want is the. We want to use. Anything that moves position so we got to translate to go to translate function. Will always want to translate the graph node or the entity in world space through a vector so you can. We can say that every frame or every time is called move. Along whatever values we pass it. So for example, in this particular case example is saying when this function school translate move this NC 10 units to the right along the X axis. So that's so that's potentially in use, so this dot entity dot translate. And this guy left so minus X let's do the most end. I want to multiply Delta time so that no matter what they make them on says that. In real time. Is going to move at a constant rate. And we want to do the opposite of that for. Write write cookie. OK. I'm going to on screen for a little bit longer. But what I want to show now is what I got. My faults is you go back to the player character. I go to the root of it, which is this. The so the root object of the whole hierarchy we go at component. Script and then under the script. We attach the player. The player script players script type and this means that. This the lifetime of the scope of the script is now within this entity. So in conceptually we got the NT, the NT which now has a script component and under script component it has all these script types that we attached to it. That means that script content is responsible for updating initializing old script types are attached to it. So if the script component is disabled then all the script types are also disabled as well. So let's run that. Go to the blush tab, go refresh that. And if I. Use courtesy left and right we can see now we're moving. Um, in the actual that we wanted, which is like the front screen. OK, one of the things that we want to do is that probably a bit too fast. So we want to try and just the speed. So it's like kind of like nice speed so it's not too easy to get across the screen. Also not too hard either, and sometimes it's quite hard to dial in. So what we're going to do is how to enable the keyboard again, so go back to code. Uhm? And let me copy that into the chat, probably easier. I'm. Was going to say we're going to here. So what we can do is like we can manually adjust this to whatever number we have and then keep relaunching the tab is trying to write or we're going to do is expose this value as a as an attribute in the editor. So it can just stick here and so that way we don't have to basically keep refreshing the scene every time. So do that we can add an attribute to script and exposed out to the editor. Oh yeah, sorry, yes, I don't get saved the script so as we enter the script. This will go yellow and if you do a file save or command S or control S. It should go to white and that means it save and you can you can run it by the launch tab.
9. Adding Speed Attribute and Position Bounds
We add a 'speed' attribute to the player object and set it to 10. After refreshing the editor, we can tweak the speed value live in the ETSA. We also want to ensure that the character doesn't go off the floor, so we check the bounds of the player's position within the world and adjust it if necessary. We clone the position data to modify it without affecting the rest of the system.
I was looking at it. Go player. Dot attributes. But add. I'm positive name for the attribute. So when this name will also be. The property name under the disk object as well. Uh, so we do speed. And then we do K brace so we pass in the object which is basically a config. Say this is off type number. So what is in here is? Expose a property called speed is it's not a so that way we're going to put numbers into it and then we can use. Get we can access it by this object. Also got a minus. What's speed? OK, that's now saved and we go back to the editor. There's a pause button on script type and what that does is it will pass the code. Look for any attributes. Look for any attributes. But you've added a suppose that in the other so for sure. And then we got speed attribute here so we set it back to ten. Refresh this again. And. Move around and what we can do now is we can tweak this. Live in the ETSA. And I get direct and we see the update directly here in the lunch tab. So I split it to 5 somewhere between 2. And this is a nice speed, effectively. This is a nice speed, not too easy to get to the other side, so give us a little bit challenge, but also not too slow that is is a chore. OK. And now I've done this. Two things I will do now is that be great if the character was facing directions moving in. And also, we don't want it to go off away from the floor. Effectively, we basically want to stop the player about there. And there. So we're going to do. Let's do that first. We go back to the code. We're going to do is after we move to player we can get the position of it and then check the bounds of it within the world. And if it's gone too far, then we go back, pull it back, say this is as far as you can go. Um? And with that, if we go back to the NT API. Who do with top we have get get get get? We also have a get position so this will get the workspace of decision. This will get this position of the NT and then from that we can select compare. We can compare it to the bounds that we can set. And then you set position is gone set position. The products at local view set position to put it back in the right place. If it's gone too far. Let's do that now,if I get the position, so we do a. Const position. Equals this don't see. Bot get position. I'm also going to do here is going back to what I said earlier, where JavaScript is copy. Every object is copy of reference as a convertence, rather than modify the object that we get passed from get position. We can clone it so we get a copy of the position data that gives us which allows us to modify. The position variable here without affecting the rest of the system. So good. And then if position x is less than how big is the plane. How about this for scale 8 so about 4 units this way? My full. And then if position. But X is more than 4. Once at the bank so position dot X equals sisters. School belong and then we're gonna do is set the position back so this. And see. So position. OK, that's saved. So just recap. We're getting position or we can copy of the position of the entity. Uh. Less if it's gone too far to the left, and if it has fixed it onto to minus following world, if it's gone over to the right. Separate mirrors on the camera too far to the right then set position to four and then. Set the vision back to the NCS to ensure that the entities position within the bounds. So let's run that now.
10. Player Character Rotation and Animation
To make the player character rotate in different directions, a variable is created to check if the character is moving. If not moving, the character faces the camera. If moving left, it rotates 90 degrees to the left. The same applies for moving right. The next step is to animate the player character. An animation state graph is used to represent different states of animation and transitions between them. A new asset for the state graph is created, and animations for idle and movement are added. An anim component is then added to the character to enable animation.
Xena and better left dragons? I can't go further than the edges. OK, so next step is to have it rotate directions is moving in to make it look a bit nicer. I'm gonna do similar thing with go back to API separate size separation, set user angles, oil angles, always want set oil angles and like get oil angles as well. So could do pretty much the same thing. And the way I'm going to do this here is. Um? Create a variable to check whether I'm moving. So if I'm not moving onto face towards camera or face towards camera or towards the screen, and if I move in, then I want to rotate either direction. So what I'm gonna do here is. Uh, reset the reset the angle of. That's it. Uh, set with angles that would angles. So set it to 40 back to 000. So if we do not do know input is always gonna be facing the camera. And then. If we're moving left, we wanted to face. No, not that way, so we want faced 90 degrees on. We want to rotate it around the Y axis, which is up 90 degrees. And then the other side for right. Uh, so I can come off this in seconds. They want to copy this. You can get it directly from. Yes, wanted it message. I'll paste the. Pasty links to the code letter directly in the chat so that we can copy and paste if you need to, or copy of direct from it will go through showcasing this. So now if we move that right. I stopped moving the player now so I can copy and paste into documentation. OK, um, is that? There is a subject to do. I made it there. And I'm put, yeah, OK cool. So that covers this slide will add the player. We've done the input. And again, there's like links to other bits of documentation if you want to go further into this a bit later.
OK, next step we want to animate player at the moment. It literally is not. It's not animated, so it's docked still. Moving around the scene. What we can do is add some animation for when it's idle. Um, and when it's moving as well, so if it was, just gonna do like a little, like. Bounce when it's running or walking, there's gonna be a little bit more arm movement. I'm going to do this file. The anim state graph. So. I'm the anim state graph is. The TRD are well describing it. The anim state graph is a. Um? It's effectively a finite state machine that has multiple states that represents different states of animation and then transitions between two that have conditions to move between them and this allows like users to create side animations that are driven purely through data. So we're gonna do here. And if you go back to go open if you go to data panel assets models. Normal character. If we open up right click new asset and want the. And a state graph which is at the bottom for some reason. And this will create a new asset for the state graph and call this plan. Animation. My animation graph. And let's open that up by double clicking on it. OK, so this is graphic is all about. It's here, so it will give you initial state to start off with there a default state. So from the start of the animation it will go directly to this state that we said. And you could see what part you've got name of the state, the speed of it, and whether the animation loops. So just show you how it's put together first. So before we go into adding the different animations, if I go back to the editor, I've go hit the X up here. Come out here. And we're going to add because we want to add an animation. We're going to add an anim component to it to get an animation and go to anim. Not animation legacy. That's that's the old version of the animation system that's no longer support, but no longer supported. But it still works if you want to use that. But for this we can use the anim, anim component. And is asking us for a state graph asset. So click on pencil icon.
11. Adding Move In State and Transitions
We add a new state to the state graph called 'move in' and set it to loop. We create transitions from the idle state to the move in state and back. To prevent instant transitions, we use parameters and set a boolean parameter called 'moving'. We gate the transitions based on the value of the 'moving' parameter. In the player code, we create a variable called 'move in' to store whether we are moving or not. We access the anim component and use the set boolean function to update the 'moving' parameter based on user input. The state graph now transitions between idle and move in based on the 'moving' parameter. You can see the setup in the project link shared in the chat.
Asset. And you see here. It automatically populates the inspector with the state start in the state graph. So you see initial state here. If I go back to the state graph again. I call it idle. You're back to the an component. You see now it's been renamed to Idle. So this the list here reflects the state start in the state graph. And I've prepared some animations for the character here, which comes as part of the FBX pack that I imported prior to the workshop. And if we click on the pencil, click on idle and hit and refresh the launch tab, you can see here, or I say, you can see here, I don't know if you can see this through the video recording, but there's a little idle animation where the arms are just going up and down. Exactly. And that's basically us adding a default animation to the character, which works fine. But what we want to do is only idle if we're not moving. And we want to use perhaps one of the other animations like run or walk when we're moving left and right. And to do that, we go back into the state graph. Go right click, add new state, and call this move in. With this state, and we want this to loop as well. So for this, we want to be able to have the idle state. So have the state graph, be able to go from the idle state to the moving state and then move state back to idle because that's the kind of transitions we want in the Player4 animation. So right click, go to add transition, and then click on that. So we've now got a path here, and we want to do the same again back. So add transition, go back. So right now, if we click on one of the transitions, we can see a warning, and this says no exit time or condition set. This transition will activate instantly, which means that what's going to happen if we set this up with the player character is that it's going to bounce between states because there's nothing stopping. There's no conditions gating whether those transitions activate or not. So it's going to go to idle state, go right, there's a transition that exists and we're going to go to that, and then back again. So one of the ways we can do, well, sorry, one of the ways, the main way to gate this is through parameters. The parameters are values that we can set dynamically through code, to effectively control the parameters that we set reflect the behavior of the object in code, and that way the end of state graph can transition through the different states based on the information based on the values of the parameters that are set from code. I know that sounds complicated because I really have not explained that well enough, but showing is better than explaining in some cases. What we're going to do here is, set a new parameter for moving, make it a Boolean type, so it was either on or off, and what we're going to do here is, gate this transition based on whether that moving parameter is true or not. So select the transition that goes from idle to moving, hit moving, and then we want that to set to true, so if moving is true, it will transition from idle to moving, and we set the other transition, new condition, do the same again, but untick this, so this only goes back to idle if a moving is false. So the idea is that from code, we can tell the state graph that we can set this parameter based on whether the characters are moving or not, and this is the logic in the finite state machine for the state graph will automatically move from one state to the other. So let's close that, go back to a player character, set up an animation for the moving state, let's use run. And then within the player code again, what we're gonna do here is create a variable to know whether to store whether we add an input or not, we're moving or not, so move in, equals false. And then within our input controls, we need to start moving to true. So true input. And then what we're gonna do is access the, what's that? We're going to access the anim component through the entity, so that's entity.anim. And then from here, I believe we've got a set boolean function, give it the name or the parameter, move in. and set that to our variable that we set up, that we stored the bool. So now that parameter, or move in inside graph, is going to directly reflect what logic is going through, what logic we have set for the player, which basically is, if we're holding on a key, it's moving. If we're not holding on any key, it's not moving. Let's go back to here, refresh. And you can see now that it's going from the idle state, we're moving now going to the move-in state, and back again. And if you want to give this, let me quickly share. So you want to see the setup. I've sent the link into both chats again, of the project that I'm currently working on. So you can either fork from it, or look at it more closely as I'm talking.
12. Adding Falling Ball with Physics
We adjust the duration parameter in the image state graph to create smoother transitions between animations. We then move on to adding a falling ball with physics using the Ammo physics engine integrated with PlayCanvas. We give the ball a rigid body and physics properties, as well as a collision component. We set the ball's type to dynamic so it will fall naturally.
Okay, so you've probably noticed this as well, it snaps quite quickly between the animations as it goes instantly from idle to run in, and run in back to idle again. And that's because in our image state graph, we have in the transitions a duration value that's set to basically empty or nothing in this case. And that duration parameter, that is saying how long the transition lasts for when moving from one state to another. And when it's in that transition period, it blends the two animations together to create, so it's got a half-weighted between the two. And if we set a value to it, so let's say we set a duration of, let's say not quit two seconds, it means, but not quit two seconds, so it's going to blend the animation from idle with animation on move in, and that creates less of a jarring snap to it. So we do that for both transitions. And go back to the launch tab, refresh, and move. You can see that it no longer snaps. It basically interpolates the position that was in before to the position that it needs to be in for the next state and blends between them for the duration of that transition. So that creates a slightly less noticeable snap and it's a lot more pleasing to see.
Okay. Cool. Any questions so far before I move on? While we wait for questions, it's worth noting that the animation state graph is actually quite a big feature of PlayGammas and I can probably do another workshop alone on it. So there are some tutorials and some documentation that probably worth reading if you go heavy into the animation stuff. Move on. And what we're going to do now is, sorry, excuse me, add the falling ball with physics. So this is the ball that's falling from the top and the ideas that you bounce off it. So here we're going to use the physics engine that's integrated with PlayGammas called Ammo. And Ammo is a JavaScript compiled version or an open source physics engine called Bullet. So it's quite mature, a little bit old now, but it's incredibly mature and it copes with a fairly introduced standout. I think Unity used that at one point as well. So we're going to do that. We're going to add the ball model to the scene, give it a rigid-body inclusion and give it physics properties so it will fall and then add collision to the rest of the scene so that it can collide with both the player and the floor. I'm going to talk about a little bit about the different types of physics objects that you have.
Okay, let's go. So I'm back in here, exit the anim slight graph. So if I go back to our assets panel, under models, I do have a blue ball. That's not the best material. I have a blue ball here. And what we can do is same thing with a player. We're going to add it to the scene. So right click, template, add instance, select the 14 red template. And I've got a ball. I just move it up just above the player for the moment. And what we're going to do here is give it the physics property so that it can fall and collide with all the stuff. And as mentioned before, this for I did any say new behavior to empty is going to be done for your components. So go add component, physics, we're going to give it a rigid body. So the rigid body represents the physics of teas, of how heavy it is, how bouncy about the how much friction it applies. And so how heavy is, so how much friction it has been moved across other objects and restitution is basically how bouncy is. So the higher the restitution, the more bouncy it is. The other thing we wanna do is also import the ammo module. So the ammo module is a standard bit of code that effectively runs and manages the physics simulation itself. So click on that, and you can see that as a new folder to that panel called ammo.js. And that should go away, that's a bug. What we also want to do is add a collision component. So the collision component represents the shape of the physics object. And from here we've got all these primitive shapes, box, spheres, capsules, cylinders and cones. And there's a mesh is, if you want to use a model as the collision mesh, so basically, maybe you've got an environment that you wanna set, I generally avoid those when possible because it makes this simulation quite slow. And compound is when you put together multiple primitive shapes together as one rigid body. Again, we're gonna cover that today, but we'll just cover just using normal primitives. So for the ball, we want the sphere. I could just about see here, it's like how big the shape is effectively. We space on the radius here. So what we can do is I edit the radius of the ball by directly changing the number. But what I prefer to do is, if it's any number field in the editor, we'll have a value dragger. So you can see on the far right. And if you left click and hold, you can move your mouse left and right to change the size of radius or change the value in the field. So I want to make this about one, I think. There you go. So now it pretty much encompasses the size of the ball. And we want to change the type. So at the moment is static, which basically means that, once created in the world, it can never move. And you will do that for things like scenery, environments, anything that will never move for the lifetime of the entity. We've got dynamic, which is, it's gonna be fairly similar by the physics world, which means it will, gravity will apply. The only way you can move it is through forces and impulses. And we've got kinematic, I think that's how you pronounce it. And that is where you can position the physical object no matter where you want in the world, as they can move it and it will react to other dynamic objects. So if you move a kinematic object to a dynamic object, the dynamic object would move, but the kinematic is not affected by collisions, forces, impulses and whatnot. It is always placed where you put the position of the entity. For the ball, we're gonna put use dynamic so they will just fall naturally.
13. Adding Physics and Creating Game Balls
To add physical properties to the floor, we can add a static rigid body with a box shape. The character can have a kinematic rigid body with a capsule shape. Adjusting the offset allows us to control the position of the shape. Changing the restitution of the ball gives it more bounce. Next, we'll create a game script to continuously spawn balls using a template. We'll create a new template called 'game ball' and add a 'game manager' script. The 'game manager' script will reference the template asset and instantiate it at runtime. We'll expose an attribute to assign the template asset to the script.
So now we go, if we launch this again, you can see it falls and it goes through everything because nothing else in the world currently has, what do you call it? Currently has physical properties to it. Sorry, just watching time. So what we're gonna do now is for the floor, we can add a static rigid body because the floor is never gonna move and use a box to define the shape. So we select the floor, as we did here, go to physics, rigid body, collision, static spline, that's good, and at the moment it's just standards like one by one box. So we're gonna change this to be half a set, so change that to four, and that's four again. And we can see it's encompassing the floor but the top of the box is higher than the top of floor we want to bounce against. For this we can use position offset. So again, if we go over the Y, what this will allow us to do is move the shape as a offset to where the entity is positioned, which is quite handy for times like this. So you can see if I drag this up and down, I'm moving the shape based on the offset value. So what we'll do is just put it at there, 0.5, refresh this, I can see now it bounces and collides with the floor. And now we're going to do the same for the character. So the character, because we're moving it directly for the entity, it's fine to have it as kinematic. So we're gonna have this as a kinematic type because that way it will move wherever we set the entity position to, which we've done through the player script. So, again, we're gonna do the same sort of thing. Physics, rigid body. Physics collision. Go to capsule. And there. Change it to kinematic, and, again, you can see here that the default position of the shape is where the floor, as where the position of the character is, which is at the bottom, we can do the same thing as with the floor, which is to change the offset. So now if we launch, there we go. Okay, don't know what happened there. We can see now. We can move the ball by moving the planet into it, move the left, right, so et cetera. So that's kinda a basic TLDR on physics, effectively. So we're gonna reset again, zoom out of the way, bounce it. I also want the ball to bounce a bit more as well, because now as I'm hitting the floor, it's just like, almost hits it dead, and that's a bit boring. Can I sort of jump around? That's a bit boring. So what we're gonna do is on the ball, go and change the restitution to be the maximum, and that should give it a bit more bounce. There we go. So now it feels a bit more like a beach ball, where it's like bouncing. It feels a bit more floaty, feels less like a heavy, dense cannon ball that just falls on the ground. Okay. Buh buh. That'll cover everything here. Yes. So any questions so far still?
Okay, so now that we got one ball before the ground, we want to continuously spawn balls over time. So again, make it into kind of infinite game where you want to keep the balls away from the floor for as long as possible. And we can do that through a template. So what we're gonna do here is, make the ball into a template, make the ball into its own template. And then, oh, so excuse me. Now is the camera right? Create a game script that will create clones or instances of that template over time into the world and basically turn it into a bit more of a game. So assuming things is turned into this template, create a game script and create clone balls or create instances of balls over time. So back to Yedza. What I'm gonna do. We're gonna do here is, although it's a template already, we're gonna create a new template for the game. We don't want to mess around with the model template just in case we use it later in the app. So go back to, select assets, go to templates. We right click on this. We can create a template and add it to the asset registry by going new template. And we could change the name of it into, let's call it a game ball. Also I changed the name of that, not this. But let me change the asset and then change the name of the asset. There you go, game ball template. And we're going to add a new script in scripts folder called game manager. Okay. And for ahead of time what we're gonna do is add the game manager to the root object so that it can execute code and click on the pencil button to edit the script. Okay. I don't remember what I wrote here. What we're going to do here is reference the template asset from the script and then from there, pull the functions to instantiate the template. So to create the instance of template run time within the world. And as well, to have a quick check on how to do this. So to do this, we're going to expose an attribute to data. So that we can assign the asset to the script or to reference the asset to the script. We'll add and call it a ball template asset. And then the type is asset. And then the asset type is going to be template. The second bit is actually optional. The second bit is actually optional, but this allows us to ensure that on the Editor, you can't assign any assets to the script, you can only assign the asset of this type. And that's usually useful if you're working with Teams and you want to ensure that it's more difficult to do something wrong or do something by accident. Save that, go back to the editor, tab pass it, and then again we can do use pencil icon.
14. Adding Template to Scene and UI
We instantiate the template into the scene by accessing the data in the asset. The asset is a wrapper around the actual data, and we can do this using the 'resource' property of the asset object. By calling the 'instantiate' function of the template data, we create a new instance of the template as an entity. We then add the template to the scene by making it a child of the entity that the GameManager script is attached to. We create a timer for the script that decrements each frame, and when it reaches zero, we create a new instance of the ball. The script uses the 'teleport' function of the rigid body component to set the position of the ball, and a random function to randomize its X position. We remove the existing ball from the world and launch the game. We then move on to adding the UI to the game.
Find the template, you see it, and now we can reference this asset directly in code. I can't remember how I wrote this, how did I write this, oh, man. Okay, so to instantiate the template into the scene, what we've got, oh, what we have to do is access the data in the asset, cause as I said earlier, an asset is a wrapper around the actual data, and we can do this via this dot asset. So this represents the assets object and within it, actually, yeah, probably best if I go to the documentation for this. So here's the asset object and what we've got to do is go over to the documentation. So here's the asset object, and within it, there's a property called resource, and this is a reference to the data itself, so in this case, it's a reference to the template data, and the template data has a function called instantiate, which will create, or create a new instance of this template as an entity, and returns that entity back, so back to you in code. So we're gonna do that, resource dot, and I can't spell instantiate, I never can spell instantiate, so I'm just gonna copy and paste that. So by calling that, I've, oops, got a const and C. So by calling that, I've now created an instance of this template and have that as a variable here, and what I also need to do is add that template to the scene because right now it's just an entity. It's not attached to the scene graph at all, and we're gonna do this by entity.reparent and say make this entity the parent. So what this line is doing is taking our clone or instance that we just created and making it a child of the entity that passed into the function of reparent which is itself. So any new ball that we create at runtime is going to be under this entity that I've attached the GameManager script to, and if I save this and run it we can see is a ton of balls effectively. We'll start, that's the wrong tab. If we run this you see a lot of balls cause we're effectively creating a new physics object each frame which is not what we want. What I'm gonna do is create this overtime. We want this great, maybe new one every X number of seconds. And we can do this by keeping track of the time that's passed, and then a timer, and then once that reaches down to zero then create the NT at that point in the app. So what we're gonna do is in the initialize function is add a new property to the script type, called this dot, what do you call it? Time til next ball spawn. Was it zero, what have you done to see the asset? Sorry, Alexia, I don't follow. Okay, it's found it, okay. Again, if you do have problems, if you do have a problem please don't hesitate to give me a shout out either on discord or Zoom, and if you can send me a link to your project and I'll be able to access that and look at it live on stream and go through the issues that you may be facing. Okay, so, what was that? That's straight forward. We're creating a new property called a time to next spawn, false spawn, and we're gonna use this as a timer for the script. So when that falls to zero, we're gonna create a new instance of the wall. So what we're gonna do here is if this.timeToDex spawn is less than zero, okay, then do this. And what we're gonna do is also set the value to be, how many seconds? Mm, let's say every two seconds. And then also every frame, we're going to remove the time since the last frame has passed. So basically, this timer is decrementing each frame by the time that's passed this last frame. And then when it hits zero, it's going to create a new ball and then start again effectively. If you wanted to, you could technically use set interval or set timeout, but the reason why I prefer to use DT especially is because that in PlayCanvas, there's a property under application which allows you to scale time. And this means that you can run it at double speed, single speed or pause it, I use pause very loosely here, pause it by setting time scale to zero, which means that every time, every frame DT's zero effectively. And this allows us to do, again, like things like pause in, which set interval or set timeout won't allow us to do effectively. So let's run this now. So we've got the ball that was already there and then that every two seconds, we now have a new ball that spawns. So, you see here that it's always spawning in the same place, so next thing we wanna do is move it to a random position and also move it to be above, move it to be above the screen because it appearing in the middle of nowhere up there is not great. So let's close that, go back to code. What we're gonna do is, because it's a Physics object and it's dynamic, we can't do what we do with the play object, which is NC dot set position and put a position there. That won't work, unfortunately, because as it's a dynamic Physics object, the NC position is driven by where the rigid body position is in the Physics world. So that means even though we set the position here, because in the Physics world it's been put into a particular position, that will get overridden pretty much in the next frame. What we want to do is use the Physics functions under the rigid body to move the object. So go back to the documentation again, where they say Body component. There is a function called Teleport, there you go. So under the rigid body component, as a function called Teleport, which we can set the X, Y, Z position or the rotation and these Euler angles for it, and it's what we're gonna use. So we want to go from the entity that we cloned, get the rigidBody, rigidBody.teleport. And where do we want to set it? For the moment, let's just set it up into the sky. Eight maybe? So this will set it at 080. How does that look? So yeah, that sets it out of frame, which is good. Now we want to randomize it across the X position. And we can do this through a random function that is provided by play canvas. So go pc.math.random and minus three to three will do. So every time it's called, it's gonna be a random number between minus three and three. Refresh that. And now we've got the beginning of our game. Okay, so I'm being mindful of time. I've done quite a lot. Okay, cool. So that's our step. Oh yeah and finally, we want to also remove the one that already exists in the world. So we delete that, right click delete. Launch that. So we've got one that is out there and because now we've got the shadow, they'll shine directly down. You can see where they appear before they appear on screen. So you know where to move the character. Cool. Let's close that and move on. I might need to speed up a little bit cause I've got 45 minutes and I've got a few more bits to go. Ah, UI. Ah, okay. So, next stage here is we're gonna start adding the UI to the game. So, go back a bit.
15. PlayCanvas UI Positioning
The UI system in PlayCanvas is a big feature that allows you to position UI elements on a 2D screen. UIs in PlayCanvas consist of PlayCanvas screens and elements. Elements are entities that represent images and text, while screens represent the canvas. Elements are positioned based on anchor position, local position offset, and pivot position. The anchor determines the element's position relative to the parent element or screen. The local position offset is the distance from the anchor to the pivot, and the pivot position is relative to itself. This allows for responsive UI design and anchoring elements to specific corners of the screen.
The UI I'm talking about, oops, too far. The UI I'm talking about here is the heart icon and effectively the health counter that I've put in top left. Oops, I don't want to. UI is... The UI system is quite a big, again, another big feature of PlayCanvas and I could spend quite a big workshop on this. So, I'm going to effectively skim the very basics of it, which is how to position UI elements on screen. When I say UI elements, I mean things like images and text, how can you position them in a 2D screen? But, again, we got documentation that we will dig further into that. But I just want to cover the position, how to position it, how the different properties that control where the element gets rendered. Because that's tend to trip people up quite a bit.
So, let's go to the next slide. So, UIs in Play Canvas are made up of two objects, Play Canvas screens and Play Canvas elements. Play Canvas elements are entities in the scene that represent images and text effectively and the screen represents the canvas that you're rendering Play Canvas to. And the elements are positioned based on like three parts. The anchor position, so the anchor position relative to the parent element or screen. The local position offset from the anchor to the pivot and the pivot position relative to itself. And that probably makes absolutely no sense because it's very hard to do this purely from text. So I'm gonna open up an example scene to better show you this. Okay, so this is one of the example scenes that is from the Play Games documentation. And if we go zoom out there, you can see here, here's the UI representation. So if we go, another thing, so in the editor that I've got to show you is if you go top right, we've got different cameras to select from, the normal one, the default is perspective, which is the one we're using here. But for UI, I tend to use front. And then if I zoom out, that makes it easier to look at and move stuff around.
Okay, so what we have here is an EDZ with a screen point to it. And that's in screen space, which means that it's always gonna be covering, it's always gonna cover the screen. If you disable that, then it's a screen in 3D space, in world space effectively, which means that you can't walk around it, it's affected by camera perspective, et cetera. But screen space is all gonna be rendered directly on to the screen. And then it's got a scale blend, which we'll talk about a bit later, and a reference resolution of, of I say the units that the screen represents. Generally, I don't mess around with anything but blend mode and priority for this. What can I say? And as children, each of these entities that I've selected here are entities with element components. And as I said earlier, they represent images. So this one in particular is a type of image, which you can sort of see there. And it has properties, such as anchor, pivot position, width, height, color, margin, et cetera. And I will go through these as much as I can on this workshop. And text is more or less the same, the same sort of properties, but with additional bits to it, which is what fonts are using and the text that is going to be rendered. So if I select, actually, and let me make a clone of this so I can actually edit stuff. UI clone. Can't fall faster, there we go. Here we go. Font. There we go, great. Okay. I've set the image logo. You can see that it's positioned to the top of the screen but.
Okay, I'm trying to figure out how to go. Okay, so I've read that. You see by the image logo that we have the anchor set of... We have a preset of top anchor and pivot. This means that the anchor is set to be at the top of this parent. So the parent is the screen. If I move this down, you can see the anchor represented by these four triangles. That's because it's positioned directly in reference to its parent. So the parent is the screen, which is this big rectangle here. And because the anchor is set to be at the top, so one and one, it means that's always going to be anchored to the top edge of your screen, no matter what. So if I resize the window, the element's always going to be positioned relative to the top edge of the screen. And if I go back to the bottom here, these are anchored at the bottom. So no matter what, these are reference offsets to the bottom of the screen. So if I undo this and launch it and resize my window. So take a look at the distance between the top of screen and the image, that never changes. No matter what I resize the window to, that never changes. And same with these elements at the bottom, they're anchored to the bottom. So even if I make this smaller, the space between the bottom edge and the position of this, never changes. Oh, that's it. And this makes it very useful to do kind of a responsive kind of UI. I use the term responsive in terms of, responsive to the changing of the screen size, not so much responsive in CSS terms. And this allows us to do things like anchor things into particular corners, so in our case, we have the health bar on the top left, so no matter where we go, how we resize the screen or how big the screen is, it's always gonna be offset from the top left of the screen. Sorry, I lost my train of thought. It's always gonna be a reference to the top corner screen, and we can configure it to be aligned to anything else that we set it to, and this makes it also useful for doing things like dialogue. So if we wanna do a dialogue with a close button in the corner, so we'll do a quick example here. Set the width to 500. Yeah, there we go.
16. UI Elements and Anchoring
Learn how to anchor UI elements to specific corners, create responsive UI designs, and use blend modes for different screen aspect ratios. Two separate layouts can be used for portrait and landscape orientations. In the project, add a heart icon to the top-left and text to the left. Anchor the icon to the top-left corner and change its color. Add text using a font asset and customize it. These techniques ensure that UI elements remain fixed and properly scaled for different screen sizes.
And set a child of that, another style, and go to element. Change that to blue so we can see it. Zoom in a bit. What I can do here is anchor this button, this blue square, to be always in the top corner of this element. So no matter how big or small we make this image, this image element, it's always gonna be fixed to this corner. So if I change the preset to top right, anchor and pivot, you can see that it's fixed to the top corner of the parent. And now if I change the size of this, actually the size of this to let's say 300, it's always gonna be fixed to that thing, no matter what I do to it. So 600 and this makes it very easy to just really quickly put together sort of a UI that always conforms to a particular shape or always stays regular no matter how big the element is. And no, I've not done that pretty well but it'd probably get easier if I do a proper example in the workshop projects. Oh, the other thing I want to talk about is the blend mode. So if we go back to this tutorial, for example, oh, no, go on. You got clone, same thing. Blend mode always I never use non always use blend mode. It doesn't it doesn't necessarily work because again I like the foremost the most important thing is that it works. And then if I like this, so in blend mode obviously I always never use non always use blend mode. And there's a value between zero and one that is used and this controls how, how the elements of the screen or the screen is scaled based on screen aspect ratio. So I'll go back to the slides here, at blend mode zero. You can see that the elements don't change size when the height changes, but to do scale up and down based on the width and that blend one is the opposite. They change size based on height, but not on width. And this is useful if you want to do UIs based on portrait or landscape style games. The example, if you're doing a mobile game, that's always your portrait in a messenger app, I generally use blend mode zero because phones annoyingly vary ranges of different tallness in portrait mode that always use blend mode zero so that the elements don't change size no matter how tall the screen is. Especially if you've got a really tall bone that I have here. It allows you to create UI that is anchored and doesn't scale based on portraits. So you can make sure the UI works for 16 by nine, 31 by nine and everything in between. And let me do that. And blend mode one is the opposite. I do this for landscape style games, especially for desktop and browser games. And then the value in between take different weightings based on it. So 0.5 is a combination of both where UI elements are scaled no mat, are based scaled if you change the height or the width, but they're balanced between the two. And the other thing is if you need a UI screen or UI layout that works for both portrait and landscape, the recommended practice I have is you do two separate layouts. So you switch between. So in this example here, I've got a landscape layout, where you can see the logo and the name at the bottom here. And then when it gets to a certain size for portrait, I switch to a portrait style layout where the logo and the name is put at the top. So yeah, I will go through this, go through the workshop, again, I'm running short of time. I will need to do this on the fly. So I talked about this before, where you can, elements can be anchored to the parent, whether the parents are element or a screen. So it's useful for dialogues, if you want to anchor a certain UI to different corners. Let's go back to this. Sorry. Whoops. Sorry, bare with me. Okay, so what we're gonna do here in our project, so I'll go back to the project again. I'm gonna add a heart icon to the top-left and some text to the left of that as well. So in the hierarchy, right-click, user interface, and we go want a screen. And we can move that screen out of the way so it's not over the top of our game area to make it easy to edit. Change to front, and we want to right-click and add a new UI element to it. Image element. And from here, we can either add texture or sprite. So we're gonna cover text today, which basically means any image we upload can be used here. Go to UI, and I'm gonna drag and drop a heart icon over the top. So, and from this point, now Element will render our PNG that we uploaded before. We'll change that to an icon. And we'll change the preset from, we'll change the preset from center anchor pivot to... Oh, I forgot. Top left anchor pivot. So now, it's anchored to the top left-hand corner, so no matter how we re-size the screen, it's always gonna be fixed to the top left-hand corner. We also want to change the color. We also want to tint it as well. So I'm gonna grab that gray color. Where have I gone? I'm lost in my own tabs. Make that N. So a nice gray color, and we've launched this again. It popped up top left, which is what we want. And I'm gonna do the same again, where we're going to add the text. So right-click again on the screen, go to the User Interface, Text Element, and by default, I've already uploaded a font to our Asset Referee. So by default, it selects the first one it can find. And what I've... And so how fonts work in PlayCanvas is that when you upload a font asset, like a TTF or I think OTF support as well, it creates a separate font asset, which is effectively a texture atlas of all the characters. And what you can do is... So by default, it gives us the standard, what's it called? The standards ASCII range, which is A to Z, all the numbers, et cetera.
17. Adding Sound Effects and Collision Detection
To support other languages, add the glyphs for those languages. For example, add kanji for Japanese, acrylic for Russian, and the necessary cycles for Chinese. The default settings are fine for using numbers. Add two PlayCanvas UI elements anchored to the top-left corner. Documentation on anchors and pivots is available in the user manual. Next, we'll add sound effects for ball collisions. Not all browsers support all audio file formats, so choose WAV or mp3. Add sound effects in the Asset Panel and modify the ball template to include a sound component. Add a script to the ball to detect collisions using the rigid body events.
But if you want to support other languages, you need to add the glyphs for those languages here as well. So for example, if you want to add Japanese, you have to add the kanji. Russian, you add acrylic. And Chinese, you have to add, again, all the cycles that you need to render on the screen. But now the default stuff is fine because we're going to use numbers.
So we do the same again, where we change the preset to use top-left-anchor and pivot. And we're going to move it just to the right of that and change the font size to be 72. There you go. Move it up a bit. And again, change the color to be that gray color. And by default, let's put 100 there. Okay, come back to this. What we've done, to recap, is that we've added two PlayCanvas UI elements to a PlayCanvas screen and anchored them to the top-left so that no matter what size our window is, whether we go to portrait or landscape, it's always going to be fixed to the top-left-hand corner no matter what. What else is that? Yes. Again, there's some more documentation about how the anchors and pivots work in more detail in the user manual, and we go into further depth about how the pivot is related to the positioning of the actual, render bit, which is like the text or the icon that I don't have much time to spend here.
Okay. We are going to update the text later down the line when we … We're going to update the text when the ball hits the floor a bit later in the workshop, but first I want to add the sound effects, maybe because it feeds into some of our features of the engine. Okay. Where am I going? For now we're going to add sound effects. When the ball collides with both player and the floor, it's going to produce a sound, effectively. And from here you can use any audio file that's supported by the browser. But the frustrating part is that not all browsers support all audio files. So for example, I think OGG is only supported by, I think, IOS Safari, which is a bit frustrating because IOS Safari, which is a bit frustrating. So generally people use either WAV, which is quite large in size, or mp3. So here in Assets, I've already uploaded a few sound effects. I've got Impact Plate and Impact Wood. And we can play them back in the Asset Panel. For the moment, we're just going to have the same sound effects for every collision that the ball hits. And to do that, we're going to need to add a ball script to the ball template, so that's, sorry we're gonna need to modify the ball template to have a some functionality to know when to detect when it's hit something. And two, we need to add a sound component to the ball. So what I'm gonna do here is go back to templates, add that back to the hierarchy. So you can do that by dragging back. You could drag in the template asset into the hierarchy or you use what I've been using before, which is right click template an instance. And then here we're going to add a sound component. What's that sound component one, audio and sound. And what does this side features that we won't be using today? So we can do position of sound where it will play the sound based on position from where the sound is emitted from, I'll do where NTS to where the camera is. But in this case, we're fine for just a screen space. So we're gonna disable that. And we have different slots. So a sound component can have multiple slots with each slot representing a different sound. So we do. So for the moment we want one slot. We changed it to collision. And we're gonna find the audio asset. And we can use plates I think. For this. And got other options here. We've got autoplay, which means that as soon as the sound component is enabled or its first instance in scene, it will play. We don't want that. What we do want overlap and overlap will mean that you could play the same sounds lot multiple times without stopping the previous instance of it being played. Because if it's on ticked and it's has been played and you play sound in the middle of that sound being played what will happen? We already have the previous sound and the play the new one. But in our case, we can't want them to overlap because we can have multiple bounces in one case. So we're going to tick that. And now we want to add a script to the ball that allows us to play the sound effects. So if we go to scripts and new asset, go to ball.js and in on the NC, add component, script, add ball, and click on edit. Okay, so what we want to do here is listen for the events that the rigid body will give us for collision. So we go back to the API again, the rigid body, scroll down a bit to events. You can see the name of the events that emits from the rigid body that we can listen to for the collisions. When the collision starts, so when the first hit. Collision end when they first leave and contact and trigger. So triggers we're not covering today. We just can use collision start. And we do this by, first create a function for the callback. So on collision. And it will give us data. And then, in the initialize function, we're going to say, listen for this event on the rigid component and call this callback. So if this.entity.rigidbody.on. And then name of the event, which is collision start. We're gonna call this on collision function. And we'll be calling a callback of this event.
18. Game Templates with Sound and Collision Effects
We pass the scope of the objects called of callback on to ensure the function is called in the correct scope. We play back the sound effect when a collision occurs. We make changes to the template instance and apply them back to the template asset. We differentiate between hitting the floor and hitting the player using the tag system. We add tags to entities and query them using set operations. We add a new sound slot for the player and attach the sound to it. We hear the player sound when the ball collides with the player.
We need to also pass in the scope of the objects called of callback on, again, cause we're using ES5. We don't... The API... So because the API was written in during the ES5 period, it doesn't naturally support a nominal functions or D-crypt Arrow syntax. So, best pass at the object that you want to function to be called on, which is gonna be the this object. That means that it's gonna call this function in the scope of this object. I'm gonna play back the sound. So, if I'm recorrectly it's Play I think? Sound component... Play. So, it's got a Play function, we're passing the slot name, and that's pretty much it. Nice and easy. this.nc.sound.... That's nice for sound. Play. And what was it called again? We called it collision I think... I'm just double checking the name. Yes, we called it a slot collision.
Okay, so now... Now I do all this as a template, what we need to do is, now we've added these changes to the template instance, the part to note is that we've only made these changes to the instance, we haven't actually made the changes to the template asset. And you can see these highlighted in blue. So anything that's blue is saying you've made a change, this change is only being applied to this instance, not to the template that we created earlier. But since we're creating Bools dynamically at runtime, we do want the changes back on the template. So what we're gonna do is at the top of here, we've got some options for the template where we can first see what the changes are, and then we can also apply the changes back to the template asset. So if we click on view diff, we see we've added a new script component, a new sound component, and if we click on the blue dot, we go Apply to Gameboard. So it means like, okay, we'll apply the change back to the template. So close this now, we can see on the script, this is no longer in blue, which means that this is a property from the template asset. And we can do the same for the sound. So now the changes are applied back to the template. So now when we go to runtime, and Instance Templates, it will have the changes that we just created. So let's delete this object, and run this again. And what you should hear, I don't know if you can hear this through the recording, you should hear the sound effect every time we collide with something. Awesome. Okay, I'm gonna speed up a little bit here, because we only got 20 minutes left and I want to leave some room for questions at the end.
What we want to do now is, we want to have a different sound effect for the player, so we can differentiate between, oh, we've hit the floor, versus we've hit the player. And we can do this through the tag system. So every entity, every asset, so let's see now, has a tag property, and this tag is effectively an array or a set of strings that you can add to the entity. And the tag system is actually quite powerful because, let's go on, tags. What you can do is query if an entity has a particular tag or a combination of tags or an exclusive operation of tags as well, so it works like a set and you can do all the standard set operations with it. The common one is, so we go to Has, you can set a query that says, okay, you can either say, oh, does this does this set of tag have the player string or does it have a player string or a mob one or does it have the mob and a level? So you can do all these set operations to make, to use this tag system as a powerful query system when you have a bunch of entities that you want to know more about. And now let's categorise entities in the scene more easily and query them at a later time. So we go to where am I going, go back to the player objects. I want to add the player tag. Go to the floor and add the floor tag. Nope, you can stop playing sounds. What we're going to do here on the collision is take the data from the event. So the collision start event, so collision callback and event data of what's collided with the position of the collision, et cetera. So let's go back to rigid body again and go to that callback. So it's collision start. Parameters that it gets past is a contact result. And in a contact result, we have contacts. So array of contact points with the other entity. So that's all the points that it's collided with and also other, which is the other entity that's collided with. And that's what we're going to use. that's not one, two. Either other and then go tags as layer else play the default one. Okay. What we're gonna do now is add another sound slot just for the player in the ball. So let's bring that ball template back in again. Add a new slot for player and attached the sound to that. Ordinate wood and overlap. So pretty much same thing as the previous slot but with a different name. From here, I'm gonna say play sound of slot player. Saved, let's launch it. So now you can hear when the ball collides with the player. Nice. Oh, where's my player sound gone? Let me refresh that again. That is odd. Okay, I'm gonna have to look at that later. Oh, I know why! Sorry, I know why. It's because I've got to apply the changes back to the template asset.
19. Updating UI Text and Handling Collision Events
In this part, we added collision events to update the UI text when the ball hits the floor. We set up a callback function in the game manager to decrement the player's health and modify the UI text. We used the PlayGround Event System to send an event from the ball to the game manager. We added an attribute to the game manager to reference the UI text entity and modified it directly in the callback. We listened for the 'damage' event using the application object and registered the callback. We also implemented cleanup code to unregister the callback when the script or entity is destroyed. Finally, we checked if the ball collided with the floor and fired the 'damage' event to update the player's health and UI text.
So that's what we need to do. Bluedeath. That's a game ball. So refresh this... There we go. That's better. So now every new ball that's created... Wow, that's ha... Well, that changed and you can see now that earlier player is like a bonk, and then when it's a ground it's a plonk. Okay, so let's delete the ball that we've been playing with to edit on, so that's no longer in the scene. So if you go, if we... Okay, Alexey has asked how do you add another sound again? If you go to the game ball template and drag it into the scene and go down to the sound component, you can... There's a button at the bottom that says add slot, so you add a slot put in the name of player, put in the sound, put in the sound asset, click over that and then apply the change back to the template and that should be it. Let me know if you have issues, if you do, I'm happy to open your project and take you through it. So just hang on chat. And... Are you good Alexei? Cool, he's good, awesome. Let me delete my ball again before I forget. Okay, cool. Yep, okay, so that's that done. We talked about entity tags as well. What we want to do with the entity tags is use that to update the UI of events for gameplay. Again, this is the part where we're going to update the UI on the top left, so every time it hits the ground, this will decrement. It'll get lower and lower. So let's do that now. The way we're going to do that is through the PlayGround Event System. So what I'm going to do here is, on the collision function here, if it hits the floor, then I'm going to send an event that the game manager is going to listen to change the UI. So let's go to the game manager first and set up the callback to change the UI text every time we get an event from the ball collision. So what we're going to do here in the attributes, we're going to add an attribute that will reference the entity for the UI text, and then modify that directly on the callback for the event that we're listening to. The gameManager.attributes.add UIText.Entity. Save that. And we go into the gameManager and reparse it, we now have an attribute in the editor where we can drag or select an entity for that property, so now we can access the text entity in the script of our this property UIText.Entity. So let's add a callback for on collision, sorry, on collision, on damage, let's call it onDamage. So function, and what we're going to do is keep track of the player help. Then onDamage, we're going to decrement that by five, and what we're going to do is for the text UI textency, is reference the element component, and in the element component there's an API to change the text called.text which is only available if you have an image that's of type text and changes to be this.playerhealth to string. String to string, and now we want to listen for the event, now we want to listen for the event that's going to get fired from the ball. We're going to call this damage. So a common pattern that developers use is to send the event through the application object because the application object is accessible from any game script, from any script type, sorry. So that means we could do dot app dot on, name an event, the callback which is on damage, and the scope that we pass it into. But the thing to remember here is, we won't run it into for this particular demo, but if you have multiple scenes or you're destroying entities in the scene is that this doesn't clean up, this doesn't clean up itself automatically when the script or the entity is destroyed. What will happen is, what will happen is, when we register this callback, you have to also remember to unregister it when the entity is destroyed, otherwise you're gonna have this dangling callback effect. So, because the function still exists, but the scope data doesn't, and you generally run into errors, where you listen to this global event, this event on an object that has a longer lifetime, which is the app object, you change the scene, which destroys all the entities, you load new scene, and if the event gets called again, this callback will still get fired with this code, but stuff that's referencing will no longer exist and we just get an inception error. So in short, we have to remember to listen to destroy function on the script type, so dis on destroy, I'm going to call a not function, which I spelled function wrong, with skip of this, and then off it, so they stop listening to damage event. And this is the fallback that we want to unlist with. So that means that when the script is destroyed or when the entity is destroyed, it will stop listening to the event and everything's cleaned up nicely. Okay, and now what's left to do is now to omit this event. So we go back to the ball and what we'll do is check if it's the floor. If this.entity, that's not your entity. So if the other entity that we collided with has a flaw, then this.app, fire the event on this object, which means that game manager will listen for it, get to it, this callback will fire immediately and we're going to update our health. Rerun this. And... it's not working. Why are you not working?? Have I spelled fluorol? Have I spelled fluoro? Ah, case sensitive. Oops. There we go. Lower case. Yep, ok. Rerun that. There we go. You can see top left, every time we hit the floor, the health goes down. And that's our game logic. One thing I forgot to do is that I've forgotten to destroy the balls when they fall off the ledge. So right now we're gonna...... How do you connect the UI to the score? So Alexi just mentioned how do you connect the UI to the score. So, going back on that. Go to the game manager. Alexi, can you send me a link to your project, please? It'll probably be easier if I do it from there so that I know where you've gone up to. Uh, the element. Thank you. Copy link.
20. Final Remarks and Additional Resources
One important thing is to destroy objects when they fall off the edge to avoid too many physics objects. You can publish your app directly from Play Canvas and host it anywhere. The workshop concludes with suggestions for improving the game and links to additional resources like forums and Discord for further assistance.
Okay. So a great thing about Play Canvas is if you're attached to a team, or if you're part of the team, or it's public, you can access it immediately, you can access it quite easily. Okay, so, what Alexi's done is... let's see how far we've got it. Post Creates. So, we've got the Text Element here. Type Text. So, this type should be Entity, not Text. Unfortunately, you can't... there's only certain types that it works with for Attributes, and in this case it's Entity. You can't directly reference the Text component or the Element component. So we've changed that to be Entity. Great. Save it. Go back to your scene, go to your Route, and Reparse it up here. If you click on Select Entity, and then select Score... Perfect. Let's go back to the code. Spawn, display health. Yep, that should work. Let's find out. Okay. Let's find out. I'll do this to me. Perfect. Cool. Yes, one thing I forgot to do, is if I take out my camera, and zoom out a lot, you can see I've forgotten to destroy objects when they fall off the edge. This becomes a problem when you have like 200 odd objects still falling from the balls. So what we want to do is destroy the balls if they fall below a certain Y value in the world. Go back to the ballstrips, and basically we get the position of the NC. I'm rushing now, I've got 3 minutes. I've got to get the position and if pos.y is less than minus 2, then this.nc.destroy. So now, let me zoom out a bit more, we run this, when it's below minus 2 you can see the ball has been destroyed. And this ensures that we don't have too many NCs in scene being simulated or too many physics objects. Now, let me reset that camera position. Cool. Oh yeah, publishing. What we can do now is publish directly from play canvas and host the published app to be directly accessible from anywhere. So there's a public link, so it goes build, publish, publish from play canvases, and that's fine, publishing, and what they'll do, it will take a build of your app, post it onto to playground servers, and give you a permalink. That will always link directly to your primary build which is done by this tag on a thing. So now you can send this to anybody that you like. You can host that on your website, iframe it, or extend it over to a client for preview, etc. And this is where we reach the end of the workshop, which is just on time, quite literally. If you want to improve the game, or want to work with us more, what you can do is look at, as in the Game Over screen, so when your health level's below zero, you can do a restart, do Paths Clicks for Collision, so you can do little dust particles, work with different sized scripts, add screenshakes when you broadly click the score, and make the game progressively harder over time. For more useful links, if you like PlaneCanvas and want to do more with it, one course I recommend is one done by Joao from last year, which covers more in-depth parts about physics engines, so using triggers, using forces to move players. Here we go. Using forces, it moves the player, do more animation, and also Game State. This is a nice step-up from the presentations I've done. Also, you've got the forums and Discord if you need help, tutorials, and also there's a nice selection of tutorials and product examples on the main site. Any questions for the zero minutes that we have left? Awesome, in that case, thank you very much for coming to the workshop. I hope you enjoyed it and do more with Play Canvas in the future. Let me know what you build through Twitter if you do sort of more to your basic game. Also, if you have any questions later down the line, do use the Discord where I'm on quite frequently. Go to the Play Canvas Discord. You can hop on there, ask questions to our community, we've got quite good moderation. We've got a good, healthy, which was a helpful community, especially on Discord with a bunch of moderators as well. So, yeah, give me a shout. Yes, that is my last slide. Awesome. Thank you very much, everyone. I hope you enjoy the rest of the day. Go to any workshops, have fun. I'll see you later. Have a good weekend.
Available in other languages:
Watch more workshops on topic
You'll learn the following:- How to add 3D mesh objects and buttons to a scene- How to use procedural textures- How to add actions to objects- How to take advantage of the default Cross Reality (XR) experience- How to add physics to a scene
For the first project in this workshop, you'll create an interactive Mixed Reality experience that'll display basketball player stats to fans and coaches. For the second project in this workshop, you'll create a voice activated WebXR app using Balon.js and Azure Speech-to-Text. You'll then deploy the web app using Static Website Hosting provided Azure Blob Storage.
Check out more articles and videos
We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career
Comments