In this workshop, we’ll build a complete game using the PlayCanvas engine while learning the best practices for project management. From development to publishing, we’ll cover the most crucial features such as asset management, scripting, audio, debugging, and much more.
PlayCanvas End-to-End : the quick version
Transcription
Hi, everyone. My name is João, and this is the workshop for Play Canvas End-to-End, the quick version. Quick, not so quick. Let me quickly do like this. Cool. Yeah, so Play Canvas End-to-End, the quick version. Quick, not so quick, because it will be around two hours of a workshop. But yeah, we'll still be able to build a full game in these two hours. So the way that we'll structure this workshop is, so just do some brief introductions, but then I'll talk about Play Canvas. So I'll just talk about the basic features that we have, you know, like everything you can do in Play Canvas. Then we'll go into the actual core of the workshop where we'll build a game together. So I split that into three different phases. Phase number one is like just a general scene setup. So we'll start just importing some assets, placing some stuff around, some obstacles, some ground. We'll talk about that in a second. And then we'll talk about launching and like testing your game out. Phase two will be about interactivity. So we'll go over scripting, question scripts. We'll go about the event system that we have in Play Canvas, also about how to do physics with rigid bodies. We'll talk about the animation system as well, and then some debugging capabilities using Chrome. And then phase three will be about publishing and publishing. So we'll create just some final touches on user interface, how to do audio, and then we'll be able to publish that to the world. We'll be able to do that using Play Canvas' own hosting solution. So you don't need to have like anything on your own. You'll just press the button and you'll have a link, a firm link to your build of the game that you can share with anyone that you want. And then we'll do some wrap-ups towards the end. Now the way that we'll be doing the different phases there in the actual game is that I prepared a Play Canvas like a source project that already contains like all the assets and like the boilerplate stuff that we'll need. And what you guys will be able to do is actually take that and make a fork out of it. So you'll be able to copy that as your own project and then make whatever you want with it. I'll be doing the same thing. So I'll be starting from the same like starter project and then together we'll build up to the final game. Now each of the phases that we already, we also have a project for those. So let's say that you're in the middle of phase one and you kind of get lost. It's okay, just wait for phase two and then you can clone, you can fork a project starting at phase two. And then same thing for phase three. If you get lost in the middle of the scripting stuff on phase two, just wait and I'll send the link for phase three and then you can fork phase three and restart from there. So I have these three kind of like checkpoints that we can go along the way. And also the final project, like the end thing with full documentation, with all the features, will also be available for you guys to take a look at. So see like the entire thing done as well. So you can also just sit back, take a coffee, tea, water, whatever you fancy and just listen out how to build a full thing or you can build out like together with me as you go through it. So first let's briefly talk about who I am. So hi, my name is João. I currently work at the PlayCanvas team as a software engineer. You can also find me on the PlayCanvas official forums as Jay Paulo. And again, as I said, currently I work as a software engineer at PlayCanvas. Before I was working at Amazon Alexa and before that I was working at SAP. So I do have kind of like a backend, you know, like not games related until I moved to PlayCanvas. But games have been my passion since like the beginning. I think there's a story that many people share, I guess, in the gaming space. So games is the thing that made me really like want to go as a career as a software engineer. So I do have a few indie games launched. So I have Stapp and Minar, which did, you know, have a little bit of success. So Minar did reach I think the top 10 puzzle games in a few countries on iOS when it was released. So that was pretty exciting. And Stapp was a game that was picked as for the Google Indie Games Showcase a few years ago. So there was a whole event about it as well, which was pretty cool. You can find me on Twitter as thejohnpaul and also on GitHub. I'll talk a bit more about why GitHub is important for us at PlayCanvas. But yeah, if you're fit to follow, I follow back, you know, like that nice policy for follow, follow back. So yeah, cool. So let's talk about a bit of a PlayCanvas. So PlayCanvas is a web first game engine. So as you can see from the editor there we have on the right, it's very similar to a few other game engines that we have in the market. But again, the cool thing about that is that it's running 100% on your browser, right? Which does mean that you can like you can use it on mobile device as well, but you can it's fully cross-platform out of the box. So you can use it on Windows, Mac, Linux, anything that you can run a browser on, you can run PlayCanvas. Maybe it even works on Raspberry Pi. I don't know if it has webgl. Anyway, so the PlayCanvas is really split into two main things. So first is what we call the PlayCanvas engine. So the PlayCanvas engine is what contains all the core functionality for a PlayCanvas game to function or application to function. So it has stuff like vectors and math and quaternions, mathematics and like loading meshes and textures and loading that to GPU shaders. So all that stuff is on the PlayCanvas engine, which is fully open source and is on GitHub. That's why GitHub is important to us. So you can help over there right now, github.com slash PlayCanvas slash engine. And you can make contributions yourself, you can check out what we've been doing. And you can also use it like to learn a little bit more about how to make game engine architecture. How do we do that? The final build of the PlayCanvas engine is only 335 kilobytes zipped. So that means that when you make a PlayCanvas game and you ship for people to download and to play, our PlayCanvas footprint is only 335 kilobytes. Again, that's zipped. Once you unzip it, it's a little bit more than that. It's a little bit over a megabyte, still pretty small. So our footprint is very small. It is available as a standalone. So you can use the PlayCanvas engine without the PlayCanvas editor, which we'll talk in a second. And you can, so the PlayCanvas engine is on npm. So you can use it from there. You can also just like download the minified version and use for that .js single file, just import it and go ahead. Or you can build from source. You can download the open source right there. npm install, npm run build. There you go. You have it there. You can use it from there. Now the online editor, of course, uses the PlayCanvas engine. And this is what powers PlayCanvas.com. And we really do collaborative by design. So everything you do on PlayCanvas is very easy to collaborate with other people. We'll talk about it in a second once we actually jump into the workshop and we actually do stuff. We'll see how easy it is to have multiple people working on the same scene at the same time. But it is collaborative by design. We have a powerful backend that kind of powers everything. So everything that you do in terms of you open the editor and you're like, oh, I want to use this mesh and we use this texture. All you have to do is like drag it to the editor. It uploads to our systems. And then we do like all the heavy lifting or like converting to the proper things, doing compression, blah, blah. So nothing is done really on, you don't have to worry about anything. Yeah. So as I said, we do like asset processing, texture compression, another modern conversion necessary, except, you know, like most of the, all of the major file formats, GLBs is our favorite one. FBX also works pretty well. We do have a free tier as well. So we do have some paid plans if you want some more like more deep team management functionality, but we do have a very powerful feature as well with a lot of storage, you know, and with self-hosting as well. Yeah. So unlimited free hosting for published apps and games as well, which again, we'll, we'll, we'll, I'll show you how to do it later. So just talk a bit, some of the features that Play Cameras has to all the, all the, the code editing for your own scripts is done online as well with built-in IntelliSense. So we use actually the same, sorry, we use the same code editor as VS code, but it's for the web, but it's powered by the same engine. It's called Monaco. We have the powerful backend again, that does like all that heavy lifting for the assets for us, which does mean then as you're building stuff, you have zero build time. So you need to wait for your importing stuff, you know, you're loading the project. It's all like, it's already there. It's like basically loads up. That's a bit of the powerful backend, but also a bit about javascript itself. That is not really a compiled language, you know? So when you're building stuff with Play Cameras, we code in javascript. So you don't need to like compile everything as you do stuff. It does mean a zero build time then. It does come with WebXR support out of the box. We have some examples for that as well. We do have also a fully featured Git-like version control system, which again, as I talked there in the beginning, we'll be able to like fork projects. So this is like just, just a piece of that. So we can like fork projects, but you can also create checkpoints. You can create branches, merge back, very much like a Git-like system, which again, I'll talk about later. And the editor itself, multi-platform. And when you load it up, it's super small alone. It's only like 2.1 megabytes. If you check on like the network tab, how much have you actually downloaded it? It's only 2.1 megabytes. Again, it unzips, you know, like it compresses later, but the main thing is only 2.1 megabytes. And it supports like all the things that we expect from a game engine these days. We have a user interface, we have audio support, positional audio support, animation system physics. We have, we can create templates, which is prefabs is also called in other game engines. We also have advanced graphic features. So we have an editor time and a real-time light mapper, physical-based rendering shadows, clustered shadows, all the stuff out of the box using Play Canvas. And we have a dozen of support channels. So we have documentations with tutorials, full api references. We have our own forum as well, forums.playcanvas.com, where you can like post questions. People are very, very helpful there. Shout out to people on the forums, they're amazing. And we also have our own Discord channel as well, where sometimes we're there just to chat and help out whenever people want. And I could stay here for two hours just talking about stuff Play Canvas can do, but this is getting kind of boring. So moving on, the Play Canvas editor itself, very similar again to many other game engines that are currently out there. So we have the hierarchy list on the left. We have a toolbar also there on the left, where you can just select what tool you want to use. You have the project assets there in the front bottom, where you have all the assets that were imported into that project. You have the inspector there on the right, where you can inspect your selected entity. So for instance, in this case, I'm selecting the light entity. So you have the basic information such as, well, the name, is it enabled? You can add some text. Then you have the transform of position, rotation, and scale. And then you also have the components, which I'll talk later about them. But basically, you define the behavior, right? So in this case, I added a light component, and then you can select what type of light. Is it color, intensity? Does it do shadows? Does it not do shadows? How often to update the shadow map? All these things you can select there on the inspector. Some useful links, just general Play Canvas useful links. So we have this awesome Play Canvas GitHub as well, which is a curated list of some cool stuff that you can do with Play Canvas. Some of them have sources as well. So you can check out how those things were made. We have an examples browser where you can check out, again, how to use the engine and all sorts of crazy stuff. And you have the api reference and tutorials. You have the forums and have a Twitter as well. So our Twitter is pretty cool as well. We do like to retweet and help out the community as well. So just give us a shout out if you have a cool project, and we'll be sure to retweet and help spread the word about your project. Cool. Right. Okay. With all of that boring stuff out of the way, let's actually go ahead and build something. So the game that we'll be building is this one. I'll open up here so you can see what it is. So this is what I was talking before about having a build hosted by Play Canvas. So this is a playcam.as website. And from here, you can host any build that you want. This is the game that we'll be building. So it's a pretty simple one. So you have your character moving around. You have some obstacles that you cannot really cross. And then you need to eat some food. And then as you're eating foods, the time increases and you have a certain time to eat all the foods. And one of the game, very silly game, is just to eat as much as you want. And if the time runs out, let's leave the time to run out a bit. The timer runs out. Time's up. Nice. There's some audio effects here as well. It just says time's up. And then you press the button to go again. Resets the score. And then you start over again. Pretty simple stuff, but we'll be touching a lot of different things here. Let me go back to the slides. Cool. So again, this is just a screen of what we'll be building. So if you break down what this game actually is about, so we have a ground entity there, which has some physics. We have some obstacles which need to somehow block the player from going. So we'll need, again, some physics collisions on that. We also have some food items that keep popping up. And as you can see there, the food is not always the same. So we have cakes and apples and bananas and stuff like that. And it's dynamically placed. And you also have the player, which is, of course, dynamic as well and has some animation on it. We also have a very simple UI there on the top left, which just has the time remaining and the count of the score. Cool. Now, let's get going then. So we'll start at phase one. So hopefully by now, most people have the PlayCanvas account. And then if you don't, just go ahead to playcanvas.com and create your new account. It should be pretty straightforward, very easy, very fast. And then we'll start with the Food Run Starter Kit. So I'll send this link on Discord. And I'll also send this link here on the chat, which is this one right here. So just open that up. It's public, so everyone should be able to see that. And from here, we can go ahead and fork it. So to fork a project, simply go here and click on the fork button. So the owner should be like, this should be your name, should be your account there. And you can call it whatever you want. So I'll call it Food Run, my own. Just click on fork, just wait a while for the backend systems to duplicate the project. So duplicate everything, scenes, assets, scripts, everything, and it will create your own copy of the project. So now I have my own food run that you can do whatever you want with it. So like every one of you have your own. If you're having any issues at all with it so far, just send a message on chat or on Discord. I'm seeing those. There's nothing, I'll assume everything's kind of, everyone's kind of following along. I won't be too fast on it. I'll give you guys some time. But once you have the fork done, you have your account and you have your fork done, just click here on the editor. So I'll close my other tabs here. It doesn't get in the way. Let me just check if there's anything. Okay, nothing on Discord, nothing on chat so far. Cool. Right, so once you, so this is the first thing that you see when you open the uplay canvas editor. So you're greeted here with a list of scenes. So by default, when you create a new project, you'll have a blank scene. In this case, we already have a game scene here. So you can go ahead and open up. And now we're good to go. So as I said before, so here on the left, you have the hierarchy, you have a toolbar here, you have all the assets. On the right, you have the inspector. Here you have the viewport, which again is nothing. We have a camera and you have a light source, right? Now again, so this is like the starter project for the Food Run game, which means that already added here, so on Food Run, already added like all of the assets that we'll be needing for the project. So for instance, if you open arena and you open textures, you already have some nice textures here, some prototype textures. Now all of the assets that we're using here are from the amazing Kenny, of course. I think most people here might know who he is. I think that's an amazing creator doing some fantastic stuff. And it's all CC0, of course, but all the licensing and source information are there to link back to their amazing work. So we have the, again, the arena with some textures. We already have some sound files as well that we can link later. We already have the model as well, which contains, you know, like, which has the fx file here, have some animation clips as well. So everything is here that we'll need. And, you know, as we go through the workshop, we'll go through all of those together. But moving on, we also have the script, a folder called script templates here. So of course, like building the entire code for that thing in two hours together while we're sharing screen might not be like ideal. So already have like the boring code is mostly done, you know, like hooking up game managers and like coding this simple game logic is mostly already there. But we'll need to do some stuff throughout workshop to fix it up. We'll do that together. But again, don't worry about those for now. I'll tell you when you need to use those. For now, just leave them there on script templates. Also, I do have some notes here, just so I don't lose myself. So if you see me like looking down, writing down, just make sure that don't lose anything. So yeah. Cool. With that, I think we can get started. By now, hopefully most of you have created your own fork and already have it. Let me check the chat. Okay. Can I get like at least one? Yes, I mean, I'll be able to do it. Just so I can go ahead. Want to be sure that. Awesome. Thanks. Oh, awesome. Thanks, Mike. Thanks, Rick. Oh, awesome. Thank you. Amazing. Great response. Thank you, guys. Cool. So let's let's start then. Let me just grab my mouse here. So it might be a little bit easier. Cool. So we'll start. Let's start from the biggest thing that there's one more chat. Sorry. I'm just watching. That's totally okay. Yeah, totally fine. Glad you're with us. Cool. So let's start creating just the ground plane, right? So let's start creating a new entity here. Sort of root. We call that arena. We have the arena here, and let's create the ground plane. So if you go here for a new entity, we have a lot of like predefined stuff, and one of them is primitives. So you have stuff like boxes and capsules, cones, et cetera. We'll want a plane, which is, again, just two triangles, just a simple thing for us to define a plane. And let's make it big to cover a lot of things. We'll make it actually 25 by 25. 25 by 1 by 25. The plane canvas Y is up. So we have a big plane here. And let's create a texture, a material for that as well. So let's apply a texture to it. So if you go on food run here on arena, we already have some nice textures here. So let's create a new material there. Let's also do some nice project management. So we'll not create it here. On arena, let's create a new folder. Let's call that one materials. Let's create a new material here. So then on the assets panel right here, let's create a new asset. Let's call it ground material. And this is, as I mentioned before, plane canvas supports full PBR, so physically based rendering materials. So we have stuff like ambient, diffuse, specular. You even have clear code, which is useful for cars and stuff. Pretty cool. We have emission, opacity, blah, blah. This one is pretty simple. We just want a nice... Let's actually refer back to our image here. It might be useful. So we just want this nice grid pattern right here. So just like apply a diffuse here. So if you open up the diffuse drawer here, we can apply any texture here. We can also apply some tinting, some color. So let's then open the click on the textures folder here and let's use sector eight. I think it's a nice one here. Actually, because I did it before. So when we apply it here, well, of course, we applied to the material, but the material is not being used on the mesh yet. So let's jump back to the plane and let's be sure to use the ground material on that plane. So we can either drag and drop here on the world. So this works or here on the material slot on the inspector. So let's draw here. Let's drop here because this is a pretty cool feature. Right. But this is looking very big. It's stretching across the entire thing. So let's add some tiling here. So a good number that will work here since this is two by two is if we take then the 25 and then divide by two. So if you take 25 and then divide by two, which works by the way here, this will work. This will tile seamlessly here then. And this will also line up perfectly with our grid. So each tile here is exactly one unit, one meter, whatever you want, one unit across, which is pretty cool. So now we have our thing here, but this is looking very white. All right. This is looking too to blend. So let's give it some color. So again, if you go back to the material on the diffuse, we can click on tint and then we can add any color that you want. So we'll be able to change to any color that you want. We'll just multiply the colors together. So a good number that we have there is a nice greenish tint, which makes it look like kind of a grass. So the number for that is 59 D1DD. So this is the same color that we have there on the example that we have here. So again, if you want to use the same number, it's 59 D1DD. So that's the hex code for this nice green tint, but you can put whatever you want. You can also use another texture if you want. Not even use the texture. But cool. We have our plane here. Let's go ahead and create a player. So to create a player, let's create a new entity then. Not on the arena, because the arena will be putting the static stuff. Let's create a new entity, son of root. Let's call it player. So the player entity currently doesn't have anything. Let's put that mesh that we have. So here again on footrun, character, character, medium, FBX. So this is what happens if you take any FBX file and you simply drag it into Play Canvas. I didn't touch this at all. This is the raw files downloaded from Kenny website. This is what we'll create for you. So it will upload the raw FBX file and it will convert that to all the files that internally Play Canvas needs. Now there are two more important ones here. One of them is the material that it uses. So this is just important, the raw material, which doesn't have the texture on it, but it's the material of the settings that it had. And this one with this prism diagonal, I forgot now the name for this symbol right here, but it's rotated square. And this is important because this is the template. Now we'll talk a little bit more templates in a second, but this is the asset that contains the model and the material. Everything kind of bundled together will be on this one. So this is the one that we'll be wanting to use whenever we're importing models in. We'll use this one. So then what we can use is you can take this one and you can just drag it into the scene. So we can drag it here, but I actually want to be a child of player. So I can just drag it in here. So I'll be dragging into the player entity there. Cool. Now we have the model there. Now, of course, it's still, by the way, if the camera is kind of wrong, you can press F. You cannot really see that, but I just pressed F and you zero in on whatever you're selecting. So if you're selecting the arena and you press F, go to the entire thing. If you press the, if you use another character, zoom in there. Very useful feature. So now we have our asset there, but of course it doesn't have the material yet. So let's create a new material as well. Again, let's do some nice project management. Let's create a new folder, not to create it anywhere. Let's call it materials. Let's create a new material there. Let's call it player material. Of course, we could just take the material that was already imported, this one, and just edit this one. But I think for our use case, we'll be creating our own so that we can tweak some more things. So then let's assign this material to the mesh there. So we just like drag and drop. It doesn't look too different, but that's okay. So let's select the material. Let's go on diffuse and then let's select one of the textures. So on this particular Kenny asset pack there, it comes with four different textures, and all of them already have the correct UV mapping for this particular model. So we can use whatever one we want. So I'll be using zombie C, but it looks pretty cool actually. So I have this nice zombie here already applied there. Now, as I said, the material is a PBR, so it's physically based rendered, which means that we can change a lot of different settings for whatever look that we want to have. So if you open the specular drawer here, we can do some more things as well. So we can have different specular tinting if you want, that gives a nice interesting effect. But what I want to touch on is actually the glossiness, because if you go ahead, the last parameter here, glossiness, and we put it all the way over there, kind of looks like a plasticky thing. This looks like a plastic doll, which could be something that you're going for. It gives a nice specular highlight there for the light. So this is the light being reflected. But I think for this one, I don't really want that effect. I want something more diffused, as if it was a person or like a character or like some plastic that you're moving around the board. So if I bring the glossiness down all the way to zero, it kind of does this more diffused effect, which again, something that you want. You go ahead and do whatever you want. If you want that plasticky look, it might even look cool. Anyway, so right, we have that done. So let's create a template for this. So it's very useful as you're developing things, if you're going to place them in multiple different places, to create templates for it, which in other game engines is also called a prefab, which is something you can just drag and drop and already contains all the material mappings, already contains all the hierarchy that you set up. So let's create a template for the player, which currently doesn't have much. We only have like a player entity with the model here, but we'll do some more stuff later. So let's on the character folder, let's create a new folder here. Let's call it templates. Again, we want to have a nice project management here. And the way to create templates in PlayCanvas is the following. You select the entity that you want to have to create the template from. So in this case, player, so the player entity, right click, template, new template. This will create a new template in whatever folder you're currently on, on the asset panel here on the bottom. So just create a new player template on my templates folder in the character subfolder. So again, so it's a on foot run, character templates have the player template right there. So if we delete this, oh no, you just drop it back in and have the full thing exactly how we had had this set up before. Cool. Now let's go back to what we're building. So we have the player there, we have the ground. Let's create some obstacles. Now to create the obstacle, again, it will be similar to what we just did because we'll also be creating a template out of that. So let's create a new entity. Let's call it obstacle or whatever you want to call it. And then from that, let's create a box shape. That's the simplest obstacle that we have, right? So right here is just like some box shapes. Let's just create a box out of it. So on obstacle, let's create a new primitive box. That's right, there are a box. Let's move it a bit forward here so that it doesn't get in the way. So it's easier to see. You can also see the shadow map there being updated. Pretty cool. Let's raise it up a bit so that it's above the ground. So the value that you want is 0.5. So by default, the primitives are one by one by one. So if it's 0.5, we get it halfway there. Let's make it a bit bigger. Let's scale up two on X and two on Z. Just a slightly bigger there, but not too big as well, not going above. Because we want to see what's behind the obstacle. It's just really something that they won't be able to go across. Let's also create a new material for this because right now this is using the default material, which is just pretty bland. Let's go on arena, materials. So the same for that we have the ground material. Let's create a new material for that. So let's call it orange or something. It will be orange. Spoiler alert. Let's drag that in. It doesn't change much because it's the same color. But now the box is using our custom material. Let's go ahead then. We won't be using a texture for it, but we can just give it a color. So whatever color you want. So here I want to have a nice orange thing. I think it works very well with this greenish bluish background here. So the nice color for that is FBB057 to give this nice orange tint. But again, you can use whatever color you want. This is just a color that I use, FBB057. And you have it there. Let's create a template out of that. So on arena. Yeah, because this will be part of the arena, right? Yeah, this makes sense to be part of the arena. Let's place the obstacle as child of arena as well. So arena now has like the ground plane and also has an obstacle. Let's create a template out of it. So let's create a new folder. Let's call it template. And then obstacle. Let's create our template out of that. So now you know the drill. Your pros at this already. So just right click on the entity, template, new template on the folder that you want. Like if you create on the wrong folder, just like move about. Like we'll do the heavy lifting of like the referencing will be correct. You don't need to worry about that. But yeah, so then if you create it on the wrong place, just move it to the correct folder. So we have here on arena then templates we have on the obstacle. Cool. And by the way, when you do that, this one is already linked to this. So if we move this, if we change this template, like it will be applied. So we don't need to worry about that. What we'll do, let's also take this opportunity to put some obstacles around. So of course here I made this pattern of whatever. It's kind of nice. There's a symmetry to it, but you can do whatever you want. So in this case, I'll just like be placing those around relatively randomly. So I'll put this here. And then so you can right click and say duplicate, or you can do command D or I think in Windows is control D to just duplicate. And then you can move about however you want. So let's just put some around. And I think this is a place where you can where you can let your game design fashion shine. So we can build some corridors if you want, you know, you can build some nice structures of different areas that you want. In my case, pretty boring, just like having some around. I'll put one more in there. Yeah, okay, cool. There you go. So just some scattered. Be interesting to see what you guys come up with. So again, like once this is done, make sure to take a screenshot, post on Twitter and take a look at what you guys come up with here. Cool. So now we have our obstacles. What else can we do now? Let's create the food, right? So we have the food entities there, which we can pick up to increase time. That's very important as well. So let's create one of that. So again, on route, let's create a new entity. Let's call it food. You can call it food or collectible or whatever you want. So it's right now placed right there. Let's move the player back a bit, because it's on center. Let's move him back a bit there. There's fine. Just whatever you want, just so it's not just out of the way. And then let's put in a model. So on your food run asset folder here, you have the collectibles folder. And within here, you have the models. And again, this is like the raw FDX imports. If you just download Kenny, take the FDX file, drag it in. This is exactly what you'll have. It creates a folder for you, which contains all the assets that you need. You have the mesh information here, all the different materials that you need as well with already all the tinge information. Again, I changed nothing out of this. This is plain import. And you'll have the diamond. I think that's a good name. You have the diamond shape right here, which again is our template. And this is what we want to use right here. So let's take, shall we take apple, banana, or cake? Let's take apple. More healthy, more healthier option. So let's take this one. Let's drag it in as a child of food. And it will be there. It's looking kind of small. And we want to be able to see it later. So let's select the model entity here and let's scale it up. So in the toolbar here on the left, you can select the scale. And then if you click on the, if you click either of these, you can scale up in any of the axes. But if you click on the middle there, you can scale all of them up together in uniform fashion. I think this is kind of what we want. We want to make it kind of big. Yeah, I think that's a good big apple. Cool. Now, since we have that, I think we're good to go. Let's create a template out of that. Let's go here on collectibles. Let's create a new folder. Again, you know the drill, your pros, project management, blah, blah, templates. And then on food, on the food entity, let's create a new template out of it. So like, again, in this phase, we're doing like project setup, right? We're creating all the different little, all the little aspects that you need for the game. In real world scenario, you would have something like an image from, you know, from a designer or something that kind of explains the gameplay, and it would be like breaking down all of that. That's kind of what we're doing. We're breaking down the core gameplay, creating templates for it, and then we'll go from there on phase two. So we have now our food. Now, again, we have some, I did add some other models here. So you can go ahead and create some new templates. I won't go through all of those, but it will be a pretty similar process. Just create a new entity, add your model, again, the diamond-shaped one, increase the size a bit so it kind of makes sense, and create a new template out of that and just place it on the templates folder here. Again, I'll just use the, I'll just use the Apple for now. Cool. Now, if we compare this with this, it's looking a little bit different. So this shadow is right here. So we have the shadows, but they're not really that strong as we have here. These are pretty like strong shadows looking kind of gray. Also, our background is like this monocolor. So that's, yeah, that's not really something that we want. We want to kind of improve the look of that. So you have this nice greenish thing, which kind of looks nice. So the way that we can do that in Play Canvas is, so we support two different kinds of environment light. This is what we're missing, environment lights. So if we open here on the toolbar, the last option there, settings, the little cog, you can open, so you have some lot of interesting settings here, like some editor settings, stuff like grid divisions, you know, camera, near and far for the editor, locale stuff, can select the engine. It's a lot of cool stuff that you can do here. But what I want to touch on is the rendering drawer. So in the rendering, you can select your ambient color, you can select like tone mapping, you can select the gamma, you can, we have fogs as well. So like linear exponential, you can set like a target resolution that like all your things will be kind of based around. So a lot of interesting things here, very technical. But the thing that we're worried about here is the ambient light. It's environment light. This is what we want. So if you just, the simplest one would be just changing the ambient light, right? Because if you just like bring that up, okay, that's already looking kind of better, because now the shadows are not that that strong, you know, if you put it all the way down, we either have to have the main light, or you have nothing. So if you bring that up, we have like this kind of stuff, right? But I think you can do better. So that we do support cube maps for environment mapping. So that's what I'm talking about, environment mapping. And you already have that right here. So if you open on food run environment, we have this six faced, might have seen this kind of images before, to have six, six images, which define a cube, which we can use that to give lighting and to give like an environment to our scene. So we can very easily do that by the following. So on environment, let's create a new asset. Let's call it cube map. So new asset, cube map. Let's call it environment. And then here you can see that there are the slots for six images, which again will define that cube, right? So if we take a look at each of these images, their names are already like pretty, pretty self-explanatory on what they want. So this, for instance, is the back one. This is the bottom. This is front and etc. Which exactly those names right here. So we have a bottom, we have a back, we have front, blah, blah. So if we take any of these, for instance, the first one is the back one. If we take this one and just drag it to the back, it will automatically see, oh, we have more images here. All of those have the same names. So I'll just apply all those here. Now we have like our cube map already set up there. And once we do that, we do want to click this button right here, this magic button. We may need to scroll down a bit. Pre-filter cube map. So what this will do is basically create different MIP maps for those textures so that we can properly use, so Play Canvas properly uses it on environment mapping. So just click that button. And then if any of these textures changes, you will need to delete and then regenerate that. We will know all changes, so this is fine. Now if you go back to the settings here on the little cog on the left, we can just use this environment cube map as the skybox. So we just pick that, drag that in the skybox, and then, oh, okay, here we go. So now we have something there. So now we have that nice, it's using the environment to calculate the ambient light, so it's not like a fixed color. And we already have like this nice background here. Also here on the settings, you can change the MIP level. MIP level is basically the amount of details that will be there. So if you use MIP level 1, we'll be using the raw texture, which if you try to stretch it out across the entire thing, you'll need a massive texture. And as you can see, this is kind of looking not so great if you're using MIP level 1. You will need like 8K textures to make this look great, which you might want, but in our case, our images are pretty small. So let's cheat a little bit and let's use a smaller MIP level or a higher MIP level for a smaller texture size, like 3. So then it kind of blurs out and you cannot really see the details and it looks kind of nice. Cool. So this is the end of phase 1, actually. Yeah, so with this, we have already our project kind of set up. Let's go back here to the slides. So what did we learn? We learned how to create and manage assets. We learned about creating and using templates. Oh, we didn't use the launch tab. Let's use the launch tab, actually. So as you're building stuff, you will be able to see like, okay, what I'm actually building, right? So if you go here on your viewport, there's this small launch button here. And if you click that, it'll open up a new tab, load up the game. And this is exactly what the camera is seeing, right? So if you go here and then we select the camera. So this is what the camera is seeing right there. So this is what we call the launch tab. And this is like your game running, right? So if you have scripts, once you have scripts, this will be the actual game running while this is just like the editor, right? Now, the cool thing is that if we move some stuff around, so let's say, I don't really like this obstacle here. I want to move it around. I want to put it here. And I want to put this one here. As you're moving stuff around, your launch tab will automatically change as well. Okay, we couldn't really see that. Let's move the apple, the food. So if the apple actually was right there, and you jump back, so it automatically moves. So this is called editor link. So every single tab that has this in open with the launch.playcameras.com will be connected. And as you can see, this connection is like on the internet itself, which means that if you're building a mobile game, and you open exactly this link on your mobile device, and you log in with PlayCameras there, it's super easy. You can check the differences on a mobile device as you're doing the changes on azure. It's real time. And this is, again, what I was talking about before about collaboration as well. So if you have like some people testing, some people building, all these people connected together on the same scene, and you can all test together, okay, what do you think about this? Okay, what about this? Is this color better? And everyone kind of sees in real time as you're making changes. So this is the launch tab. Yeah, sorry, I forgot about that. Okay, now we have the phase one wrap up. So we talked about the launch tab, and you talked about the editor link, right? Before we move on, so actually, as we move on, so to next phase, if there are any questions or anything like that, please do drop them on chat, either on Zoom here or on Discord. Otherwise, I think we are good to go. So for phase two, again, as I promised before, we have the project just for that. So I'll be dropping the link for it on things here. So you can use this project, you can fork from this project if you got lost in the way, and you want to start exactly from this stage, exactly from this stage, just go ahead and fork this project. Let me paste it here. Let me also paste on Discord. So now, so that's phase two, right? So now for me, yeah, I'll just continue from this, and for you guys as well, you can continue from your current project, or if you think it's not looking that great, you can feel free to fork it as well. Cool, let's go. Right, now for phase two, we'll be introducing other cool stuff. Now we'll be doing physics, we'll be doing scripting, we'll be doing animation, oh, lots of things we'll be doing, we'll be doing animation, oh, lots of things we'll be doing, oh, lots of things go through, papers big. Cool, so let's start with physics. What I think we'll do about physics is let's start from the same thing that we started on the phase one, which is the ground. Let's just make sure that the plane has a collision on it, because right now it only has a render component on it, right? So here, if we select the plane entity, it only has the position, rotation, scale information, it doesn't have a render, it has a render information, but there's no collision on it. So let's add that. Now, the way to do that is we simply need to add components, which will add behaviors to this entity. The one that we want is collision. So if you just click here on component, you can see all the components that we support. So you have stuff like audio stuff, which we'll touch on later, you can have cameras, elements, so this is for UI stuff, we have model for rendering. We already have a render component, so we can add that again. We have scripts, which we'll touch on later. What we want is collision. Now, the first time that you add a collision, you'll get this scary red button, say, Emma module not found. Now, what this means is that Play Canvas on its own, we do not have a physics engine, and we don't ship with one, because most people don't use the physics module. So if we ship all games with physics, it will make all the builds too big, so don't really ship with that. So then if you want to use physics, you need to import that manually, but manually is just clicking a button. So just click on import, ammo, just give it a second, and you'll see here on your assets that it created this new folder called ammo and some files there. Now, ammo is the name of a very well-known, respectable physics engine with javascript. You can search later. We also have some documentation explaining what it is about, how to use it, et cetera. For our purposes, this just means that we imported the physics module, and now we're able to use physics in our project. Now, if we select our plane again with the collision, now it doesn't have the scary red button anymore, because now we already have ammo there. Just forget about it now. We're done. We're set up. We have ammo. Now, let's just make sure that the physics collision is the same size as the model itself, because when we create it, we can see here. So this is the preview of the collision, but we need it to go across the entire thing. So let's make it big as well. It uses half extents. So half extent is half of the extent. So if our scale is 25, we just want to be 25 divided by 2. So that will be 12.5, and also the same thing on z. So 10.5 divided by 5, you can do it. Let's also bring down the y. Feed it down. So that it's really small. Yeah, so now we have a collision. Now, the thing that we also need for a collision is a rigid body, because if you only add a collision, this will be treated as a trigger, which means that things will go through it, but it will just let you know that it went through. But we actually want to make it so this is a rigid thing that you cannot really go through. So we'll add a new component called rigid body. Now, rigid body needs a collision to work. So let's keep it static. So we'll not be moving the arena about, but let's increase the friction to 0.5 so that the player does slide about as it walks around. Let's put it 0.5. Restriction is fine at 0.5 because things aren't really bouncing around, but let's increase the friction. If any of these things are like, oh, what is this? What is static on render? What is layers? You can just hover with your mouse, and then it'll give you a nice tool tip explaining what it is and a link to the api reference to it with more information. So this is, for instance, the layer documentation for the render component. Again, very easy. Just hover whatever thing you don't really understand. Oh, what is friction? This is friction. What is restitution? Hover, click on api reference. It will give you a job of explaining much better than I could. So let's just move forward. So we have now the collision for the plane. Let's also add collision to the obstacles then because you cannot really go through them. Any of the obstacles that you created, if you did a template, this will work just fine. Let's create a collision there. So same thing. Create a collision. Let's make sure that it covers the extent. So I think this should be one. Yes, this should be one because I made it two by two. Cool. There you go. So now it's covering the entire size of the obstacle there, the entire model. But again, we also want to add the rigid body thing. Make sure that it's a rigid thing. Let's just add a rigid body. Let's keep friction and restitution as the default values. And again, let's keep it as static because we won't really be moving that. We'll create new types of rigid bodies later so the player won't be a static rigid body. Moving on. So you can see when you've created new stuff, since this is a template, it highlights with this blue tint and gives you here on the top this template instance override information. So this means that this instance of the template has some modifications which the raw template does not have. And then we can either keep it like this, so only this one has these changes, or we can apply to all the different templates that we duplicated there. It's very, very cool that you can press view diff here to see exactly what changed. Oh, so now we have collision and rigid bodies, like new components there. Okay. This is exactly what we want. When you press apply all, it applied to the template, which will then apply to all the instances of the template. So now if we select any other of the obstacles there, all of them will now have the collision and rigid body, which is exactly what we want. So very easy to apply that to all of them. Cool. Now let's add a collision also to our food there because again, referring back to our gameplay, once you collect the apple or the cake or whatever, something happens. So we need to check when does that happen? So let's add a collision to our apple as well. So selecting the food entity or whatever you called it, let's also add a collision. So same thing, add component collision. Now for this one, we don't really want the player to bump on the apple or on the banana on the floor and bounce back. You want to actually pick it up. So it's not a rigid body. It will just have a collision. But with this, we'll be able to add a script, which will allow us to know when a player collided with that. So with this, you can select a type of collision that you want. I think for this example, a sphere works very nice. Let's just increase the size of the sphere a little bit. It covers a little bit more than the actual size of the mesh, but I think that's fine. But by the way, if you want the collision to be exactly right, you can use mesh here, and then you can select the actual apple mesh to be the collision if you want to be exact. This is a little bit slower, but depending on the mesh or depending on your use case, you might want the exact physics collision there. But again, on our use case, I think the sphere is more than fine. And you can also check here the preview of where it does. There's a halo around it. I think that's fine. And again, let's be sure to apply that to our template. So apply all. Now we'll apply it to our food templates. And again, if you have more than one food template, make sure to do that on all templates. Otherwise, some foods will not be pick upable, collectable. Cool. And now finally, so we have every collision, everything except on the player. So now let's add collision on the player as well. So now selecting our player entity here on the player template better, which again is the one that contains the character medium here, which is the model. Let's add collision there. So here, let's add a new component. Same thing. Again, you guys are becoming pros at this. So add a collision. Now it's very common for characters like this to be capsules. And a capsule looks like this. So it's very easy to control this to be like a character that can rotate around and bump around and won't really bump into things as it's rotating. So it's a very nice shape to use for that. And what we'll use is what we'll do on the capsule. Let's increase the height to three and let's make the radius zero. I think 0.5 is fine. Now what happens here is that the capsule, the collision has its bounds around the center point. So right now it's using whatever this is. So if you want the player to actually fit inside that, we'll need to take our character model and move it down a bit. So since the size of our collision is three, what we'll need is to select our character medium and then set the y to minus three divided by two. Oh, sorry, minus three divided by two. Sorry. Minus three divided by two. And then let's just put that back into the ground level there. So that should be around 1.5 then. So now our character is again on the ground, but we placed the collision on the right place. I think that looks kind of okay. There's no collision on the head, but I think it's fine for our use case. This should be fine. Okay. Now again, we do want the player to bump into stuff. So the player is a rigid body. So let's add a rigid body component to it for the player. So the same one that has the collision. But this one won't be static. This one will be dynamic. Once you change from static to dynamic, bunch of new options open up. And again, if you have any questions about what do these numbers mean, just hover over those and then you can see the api reference. You can see some hints there. This is similar to some other game engines as well in terms of mass, linear damping, et cetera, et cetera. I'll give you some of the numbers that I'll be using that also you're able to see on the full projects later. But if you want to use the same numbers as me, so mass is 100. Linear damping is 0.9999. So four nines. So it's not exactly one, otherwise it wouldn't move, but it's almost one. This is to prevent the player from skidding across. You're moving around and it only moves as you're adding force width, otherwise it will stop. So we want a number close to one, but not really one. 0.9999 works well. And we'll be using that same one for angular damping. So same 0.999. And we'll also be using the same number for friction. So you can just copy that same number. So again, 0.9999999 for linear damping, angular damping, and friction. Now, what do these do again is make sure that the player feels nice on the ground that you have set up. Feel free to change it, to play around with those things. Maybe you're like, your game has some cool skinny mechanics. Maybe the floor is actually ice or something. Just go crazy with these numbers. But the one important thing though is on angular factor, we want to put 0 on X and Z. And what do these do is this prevents our character from topping over. Because if you think about it, if you think this is a capsule on the real world and you try to put it like this, it will fall over. But that's not really what we want. We want the character standing like this and just walks around like this. It must be able to rotate like this. So this is why we kept the Y at one so that it can rotate on the Y axis. But we don't want it to rotate on the X axis or the Z axis. So we put that to 0 on the angular factor. Linear factor needs to be 111 because it can move on all directions. Maybe not on Y, but let's keep it at one. But again, we just don't want it to rotate on anything but Y. So this is my setup. That again, feel free to use your own numbers. I mean, this is the one that feel okay here. And you can take a look into those later. Let's apply those changes to the template now. Just click on Apply All. Let's move forward. We need to keep going. I talk too much. Cool. So what we'll start now is we'll be using now the script template. So we're moving into scripting now. And let's start with some scripts that we have here. So on script templates, we have already some, again, some templates, some boilerplate code for most of the functionality. We'll be starting with the player controller. So find the player controller.js on script templates. And then let's move it somewhere that makes sense. Let's create a new folder. Let's call it player. And then let's move our script there. So again, player controller. Let's move it to that player folder. Or you can keep it there. But I think this is work better. And then if you double click there, it will open a new tab with the script. Now, this is the online editor that, again, is powered by Visual Studio Code. Now, the theme that I'm using, it's actually the same one as Visual Studio Code. But if you guys want to change it, I'm not sure which one are you using. But if you go here on edit preferences, you can change the editor theme as well. We have a bunch of different things that you can use. And also increase the font size so you can see it better. Let me increase it even more. So you can see it even a little bit better. But again, you can use whatever thing you want. Now, scripts in Play Canvas are 100% javascript. And they are downloaded and applied to your website, to your code while the game is loading. Which means that the way to create functions is to define what the functions are. In this case, we have the player controller. And this is what line one is telling us. Line one is telling us we have a new class called PlayerController variable that is a class. And we're calling the PC.CreateScript method. Now, this comes when you create a new script from start, if you just create a new asset, create a new script. This boilerplate code will be there, which is a brief explanation. This means that we're creating a new class called PlayerController that is a Play Canvas. So PC is Play Canvas. A new Play Canvas script. This is what this is doing. Don't need to worry too much about it. Now, the first method that we have here is the initialize method. This is called before the first frame that this script is enabled. So if you have an entity that is enabled by default in your scene and you start the game, this will be called right before it starts rendering. So you can set up internal state and et cetera. And the second one that we have here is the update, which is called once per frame. Now, the important thing is this is once per frame literally. So if your game is running at 30 FPS or if the game is lagging a bit and for a few frames it runs like 10 frames per second, this will only be called 10 times. So for that reason, we have DT. DT is a number that is the delta time. So this is how much time in seconds it took since last frame was rendered. We can use this number to scale movement and make sure that we don't lag behind too much. So yeah. So we have initialize and update method. So as you can see, our particular boilerplate code here already has some stuff in, but it is missing some stuff, right? So the first thing that we're missing is input. So we're doing some, we're initializing some methods, some variables here. We have X and Z for X and Z movement. And then we're already applying that force to the rigid body if something happened, right? If you have some movement, but we don't have like the actual movements. And we also have already the calculation to see, okay, what the angle of the model should be. Feel free to copy this code, by the way, in your own projects, if you wish. But this is what we're starting with. So let's do the basic thing. Let's add some input here. Now the method that we want is what we want to do is check if I'm pressing, let's use WASD. So W S D, classic Doom stuff. So if we, let's just see if W is pressed, let's move forward. If S is pressed, let's move backwards. Simple stuff like that. The way that we do that is we just do if, we must check if Play Canvas noticed that the key is pressed. What we must do is this.app. Now this.app in javascript, in the scripts here, is referencing the Play Canvas application. So the raw Play Canvas stuff that you can query stuff about. Now this, if you're not too familiar with javascript, this is whatever context you're currently running. In this case, this will be the instance of this player controller. So you have this.app and then we'll do keyboard. As you can see, we have a syntax highlighting and we have italicense as well. So very useful to see, okay, so this.keyboard.isPressed. And then if you do command shift space, you can also see also like some controller, some autocomplete there. So you have the isPressed, so key code, key to test, so that it's key underlined something, right? So what we want is to reference like the PC, for instance, .key underlined w. So this is checking if the w key is pressed. So this will, and since we're running on every frame, every frame that the w key is pressed, this, whatever is here, will happen. And what we want to do is we want to increase, want to say that on x we'll move one. So let's use like one as a uniform thing. So if w is true, we'll increase x. And now let's just copy this code over and do the s, which is for behind. So just change key w to key s. But now we need to go the other way around. So we do x minus equals one. This also means that if you press w and s, at the same time, it will stay still because you're adding one and then removing one. Just stand still. I think it makes sense. And then let's copy that again now for a. Oh no, sorry, hang on. No, yeah. So w, w should be on z. Sorry, not on x. So w and s should be on z. So this will work better. So w means z plus one, s means zero minus one, and then a, a means x plus one. Yeah. And then d will mean x minus one. Now this will work well with the setup that we currently have. So just trust these numbers for now. Cool. Right. So now if you save that, so command s to save that, if we go back now to the editor, we need to, we need to, no, no, sorry. While we're still here, one thing that we'll need to do is we are applying this movement to our rigid body with the applyForce method. Now the applyForce applies like a method, applies an immediate force on that frame on the rigid body that we might need to scale up or down because maybe it's moving too slow or too fast. We're just using one, but we might need to scale that up. And in order to do that, we'll need to add a parameter for that. So let's add an attribute to the player controller now. And what an attribute will do is the following. Now, if you go back to the editor, we select our player. Let's attach this script to the entity. The way we do that is we select the player entity, add component, get a script. With the script, it's here on the bottom. Now you can select whatever script that you want that you already have created and add this behavior to it. So here you just click and you select the player controller. Once we do that, we have the player controller attached there, which means that, okay, it will work. It will do stuff. But again, we will need that scaling of the force. The way we'll do that is the following. By the way, you can create a clicker on edit to quickly edit that script. It was already open, so it just went up, but here you go. So what we'll need to do is create a new attribute for it so that we don't need to go back to the code to change that number. So what we'll do is we're going to say player controller, which again is the name of our class, dot attributes, so the attributes of this class. I'm going to add a new one. We're going to call that power. Now if you just add this, you need to give it a type. The way that we do type is like this, so it already gives a nice documentation here. So you need to open and close the brackets and you say type. And then as a stream, you give it a number. Now in javascript, if you're not too familiar with javascript, it doesn't have the concept of an integer or a float. It's just a number. So if you do float, this won't work. If you do int, it won't work as well. So you need to use the number, which encompasses both floats and ints. Now internally, it doesn't know what type it is, et cetera. You can round it, et cetera, to get a nice round integer. But the type is a number. So again, player controller dot attributes dot add power. And then you give a type and you're calling it number. Now if we save that and then go back to the editor, it's not there. Where is our power? Where is our power? You need to click on this parse button right here, which will reparse that file and check for any attributes that you added. So now here it is. So now we have power number there. So anytime that you change the attributes, change the faults or whatever, be sure to click on the parse at least once on that script to update what the editor knows that the attributes are. Now the nice number for here is a huge number. So that's why it was important. So this one is 4500,000. So four zeros. And this is important for the reasons of how much skidding we want and et cetera. So you play around with these numbers if you want. Now if you press play, again, 45 four zeros. If you press play here, okay, we have it there. And then if you use was, it's not running. We might need to check what's going on. Let's see. Oh, right. If we check back our code, we created the power attribute, but we're not using it anywhere. It's not on the notes that I needed to edit there. So we need to use actually the power attribute that you just created on our code. So here on line 37, if you're using the same, it will be line 37. So it's constant movement here. What we're doing here is we're creating a back three, right? We're defining our movement vector. We're normalizing it and then we're scaling it. Currently we're only scaling it to dt. Again, so it's per frame, it kind of makes sense. But what we need to do is we'll need to multiply that with power. So just on dt here, just say times this dot power. Now again, this will be about the instance of the script and power will be exactly this value right here, which in this case will be 4500,000. I think that's correct. Now, if we just refresh this, pick up the new thing. Oh, there we go. So now we're moving around, right? As you can see, it's not really rotating too much and have no animation. So we'll fix that in a second. But okay, we have some, we're moving, right? Interesting as well as you can. Okay, this is fun actually. If you do like this, it's kind of like spinning around. Okay, I like that. Anyway, okay, we have some basic interactions there. Moving on though. Now, next thing you want is to set actually the rotation of the model, right? Because otherwise it's just like doing like this, right? Now, the way that we'll want to do that is we don't want to rotate the rigid body itself. Otherwise it will bug up the rotations of the calculation of the physics. So what we want is actually just to move the model, just the model, not the rigid body. So for that, we need on the script a reference to the model entity, which is the child of the player, only to this one. And the way that we do that is we'll just add a new, this zoom thing is getting in the way, we'll just need to create a new attribute for it. So here, let's create a new attribute, a controller.attributes.add. So create a new attribute. Let's call it a model entity. But then for the type, now this won't be a number, this will be an entity. So now we're referencing another entity. And then before we go back to the editor, let's actually add a code for that. So again, it's a new attribute. You can call it whatever you want, but the important thing is of type entity. And then here on the line 43, at least for me, we have to do setRotationToModel. Let's remove that because now we can do it. And then let's do this dot model entity, which is the entity that we're referring. And we'll do setEulerAngles. That's how you call it, I think it is. Now for rotations, PlayCanvas does use quaternions for rotation, but it's very complicated if you want to set quaternions yourself. So it uses four dimension rotations to prevent a gimbal lock. A lot of explanation for nothing, but we need to use setEulerAngles, not setRotation. If you do setRotation, you will be using the quaternions, which we don't want. So we'll be using setEulerAngles and you want to rotate on y. But I already added here the calculation for that, so we don't need to write it by hand. So just do zero for x, so we don't want to do anything on x. We want to use new angle for y and you want to use zero for z as well. Now if we save that, we go back to our launch tab and then we refresh. Oh, okay. So you can see that very useful. It's already giving us some errors, which is very useful for us to debug. But it says here, cannot read properties of null, reading setEulerAngles. Interesting. Let's go back here on the code. So it's trying to read setEulerAngles from null. What does this mean? So model entity is null, of course, because we forgot to link them. If I go back to Player here, which has the script, I'll need to press pause again. And now we have the model entity attribute right here. So then what we do is we click on the character medium and just drag it there. So now we have a reference here to the character medium entity from our script. So if you click there, we can easily see where it is. It also highlights there. Okay. There you go. Nice. Okay. Now if you refresh here. There you go. Right. So now it rotates. Now it's very sudden, the rotation. But, you know, we won't have time to fix that, to make a nice rotation. I'll leave you guys as a homework to fix that. Or you can just check how we did it on the full project later. But okay, this works for now. Let's move on. Let's make some animation that grabs now, because that's fun. Because again, now we're only on T pose. And not really goes happens, but let's add some animation. Cool. Now if you go here on food run and character, on the animation folder, we already have idle and run. So those are two, again, two FBX files that, again, I just drag and drop from Kenny download. That's it. This is what it provided me here. Click and was ready to generate all those files for me. But what I'll need to do first is create a state graph for it, which defines like, oh, I'm idle or I'm running and then how to, you know, how to do that. So to do that, let's select character animation. Let's create a new, a new state graph. So again, on assets now. So now we're creating a new asset, which will be an animation state graph. So new asset, a new state graph. Let's call it character graph. Now if we double click on it, we'll change gears on the editor to the animation state graph editor. Now if you, if any of you guys have used other game engines before, this is very similar to, you know, other solutions in terms of like how to set up an animation state graph. The way it works is that you have a starting node and you have, and then you have nodes and transitions between them based on conditions. In our case here, you've already created like the boilerplate simplest one, which has an initial state, which currently does nothing. And it's looping, right? So it's a stage with no animation attached. What we'll do is, let's rename this one to idle. So the default animation, like by default you're idle, like breathing, just like kind of like this, right? And then let's create a new, let's create a new node, sorry, new state and let's call it run. So we can jump between idle or run state. The run state will also be looping because like it will be doing like this, right? So let's make sure that we click on loop there. And let's add a transition from one to the other. There's like, there's a condition that will make it so from idle, we'll start running. And then if we stop running, the like speed is zero, we'll start, we'll go back to idle. So right click on idle, add transition to run. Same thing on run, add transition to idle. Now, if we just do this, it'll just be coming back and forth and nothing will happen. So we'll need to add a parameter so that we can let the graph know what state, you know, what is the speed of the player so that it can like actually move around. So here on the left, you can create a new parameter here, so parameters parameter, and let's call it speed. You can call it whatever you want. And you can provide whatever type you want as well. So you have integer, so it will force you to float, boolean, so true or false, or a trigger. A trigger will be true only for one frame on the first one, and then it will go back to false. Very useful for like shooting or something happens only once. In our case, we want the float. So a floating number which will give us a number of what is the speed, what's like the magnitude of the speed vector that you're moving around. So with the speed parameter, we can select now our conditions, and then let me just expand this a bit. And then here, I can add the condition. So on idle, I'll only want it to go to run if the speed condition is more than 0.1, for instance, right? So 0.1 works fine in this case. We want exit time to be nothing, so just erase exit time. Exit time means that it'll only do the check once the animation is done, but actually we want to be able to interrupt the animation. So if you're in the middle of your idle and you start running, you'll like stop idling and start running immediately. So be sure to erase exit time there. For duration, you can add anything you want. A duration just blends between the two states as a transition goes instead of just jumping from one to the other. This is a normalized time based on the speed of, based on the time of the animation. So 0.2 is a fine number. It just gives some small blending. Let's do the same thing on the other one then. From run back to idle, that's at a condition, but we'll only do that if the run is less than 0.1. So then if it went to 0, a very small number probably is not running. So it will go back to idle. Again, make sure to delete exit time. Exit time shouldn't be 0. Exit time should be blank. Just erase that. And then duration again, let's do 0.2 or whatever. Cool. That's it actually. That's the setup that we need for the animation state graph. Now, when you create the state graph, you don't assign the animations yet. We'll assign them in a second. This is only defining the raw behavior, just the brain of it. But the actual actions we'll define soon. So if you, sorry, I didn't show that. If you click here then on the previous selection, or if you press escape, you'll go back to the main Play Canvas editor. Now we have our graph here. What we want is to apply that animation to our model. So if you go back here to player and then select the character medium here, we need to add that component for the animation. So let's add a component, anim. So this should be the first one, not this one. Anim. And then just drag in the state graph that you just created. Just drag it in. And now here, as you can see, the idle and run states that we created. Now there are some slots for the actual animation clips for us to link, for us to attach. Before we do that, though, make sure that on the speed you put that to 1.22 because from the test that I was running, this is a good number for, like kind of looks like it's running across the board. Looks pretty cool. So again, 1.22 should be fine. And then on root bone, select the root entity here. So if you just expand character medium, be sure to use root bone there. This is just to make sure that the, this is a little bit technical on how animations work, but this is just to be sure that the animation and your entities on the scene are referencing actually the same transforms. This only makes sure that everything lines up properly. Just a nice thing to do. And then here on idle and run, let's assign the animation clips. Now if you open here idle, actually sorry, we'll need to make sure to select. So select character graph, sorry, select the character medium here to be sure to select, to have the slots here. So select character medium on your hierarchy. And then from here, we'll need to select those clips. So you'd see that it's kind of like a, like a blackie thing from the movies. Like there's a, what's the name of that black? I don't know. So you need to select, in this case, it's root idle dot GLB. So just drag that into the idle. If you're not sure which one to use, there's a very nice way to preview, which is just by clicking on it. If you click here, you see, okay, like this is just kind of standing still. There's a character here, but in this one, okay, there's a nice animation here. So this only shows like the wireframe view, but you can check like what actually goes on. This looks like an idle animation. So this is what we want. So I just dragged in that one here and then same thing for run. So I just like to run a folder and I want the root run GLB. Just drag that in. Again, it's the file that has the movies thing. And if you're not sure, just select. Okay, this is a nice running animation. Okay, cool. Cool. Now we made some changes there. Let's be sure to apply that to our template. All right. So here on the player template, make sure to apply all that it applies to our template. Okay. Now if I just press play here. Okay. So it's no longer on T-pose. That's good. So it's using the idle animation, but we are not changing the speed number. So even if we're moving about it, the animation stage graph, still things that were idling. So we need to make sure to set the speed parameter according to the actual speed that the player is going at. And the way that we do that is if we open up the editor. So there's a nice to do animation here. What we need to do is we need to somehow reference the animation component on the model, right? On the entity and then set the parameter. Now we already have this setup, right? The model entity. And if we look at our setup, the model, sorry, the animation is on the same entity as the model. So we can just reference the character medium and get the animation component out of it, right? The anim component out of it. So that's very handy. So it's again, model entity, right? So or whatever we called here. So if you go this dot model entity, which again is referencing the same entity that has the animation. And you say dot anim. So now we're referencing the anim component of that entity. And then we do set float. Now set float, you need to specify two things. First, you need the attribute name. So we called it speed and the actual number, right? Now the number, we could, like the easiest way is just to use the actual number that the rigid body is using. Like if it's applying force and then sometimes it's hitting, blah, blah. We want the actual number that the rigid body is using. And the key for that is this dot entity dot rigid body. So with this, we got like the reference to the rigid body, right? And then we want the linear velocity, which again is the, so that is the velocity on the X and Y and Z axis, not the rotation. So how much it's actually moving. But this is a back three. So this is like an actual three dimensional thing, but you want like the length of it, right? You want the magnitude of how much it's actually moving. So we can just say dot length, which is a, which is a And also you can just check the reference for that. So with this, we're getting the magnitude of the linear velocity vector and passing that into our animation. So if we say that, go back to the game, we have to reload to reload the scripts because the scripts are already loaded. And then we move. Nice. Now what happens if I keep pressing D to move to the right and then I hit that collision right there. Let's see what happens. Oh, it stopped because it's using the actual rigid body. It doesn't look like the, you know, the classic thing of in video games or the characters like running against the wall like that. This actually makes a little bit more sense, which is pretty cool. We've got that out of the box. Cool. Right. So now we have some very nice integration here now. Now, if we take this player and we go over the apple here, nothing happens because we didn't add anything to do that. So let's add that. Let's add a trigger so that when a player goes over the collectible, it does something. Going back to the editor here, let's select our food. So we need to add a script for that. Right. Let's so here on collectibles, let's create a new folder and let's call it scripts. Then within the scripts folder in the collectibles, let's call it collectible controller. This is what I mentioned that we'll be creating some new scripts rather than just using the script templates. Right. This is the only script that we'll be creating from scratch. But this is interesting because then if we create a new collectible, sorry, if you create a new script and just edit that, this is what every new Play Canvas script comes with. Right. So it already contains the boilerplate code of like creating and attaching like a PC, which is like a Play Canvas script and already have like the initialized method and an update method. Now this is a topic for another workshop about hot swapping, hot reloading. So I'll leave it to you guys to take a look at later. I'll just remove this because we won't need it. And also we were moving the update method. We don't need the update method. What we need is some way to say whenever an entity, whenever a collider passes through this, like it was picked up. Right. And we need to do something about that. So the way that we'll do that is since this script will be added to the same entity that has the collision, we just have to listen for an event on the collision. Now this is very important topic on Play Canvas, which is events. Now events in Play Canvas are fired and triggered by all the different components and it's all documented, but it's very important to understand the events drive the behavior of different things. What I mean by that is the collision component, for instance, every time that another collider enters through it or lives or is still there, it fires an event. And if anyone is listening to that event, you can do stuff based on that. So you don't need to constantly keep checking has it collided, has it collided, has it collided. You don't need to do that. You just need to subscribe to different events. And this is what we'll be doing. We'll be subscribing to the event. I would call it attaching. We'll be attaching to the event on the collision component that says something entered this collider. The way that we do that is on the initialize method. So we'll attach right when the game starts. We'll say this dot entity dot collision. So with this, we are referencing the collision of the entity because this is the, again, this is the collectible controller. This is the script itself. We're not talking about the script. We're talking about the entity in which the script is attached to. So this dot entity dot collision. So now we're talking about the collision. And now we'll attach to that event. And to attach, we'll say dot on. So dot on literally means like whenever this happens on this event, let's do something. The name of the event is trigger enter all our case altogether. And again, this is all documented on Play Canvas reference about what are the events that the collision triggers and how can I use them, et cetera. So whenever there's a trigger entering on this particular collision, we can provide a callback. Now callbacks in javascript can be done by just referencing another function or we can create one right here. So we can create a new function, an anonymous function by doing the following syntax. So it's open and close brackets because there's no, so this will have any parameter. And do equals more than, so a narrow. And you can provide any code that you want here. So this is the code, right? This is the function that will be called whenever the collider that's attached to this entity has a trigger, right? And what we'll do here is, so one thing it needs to disappear, right? So do this dot entity dot destroy. So it will disappear. But more than that, we needed to also, we need to let the game know, oh, you picked up something. Now, the script templates that we have set up there are listening for events as well. So events can come from components, right? So for instance, the collision component, or we can fire our own events. So you can tell everyone on the scene, you can say everybody on the application say, oh, by the way, this happened. And the way that we do this is say this dot app. So remember this dot app is in reference to the to the Play Canvas application and say dot fire. So with this, we are firing an event so that if anyone is listening to this particular event, such as we are listening to trigger event, trigger and around the collision, they'll immediately receive that message. And we can say collect it. So this is the name of the thing that we called. We can find more about it later. But if you just say this dot app, this dot app dot fire collected anything that is listening on this dot app. So for instance, if they do this dot app dot on, sorry, collected. If they do this, they'll receive the message. Cool. So that's it. So this is the code that we need for the collectible controller. Whenever there's a trigger, we fire that event and then we destroy it. Then what we do on the food here is then we need to add that script now. So then we go here, add component script, and then we add our new script called collectible controller. And then again, we need to apply to make sure that we apply to the templates. And if you created more than one template, like have bananas and cakes and you ported your own assets and you want to use, make sure to add those to the collectible controller to all of that. Let's test it out. Press play. We're almost done with phase two. Don't worry. And then I walk over there to the Apple. Oh, cool. Right. It disappeared. Nothing else happened because we didn't do anything else. So currently our game is walk towards the Apple. Cool. Right. Nothing happened. So let's move on right now. Now, what we want now is to use now the script templates that we have here, right? Because again, it contains like the boilerplate code for the actual gameplay that you're free to look at later, copy and make it your own. But what we'll need to do is we'll need to create a game manager. So we have a game manager entity. Let's create a game manager. It's called game manager script. And let's add the game manager script, which again is right there, right? Let's take a look at what this game manager is doing. Make sure to click on parse, by the way, because it has some settings there. So if you open the game manager script, it already has a bunch of stuff that I won't have time to go through here, but it basically does stuff like, oh, am I playing around? Is the round over? It does stuff like counting the time. So for instance, in the update method, it decreases time as time passes and then it fires an event when the time's up and et cetera. Now, this game manager already contains like most of the code that we need to make it work. And if you remember, it's already listening to the collected event that we just created on our collectible controller. So as you can see, we're already attaching to this event. So the game manager will work flawlessly with the collectible controller that we just created. So, okay, we're good to go. So now the game manager is receiving the message as you want. Now, by the way, game manager is an interesting name to give to exactly is the script that doesn't handle anything, any particular like entity or thing in your game, it just handles like the overall thing, right? Okay. I collected something, the players over there. I'm starting a new game. I'm restarting the game. It is the thing here. And if we do a command F or control F to do, there's one thing left, which is on the spawn collectible. Now the spawn collectible already has some code here, which is basically, what it basically does is it looks for a random position on the board for a place that doesn't have an obstacle on it. So this is already implemented for us. You can take a look, take a look here at how it's implemented. You can make it on your own. You can copy it. But what it's not doing is it's not spawning. So if you play this and we check it out, it's not spawn anything. We need to add that personality ourselves, because I want to show you guys how to do that. At this point, at line 92, we already have the new X and new Z, which is exactly which position we want to spawn. But we need to do that. Now, in order to do that, first, we need a reference to the templates that we created. And this is why I wanted to create a template out of the food items that you created. So for instance, the app we have is a template that we saved here. Food. So what we want is on the game manager, we'll add an attribute so that we can reference that template and then we can dynamically instantiate it as we pick up stuff. So what we'll do is we'll say game manager, because now it's a game manager script, .attributes.add. And let's call it food template. And then for type, it's not number, it will be asset. So this means that we're not going to reference anything that's on the scene right now. We're going to reference something that is on the project asset. Now, we don't want to reference any asset. You cannot instantiate a texture, but you can only instantiate a template. So we're going to say that the asset type is template. So this means that food template can only accept references of type asset, which are templates. So we're assured that it's only template. By the way, if you guys have created like more than one, if you created like multiple, sorry, multiple food templates, you have banana, you have apple, et cetera, you can say, in the args, you can say array equals true. If you do array true, this means that you can set more than one food template and then food template will become an array. And you can do like dot length to see how many were added, or you can reference by with the caution thing. In our example, there's only one, so we'll keep it without the array thing. Now, what we'll do, so I have food template here. Let's go back here to the spawn new collectable position. What we'll do is we're going to say this dot food template, right, dot resource dot instantiate. Sorry, instantiate. Now, we need to do dot resource because we want the actual raw template from it, the actual data that contains from the asset reference. And then we want to instantiate that. So this is the api to do that, so dot resource that instantiate. This will create a new entity, which is a copy of that template. Now, we want to do stuff with it. We want to move it where you want. So let's reference it somewhere. So let's say const new entity equals the instantiate. And let's say new entity dot set position and new x, zero, new z. So now we're going to position the new entity that we just created to that position that we already found before, right, that the code's already done. But if we just do that, we create a new entity, but it's not added to the scene yet, actually. We need to actually make it a part of the scene, otherwise it's hidden away and it'll be deleted afterwards. So we need to add it somewhere. We need to add it to the hierarchy. Let's just add it as a child of the current entity. So let's say this dot entity dot add child new entity. There we go. Now, with this, we're creating a new entity, which is an instantiation of the template, setting the position, and then adding it to the scene. If we press play on this now, oh, no. On a game manager, we need to parse the script, and you need to reference our template here, right? So wherever you saved the food template, just reference it there. If you set up everything correctly, you should be able to, it will highlight, you'll be able to simply drag and drop there. Now, let's play here. If we press play here, okay. We're able to walk around, right? So this is all working. But then, when we pick this up, it will send a message to game manager, then it will be destroyed, and game manager will spawn a new one. So it spawned one right there. So if I picked it up, okay, now it spawned one there. So you can see how the game is almost ready, right? We're moving forward, right? Now, one quick thing that I want to talk about, very important. So this is the last part of phase two. Phase three will be pretty quick. That if you, you can open Chrome devtools by going to a view. Yeah. So on view, if you're using Chrome, so view developer, javascript console. And then from here, you can check anything that happens in your code, and you can add the bug points like as you go. So if you see here on sources, and you open api assets files, you can see that here we have the exact same hierarchy that we have on the editor. So we have food run, script templates, all the scripts are here in the exact same folder structure. So if you want to see what's going on on player controller, let's say that we have a bug there. Well, I want to know what's going on there. You can inspect the code, make sure that the code is correct. And you can just edit the bug point. And then this will happen only when I press W. So let's press W. Okay. So it stopped. So it paused on breakpoint. And we can check what is DT value, you know, what's X and Z, what is, which keys are being pressed, you know, what's the state of our, of our entity. You can check everything right here from the bug tools. You can just press again to continue. Let's, I was pressing W before, so let's, let's remove that. Oh, okay. I fell out of the, out of the, of the game world. Cool. That is, that is pretty cool. So, so out of the box things, things are here, right? You can also check on network, type exactly what are the files that you downloaded. And you can use the console to do more stuff as well. So if you want to see, like if you want to do, for instance, if you want to fire an event, you can do it. By the way, you can use this.app.fire. Right. I'm sorry. It's pc.app.fire. So, so if I, for instance, if I fire the collected event, yeah, it just, it spawned a new one because it thinks that the game manager thinks that something was collected, but it didn't. So you can like do all sorts of crazy stuff here from the console and from sources. So it's a very powerful tool for you to debug stuff, like out of the box. Cool. Okay. Finally, we have, so this is phase three. Let's go back to the slides real quick. So, so that was phase two. Sorry, we'll move on to phase three. Phase two was a big one. So we learned about creating using scripts. We learned about events. We learned about physics. We learned about animations. We learned about all sorts of crazy stuff. We, I'm not sure how much we can, we can do in the last five minutes, but let's, let's see how far we go. So let's, let's move on to phase three. Now phase three, we also have the same setup as before that we have the project ready to go for phase three. Let me open that up. Let me send it to you guys. This is phase three. So again, if you got lost in any place and you want to continue from now on just fork from this one and also send it on a discord or you can just continue from where you are. So I'll be continuing from, from where you currently are because we're like, we're almost there. I think we won't have time to do audio. I think we might not have time to do it, but let's do the UI. Let's at least do the UI. Now for, for phase three, we'll do, the idea is doing user interface audio and publishing. I think we'll only do user interface and then publishing because I think user interface is pretty cool. And, you know, like we cannot really see what's going on there. You know, like we don't know if we're winning, if we're losing, you know, like the, the game's playing and we're not sure how much time I have left. So, so let's, let's start with UI. So, so if you, if you, if you're forked from phase three or you're, you're falling with us so far, you have a setup very similar to this and you have stuff on, on, on script templates as well. Now what we'll do is let's create actual the UI screens first. So if again, if you go back to, sorry, this is very annoying. If you go back here to the, to our example, the UI is basically, so you have this, this time thing right here, a score text. And if you remember when we lose, there's a big screen saying, oh, time up, you know, like round over and then a button to start again. So let's, let's do that real quick. Let's see if we can do that on time. That will be a challenge. So on route, let's create a new entity and we'll create a user interface. We'll create a 2D screen. Now everything that you want to be on screen space, it needs to be under a 2D screen. Otherwise it will be like on the, on the, on the, on the, on the 3d world. So let's create a 2D screen. Since it's always screen space, you can move it up. So it's not like in the middle of your hierarchy there. So you can move it up wherever you want. It will always be in, in screen space. And let's add, let's start by adding a text, which will be the score. So here let's create a new user interface, text element. Where is it? There. I'd be sure to be, to face the right side there. If you don't see it, it's probably on the other side by the way. So when you create a new text element, it assigns already like a random font, not random font, like the first font that you have in your, in your, in your project. So on a food run here, we have a lot of interesting fonts ready set up. So you chose the first one, Kenny Blocks. But you can choose any one. Let's choose this one, whatever. Kenny Mini, Kenny Mini Square. Yeah. So I chose this one again, just drag and drop. And then we can move it about wherever you want on the screen. Now by default, the element will be anchored to the center of the screen, which means that if I play, let me do it like this. If I play text areas on the middle and I rotate like this, if I do anything on the screen, play on a mobile device, it will always be there on center of the screen, which might make sense depending on your use case. But we want one that says like there on top, on a top left, right? So if it's on a top left, if you want things to be on top left, what we do is on text, you can set the anchor to be there. So we already have some presets here. So on preset, if you just press top left anchor and pivot, it will automatically set the pivot and the anchor to the top left. And then we can move it down like this or something. Let's call it score text, score zero, right? So now we have the text ready for the script that will actually put the scores as we move along. Now we're almost at time. If you guys are happy to continue, let's continue. Let's continue for now. If you guys need to drop, then fine. But I think we can, let's go ahead for now. Let's see how far we can go. I'll go as fast as again. But again, I think the recording will be available later as well. So you can take a look at that later. And also again, the full project will be available. Yes, we did start a few minutes past. Let's see, five minutes. What are we going to do? Let's see. And the full project again will be available later if you guys take a look at like the full thing and with a little bit more like breathing. So we have the score text. Now let's add this time right here, this bar. That's pretty neat. So the way that we'll do that is the following. We'll create a new entity as well, a new user interface. Let's call it an image element. So this image element, let's also put the preset on the top left anchor and pivot. So it's there. By the way, this means then that as we move stuff around, it will always be on the top left there. So it's always there. Very, very useful for creating responsive UI for mobile devices that you can rotate or not. Yeah, this is a pretty neat feature. If you select here on the resize element component, you can easily resize the size thing. So let's create that background. So this is the background here, that white thing on the background. And then let's create a new entity to be like the middle part. So now let's color this one, the green tint that we have there. Now we want it to be like to take the full size of the time. But then as the time goes on, it should go like this or like this for you guys. So the way that we do that is very easy to set up the anchors so that it's exactly, it matches the size of the parent. So we can match the size of the parent by setting the anchors to be all of the corners. So you can either select this gizmo right here and then go here. So this one should be here. This one should be there. This one should be there and there. And this means that the anchor is zero, zero, one, one. Now it has some margin, which we don't want, so it's taking more space. So let's reset the margin there. So it's all zero. Now it's taking the full space. But if the margin then is decreasing like this, you can see this is the fact that we want. Like as the time goes on, we want the margin to increase. So we have all the setup there that we need to make that happen now. Let's take the score here. Let's put it up there now. Now, actually, this is everything that we need. Now, of course, we need to add the text that says Simon, et cetera. Let's not do that. But what we'll do is on the 2D screen here, let's add a script and let's add the UI controller script. The UI controller script already has all the code that we need to make all of this thing already set up properly, like exactly as we need. But we just need to make the references to the right entities. So the first one that we need, we need the game manager entity. So this is the entity which contains the game manager script. So let's look at that. Now we need a time bar entity. So that's the green one. Okay, cool. The score text entity. Cool. Let's put that on there. Now, if you remember here on the example, there was that eat me there string that was there. Let's skip it out for now. That's not created. On the round over screen entity and round start, so this is the game over ones, right? So let's not do anything on those right now. So otherwise it will take too much time. But now if you press play on this, and you check out what we just did. Oh, no, cannot set properties of no setting enabled. Yeah, probably because I didn't set one of the things. Let's fix that. So this is very useful, by the way, to have these things here. If you click on the button, it will automatically open up the code editor for you with syntax highlighting and highlighting exactly where it went wrong. So now it's using this, right? So let's just do, let's just check if this is not no, then do the thing. Otherwise, let's just keep it. Let's save it. Refresh. Okay. Now it only starts when you pick up something, right? So let's pick up some, that's what happens. Oh, there we go. Okay. So now we have the scores. Now we can see that the time is going down. And then if you pick it up, it goes. Oh, yeah. Okay. So now we would have lost, but again, we didn't finish setting up all different stuff. Now, if you want to see the full setup, all that you need to do, let's do that right now. Since you don't longer have time, let's now check out the full project. So again, I'll send it soon. Cool projects. Let me send you in the chat. This is the full project that you can take a look and you can fork as well, if you want. The final setup is basically just setting up all the different UI controllers there and creating this screen right here, which has this button on the bottom, which already contains some hints, some hover, press tints. So as you press, as you go over, it does different things and already has the listeners up when you click to restart. So you have everything there that we need. And this is the full project that, again, go ahead, guys. Go crazy on it and you can take a look at everything that is done. Feel free to use, I mean, all the assets are CC0 by Kenny. Do give him a shout out if you use any of those. Make sure to add the proper credits. And this is the final game. Let's see if you have anything else there on the slides. But I think that was it. So yeah. So we didn't learn how to use audio, but I think that's for the next one. Be sure to take a look at the audio manager script there. They will see exactly how it's done. And this is a wrap up. Oh, by the way, one final thing. If you want to publish to Play Canvas, this is super important. On the toolbar here, the last toolbar here, publish slash download, just click it. Publish to Play Canvas. Give it a name. Give it a description. That's optional. Just the title is required. Press publish now. It's building. There it is. It's building up. Click. This is a permanent link to your particular build. So this is yours forever. Never be taken away from it unless you download, unless you delete the project or delete the app. Otherwise, you can send this to everyone. You can send this to anyone to play around with your game. And also, be sure to tweet if you do it. Be sure to tweet about it. I'll retweet. We'll help each other out. We'll follow each other to help out those in-game devs. Because again, this is playable for anyone. You can play it on mobile. You can play it on multiple platforms. Out of the box, there you go. So this is how you do publishing on Play Canvas. I took 30 seconds or something to do that. There you go. And yeah, that is Play Canvas. Thank you very much. Sorry for running a little bit over time. I think I was a bit too optimistic when I was preparing this. I did practice it twice. I did manage to do it on time. But I think I spoke too much today. I hope it was great.