# Tutorial: SURVIVORS-LIKE w/ Unity DOTS & ECS

https://www.youtube.com/watch?v=cc5l66FwpQ4
Translation: zh-CN

[00:00] What you are seeing behind me is DOT Survivors, a complete game developed by me using Unity's data oriented technology stack and their entity component system.
  我身后的这个是DOT Survivors，一个由我使用Unity的数据导向技术栈及其实体组件系统开发的完整游戏。

[00:10] And it's available right now for free on Steam.
  它现在可以在Steam上免费获取。

[00:13] However, Dot Survivors is more than just a game.
  然而，Dot Survivors不仅仅是一款游戏。

[00:16] It's actually a massive learning resource to help you learn about creating a game using Unity Dots and ECS.
  它实际上是一个巨大的学习资源，可以帮助你学习如何使用Unity Dots和ECS创建游戏。

[00:21] Now, I'll talk about that a little bit more in just a bit because that's actually not the topic of this 5-hour long video.
  现在，我稍后会详细谈谈，因为这实际上不是这个5小时视频的主题。

[00:27] Uh, what I wanted to do with this video to kind of celebrate the launch of dot survivors is put up a simplified tutorial where I'm creating a survivors type game to help you learn to create your first game with Unity Dots or ECS.
  嗯，我想通过这个视频来庆祝dot survivors的发布，是做一个简化的教程，我将创建一个survivors类型的游戏，以帮助你学习如何使用Unity Dots或ECS创建你的第一个游戏。

[00:41] So, this tutorial video is going to focus on the technical implementation of creating an ECSbased project.
  所以，这个教程视频将侧重于创建基于ECS的项目时的技术实现。

[00:49] If you want to learn a little bit more about the theoretical side of things or if Dots and ECS is even right for you, I'll point you to another video that I created where I kind of go into more of the theoretical side about what exactly
  如果你想更多地了解理论方面的东西，或者Dots和ECS是否适合你，我会推荐你观看我创建的另一个视频，在那里我将更深入地探讨理论方面的内容，关于究竟是什么

[01:00] dots is and if it makes sense to use it in your game.
  点是否以及是否可以将其用于您的游戏中是有意义的。

[01:04] Now, with this video, I wanted to go into a bunch of the very common concepts that you'll see in ECSbased projects.
  现在，通过这个视频，我想深入探讨一下您将在基于ECS的项目中看到的一些非常常见的概念。

[01:10] And hopefully by the end of this, you'll have enough knowledge and tools to create your own projects using DOT and ECS.
  希望到那时，您将拥有足够的知识和工具来使用DOT和ECS创建自己的项目。

[01:16] Now, if you do want to take it a step further, I'll point you over to the Unity asset store where I just put up the project files of the shipped Steam game.
  现在，如果您想更进一步，我将引导您到Unity资源商店，在那里我刚刚上传了已发行的Steam游戏的项目文件。

[01:22] Survivors for sale.
  幸存者出售。

[01:25] So, this includes all the source code developed by me, as well as all of the art and audio assets created by Sill and EML of Penzilla Design.
  所以，这包括了我开发的所有源代码，以及Penzilla Design的Sill和EML创作的所有艺术和音频资产。

[01:34] By the way, I think they both did a tremendous job on this project, and I'll leave some links to their website down below if you'd like to hire them or purchase any of their asset packs.
  顺便说一句，我认为他们俩在这个项目上都做得非常出色，如果您想聘请他们或购买他们的任何资产包，我将在下方留下指向他们网站的一些链接。

[01:43] Now, with this asset store product, I'm not just going to give you all the project files and let you figure everything out.
  现在，对于这个资源商店产品，我不仅仅是给您所有的项目文件，让您自己去弄清楚一切。

[01:48] Um, I actually have a massive documentation site that includes documentation on a few dozen of the most important concepts of this game.
  嗯，我实际上有一个庞大的文档网站，其中包含了这个游戏中几十个最重要概念的文档。

[01:56] And if reading through a bunch of documentation isn't really your
  如果阅读大量文档不是您真正喜欢的

[02:00] thing, don't worry.
  事情，别担心。

[02:02] I do have video overviews where I go over the code as well as any editor configurations for each of these concepts.
  我确实有视频概述，在其中我将介绍代码以及这些概念的任何编辑器配置。

[02:09] And there's also a full API section of the documentation where you can get more information about each and every type.
  文档中还有一个完整的API部分，您可以在其中获取有关每种类型的更多信息。

[02:13] So pretty much I wanted to document everything in this game.
  所以基本上我想记录下这个游戏中的一切。

[02:17] So if there's anything in particular that you're interested in, you can learn about it.
  所以，如果您对任何特定内容感兴趣，都可以了解它。

[02:21] Anyways, I could talk about this asset store product all day long.
  总之，我可以一整天都在谈论这个资产商店产品。

[02:24] So I'll just leave you some links down in the description below if you want to learn a little bit more about it.
  所以，如果您想了解更多关于它的信息，我会在下面的描述中留下一些链接。

[02:28] So let's get back to today's tutorial, this survivors type game.
  那么，让我们回到今天的教程，这个幸存者类型的游戏。

[02:33] Now, I do think that before going into this tutorial, it would be helpful to kind of understand some of the core concepts of data oriented design.
  现在，我认为在开始本教程之前，最好能大致理解一些面向数据设计的核心概念。

[02:38] And again, I'll have some resources down in the description below where you can kind of familiarize yourself with these things.
  再说一遍，我会在下面的描述中提供一些资源，您可以在其中熟悉这些内容。

[02:45] because if you don't, then you might kind of think that a lot of the things that are happening in this video are a little bit over complicated or you know, it's like why would we do something this way we can when we can just do it in regular Unity some other way.
  因为如果您不这样做，您可能会觉得此视频中的许多事情有点过于复杂，或者您会想，为什么我们要用这种方式去做，而不是在常规的Unity中用其他方式来做呢。

[02:57] So I think having some kind of the fundamental
  所以，我认为拥有一些基础的

[03:01] Theoretical understanding of these data oriented design concepts would be quite helpful for additional context.
  对这些面向数据设计的概念的理论理解将为提供额外背景信息提供很大帮助。

[03:08] But certainly to follow along with this video, you will need to be fairly familiar with C and Unity.
  但当然，要跟上这个视频，你需要相当熟悉C和Unity。

[03:11] Now, I'll put the specific Unity version and ECS versions that I'm using up on screen right now, but the hope is that Unity doesn't change things so much that this tutorial should be relevant throughout the Unity 6 generation as well as the uh Unity ECS 1.x.
  现在，我将把我正在使用的特定Unity版本和ECS版本显示在屏幕上，但希望Unity不会改变太多东西，以至于本教程在Unity 6以及Unity ECS 1.x版本中都具有相关性。

[03:33] And if you'd like to, you can follow along with your own empty project.
  如果你愿意，你可以跟着你自己的空项目进行。

[03:34] However, uh down in the description below, I also have some links to the starter and final project files.
  但是，在下面的描述中，我还有一些指向起始和最终项目文件的链接。

[03:41] So, if you want to start with the same project files as me, that's awesome.
  所以，如果你想和我一样从同一个项目文件开始，那就太棒了。

[03:44] I do have um some of the art assets from the dots survivors project included in that starter project.
  我确实包含了一些来自dots survivors项目的艺术素材在这个起始项目中。

[03:49] Um that's what we're going to be using to develop this project.
  嗯，这就是我们将要用来开发这个项目的。

[03:53] And there's also a custom uh animation shader and some UI stuff.
  还有一个自定义的动画着色器和一些UI的东西。

[03:58] Um just kind of like regular game object prefabs that I don't want to uh
  嗯，就像我不想做的常规游戏对象预制件一样。

[04:03] spend time in the tutorial, you know, showing you how to create a little UI thing.
  花点时间看教程，你知道的，教你如何创建一个小的UI东西。

[04:08] Um so, all that stuff is just saved within those project files.
  嗯，所以，所有这些东西都保存在那些项目文件中。

[04:10] So anyways, without further ado, let's get into the tutorial.
  那么，废话不多说，让我们开始教程吧。

[04:14] Okay, so just going to start things off with a quick little overview about what you need set up in your Unity project before following along with this tutorial.
  好的，那么我们就从一个快速的概述开始，介绍一下在跟随本教程之前，你需要在Unity项目中设置什么。

[04:24] Now, if you download the starter project files that are available through the link in the description below, all this stuff should be set up for you, but still good knowledge to have if you want to create your own ECS project.
  现在，如果你下载了通过下方链接提供的入门项目文件，所有这些东西都应该为你设置好了，但如果你想创建自己的ECS项目，了解这些知识仍然很有用。

[04:31] So, so far you'll see that I've included some of the art assets.
  所以，到目前为止，你会看到我已经包含了一些艺术资产。

[04:35] So, I do actually have a font that we're using um as well as some materials all set up for the different alien astronaut background uh some little boulder environment elements, the gem, the plasma blaster, and the reaper alien.
  所以，我确实有一个我们正在使用的字体，以及为不同的外星宇航员背景设置的一些材质，嗯，一些小的巨石环境元素，宝石，等离子爆能枪，以及收割者外星人。

[04:53] Um I do have a animated shader available as well.
  嗯，我也有一个动画着色器。

[04:54] And I'll be going over um kind of how this works and how I'm using this to actually create 2D animations in this project.
  我将介绍一下它是如何工作的，以及我如何使用它来在这个项目中创建2D动画。

[05:01] Of course, I have all the sprites available for all
  当然，我拥有所有可用的精灵图，用于所有

[05:03] the necessary art assets.
  必要的艺术资产。

[05:05] As far as scenes go, I have um just a title scene here that's set up um with just the game logo as well as buttons to play and quit.
  就场景而言，我有一个标题场景，设置有游戏徽标以及播放和退出的按钮。

[05:14] And this has an attached UI controller already set up for it, so you don't need to worry about that.
  并且它已经附带了一个UI控制器，所以您不必担心。

[05:20] And then going into the moon level, um we do have a couple things set up for just the Cinem Machine camera, which will be kind of completing the setup of that when we get to that portion.
  然后进入月球关卡，我们确实为Cinem Machine相机设置了一些东西，当我们到达那部分时，将完成该设置。

[05:28] Um, I do also have this game UI which does include a uh pause screen where you can resume or quit because we will be implementing gameplay pausing and we will have a game over state as well.
  我还有这个游戏UI，它包含一个暂停屏幕，您可以在其中恢复或退出，因为我们将实现游戏暂停功能，并且还将有一个游戏结束状态。

[05:40] So this game over screen uh will display and prompt the user to quit back to the main menu.
  所以这个游戏结束屏幕会显示并提示用户退出到主菜单。

[05:46] And then this game UI also does have its own game UI controller script on it.
  然后这个游戏UI也有自己的游戏UI控制器脚本。

[05:49] And um so everything is kind of wired up.
  所以一切都已连接好。

[05:53] So all the kind of game over stuff and pause menu stuff is all functional.
  所以所有的游戏结束内容和暂停菜单内容都是可用的。

[05:59] We're just going to have to kind of invoke it from some things on the ECS side and we'll be getting to that kind
  我们只需要从ECS方面的一些东西来调用它，我们将开始处理那部分

[06:04] of like towards the end of this tutorial.
  就像在本教程的最后。

[06:07] As far as scripts go, I just have the UI scripts.
  就脚本而言，我只有UI脚本。

[06:09] So here's the game UI and main menu um UI controllers and then I just have the text mesh pro essentials imported as well.
  所以这是游戏UI和主菜单的UI控制器，然后我还导入了Text Mesh Pro Essentials。

[06:14] So that's as far as asset goes.
  所以，就资产而言就是这样。

[06:16] As far as uh the actual project setup, we can go up to window and package manager because there are a couple packages that are required.
  就实际项目设置而言，我们可以转到窗口和程序包管理器，因为有几个程序包是必需的。

[06:24] So, most of these things are just imported by default when you create a new Unity project.
  所以，当你创建一个新的Unity项目时，大多数这些东西都是默认导入的。

[06:27] Um, however, I I added four additional packages.
  嗯，但是，我添加了四个额外的程序包。

[06:30] So, we have the Cinem Machine package because we're going to be doing some stuff with the camera following using Cinem Machine.
  所以，我们有Cinem Machine程序包，因为我们将使用Cinem Machine进行一些相机跟随的操作。

[06:37] Also have the entities package.
  还有实体程序包。

[06:39] Of course, this is an ECS tutorial, so we need the entities package.
  当然，这是一个ECS教程，所以我们需要实体程序包。

[06:42] entities graphics package is also required because we're going to actually be rendering things on the ECS side.
  实体图形程序包也是必需的，因为我们实际上将在ECS端进行渲染。

[06:48] And then finally, the last package that we need is the Unity physics package.
  最后，我们需要最后一个程序包是Unity物理程序包。

[06:52] And this is the Unity dots physics package.
  这是Unity dots物理程序包。

[06:58] And that's because again all of our collisions and everything like that is all happening on the dots and ECS side.
  这是因为我们所有的碰撞以及类似的东西都发生在dots和ECS端。

[07:04] I will point out that uh the Unity physics package.
  我想指出的是，呃，Unity物理包。

[07:08] Also, if you do go to the samples tab, there are these custom physics authoring components and I typically use these although Unity seems to be pushing people towards using just the regular Unity physics components to essentially author our dots physics components.
  另外，如果你去到samples选项卡， there are these custom physics authoring components，我通常使用这些，尽管Unity似乎在推动人们使用常规的Unity物理组件来本质上编写我们的dots物理组件。

[07:22] Um, so that's what I'm going to be showing in this tutorial today.
  嗯，所以这就是我今天要在这个教程中展示的内容。

[07:25] But do just know that if you do want to experiment with the custom physics authoring components, they're just available through the samples tab on the Unity physics package in the package manager.
  但请注意，如果你想尝试自定义物理编写组件，它们可以通过包管理器中的Unity物理包的samples选项卡获得。

[07:34] And then as far as project settings go, there are a couple things that we need to set up.
  然后就项目设置而言，有几件事我们需要设置。

[07:39] So if you just go to edit and project settings um in the window here, we can go to the editor section and scroll down towards the bottom and there's these uh enter play mode settings.
  所以如果你只是在窗口中转到编辑和项目设置，我们可以转到编辑器部分，向下滚动到底部， there's these uh enter play mode settings。

[07:50] So, this says when entering play mode, we'll make sure this is set to do not reload domain or scene.
  所以，这表示进入播放模式时，我们将确保将其设置为不重新加载域或场景。

[07:57] And this is just kind of the u what they call the fast enter play mode options.
  这只是他们所谓的快速进入播放模式选项。

[08:01] So, you'll see that when we actually get into testing our game, you know, after
  所以，当你实际开始测试我们的游戏时，你会看到，你知道，之后

[08:05] Unity is already compiled and everything like that, when we hit play, it just kind of immediately puts us right into the game and we don't have to kind of wait around for domain reload, which is real nice.
  Unity已经编译好了，诸如此类，当我们点击播放时，它会立即将我们带入游戏，而无需等待域重载，这非常好。

[08:15] Now, there are some limitations for using this.
  现在，使用此方法存在一些限制。

[08:16] if you're using any type of static variables.
  如果您使用任何类型的静态变量。

[08:18] These actually do not end up getting reset.
  这些实际上不会被重置。

[08:20] However, we're not using any static variables in this project, so that is acceptable in this case.
  但是，在此项目中我们不使用任何静态变量，因此在这种情况下是可以接受的。

[08:26] Then going down to the physics settings, we can see the layer collision matrix here.
  然后向下滚动到物理设置，我们可以看到这里的层碰撞矩阵。

[08:30] And I've added a couple of physics layers.
  我添加了几个物理层。

[08:33] And again, this is just kind of through the regular Unity layers workflow.
  再说一次，这只是通过常规的Unity层工作流程进行的。

[08:36] And because we're using Unity's regular physics authoring components, um this is actually how we manage kind of the different types of objects that can collide with each other and all that.
  而且因为我们使用的是Unity的常规物理创作组件，所以这实际上是我们管理可以相互碰撞的各种对象类型的方式。

[08:52] So I've added um different layers for player, enemy, player attack, environment, and gem.
  所以我添加了玩家、敌人、玩家攻击、环境和宝石的不同层。

[08:55] And so these are all the things that are essentially going to be available to us in our game.
  所以这些都是基本上将在我们的游戏中提供给我们的东西。

[09:01] And you'll see that the layer collision matrix is very simple.
  您会看到层碰撞矩阵非常简单。

[09:03] You see that I've
  您看到我已

[09:05] just kind of like disabled most of the options here.
  这里的大部分选项都被禁用了。

[09:09] And then we have the player um colliding with gem, environment, and enemy.
  然后我们有玩家与宝石、环境和敌人碰撞。

[09:15] And then the enemy just needs to collide with player attacks as well as other enemies.
  然后敌人只需要与玩家攻击以及其他敌人碰撞。

[09:20] So a pretty simple collision matrix.
  所以这是一个相当简单的碰撞矩阵。

[09:21] And that's all we really need for the interactions that we're using in this little tutorial project.
  这就是我们在这个小教程项目中进行交互所需的一切。

[09:26] Okay, so I think that's enough little project overview.
  好的，我认为这个项目概述就够了。

[09:28] So let's get into actually creating some ECS things.
  那么让我们开始创建一些 ECS 对象吧。

[09:32] So the first thing that we're going to do is inside this moon level, we're going to rightclick and go to new subscene and empty scene.
  所以我们要做的第一件事是，在这个月球关卡中，我们将右键单击，然后选择新建子场景，再选择空场景。

[09:38] And then here we'll be prompted to create an empty scene inside this uh moon level directory.
  然后在这里，我们会收到提示，要求我们在月球关卡目录中创建一个空场景。

[09:43] So let's just go ahead and call this moon entity scene.
  所以让我们继续，将它命名为月球实体场景。

[09:49] And then here it'll open up a dialogue box prompting us to create a new subscene inside this moonle directory.
  然后这里会弹出一个对话框，提示我们在月球目录中创建一个新的子场景。

[09:55] So let's go ahead and just create a moon entity scene here.
  所以让我们继续在这里创建一个月球实体场景。

[09:59] And you see that this moon entity scene is now part of our regular scene hierarchy.
  您可以看到，这个月球实体场景现在是我们常规场景层次结构的一部分。

[10:02] Now the idea
  现在，这个想法

[10:07] with subscenes is we can kind of add game objects to this subscene right here.
  通过子场景，我们可以将游戏对象添加到这个子场景中。

[10:13] And then during a process known as baking, these game objects will actually get converted over to ECS entities.
  然后，在称为烘焙的过程中，这些游戏对象将被转换为ECS实体。

[10:22] The whole idea is that we take game objects and we have these things called authoring components on them.
  整个想法是，我们获取游戏对象，并在它们上面拥有这些称为创作组件的东西。

[10:26] And these authoring components define how we take data that we are modifying inside the Unity editor and convert that over to ECS compatible data.
  这些创作组件定义了我们如何获取在Unity编辑器中修改的数据，并将其转换为与ECS兼容的数据。

[10:38] Some things that we've defined, some things that Unity has defined.
  有些是我们定义的，有些是Unity定义的。

[10:40] There's also a ton of other advantages of subscenes.
  子场景还有许多其他优点。

[10:42] You know, we can have a bunch of these and be able to load these in and out.
  你知道，我们可以拥有很多这样的东西，并能够加载和卸载它们。

[10:46] Um, especially for larger worlds, this is a really nice feature to have.
  嗯，特别是对于更大的世界，这是一个非常好的功能。

[10:50] And it's really cool that we can just kind of like bake all this data inside of a subscene.
  能够将所有这些数据烘焙到子场景中，这真的很酷。

[10:53] So, you know, sometimes there may be some heavy calculations that you want to do ahead of time and you can kind of like bake all that data into a subscene so that some expensive operations don't necessarily need to get ran on a
  所以，你知道，有时可能有一些繁重的计算你想提前完成，你可以将所有这些数据烘焙到子场景中，这样一些昂贵的操作就不必在运行时执行。

[11:08] endpoint machine.
  端点机。

[11:10] So, let's start things off just by creating a very simple environment entity that we can have in our scene.
  那么，我们就从创建一个非常简单的环境实体开始，这个实体可以存在于我们的场景中。

[11:14] So, we'll just go to the moon entity scene here.
  所以，我们就去月球实体场景这里。

[11:16] Rightclick.
  右键单击。

[11:18] We'll go to game object, 3D object, and then we'll select quad.
  我们将转到游戏对象，3D对象，然后选择四边形。

[11:21] Now, the idea here is that we're actually going to be using quad meshes to render um basically all the entities in our scene.
  现在，这里的想法是我们实际上将使用四边形网格来渲染我们场景中的所有实体。

[11:29] And then we just have materials that are going to render on those quad meshes to, you know, display these sprites within our game.
  然后我们只需要有材质，这些材质将在那些四边形网格上渲染，以便，你知道，在我们的游戏中显示这些精灵。

[11:38] Um, and also with on those materials, we can have some custom shaders, and that's how we're going to be accomplishing animations, but you know, we'll get to that in just a moment here.
  嗯，而且在那些材质上，我们可以有一些自定义着色器，这就是我们将如何实现动画的方法，但是你知道，我们稍后就会讲到这一点。

[11:47] So, anyways, we have this quad.
  所以，总之，我们有了这个四边形。

[11:49] We can just rename this to boulder.
  我们可以将它重命名为巨石。

[11:51] And then, um, you see that we do have a quad mesh associated with it.
  然后，嗯，你看到我们确实有一个与之关联的四边形网格。

[11:54] And then for the materials, instead of this just regular material here, we can add the boulder material, which is one that is available in the starter assets.
  然后对于材质，而不是这里这个普通的材质，我们可以添加巨石材质，这是在入门资源中可用的一种。

[12:06] And then I'm actually going to remove this mesh collider and instead just use a
  然后我实际上将删除这个网格碰撞器，而是只使用一个

[12:11] Regular box collider.
  常规的盒子碰撞体。

[12:14] Um, don't want to use box collider 2D.
  嗯，不想使用 2D 盒子碰撞体。

[12:17] Unfortunately, we can't use the 2D colliders to author things in ECS just quite yet.
  不幸的是，我们还不能使用 2D 碰撞体来编写 ECS 中的内容。

[12:23] And so we can kind of zoom in here and just kind of just slightly the collider edges so it nicely wraps around the visuals of that there.
  所以我们可以放大这里，稍微调整碰撞体边缘，使其很好地环绕那里的视觉效果。

[12:32] And just real quick going to cut in here and say that I forgot to mention that when we're setting the size of these colliders, make sure we set the ZV value to a value of 0.15 because by default it's like a very small value.
  还有，快速说一下，我忘了提，当我们设置这些碰撞体的大小时，确保我们将 ZV 值设置为 0.15，因为默认情况下它是一个非常小的值。

[12:46] And we want it to actually have some amount of thickness so these collisions can actually take place.
  我们希望它有一定的厚度，这样这些碰撞才能真正发生。

[12:51] And I'm actually also just going to go ahead and increase the scale of this to be 1.75 just so they're a little bit larger in the game view there.
  我实际上也要将它的比例增加到 1.75，这样它们在游戏视图中会稍微大一点。

[12:58] So there we go.
  好了，就这样。

[13:01] We have a nice little boulder here.
  我们这里有一个漂亮的小石头。

[13:02] And again, these are all just kind of regular Unity components at this point.
  再说一遍，在这一点上，这些都只是常规的 Unity 组件。

[13:06] So, I will go ahead and enter play mode here.
  所以，我将在这里进入播放模式。

[13:08] And you'll see that we still do have our boulder inside of this entity
  你会看到我们的石头仍然在这个实体里面

[13:12] scene here.
  场景在这里。

[13:14] And so far, nothing has changed in the inspector.
  到目前为止，检查器中没有任何变化。

[13:16] It looks all the exact same.
  它看起来完全一样。

[13:19] Now, I should mention that the inspector kind of has two modes associated with it.
  现在，我应该提到检查器有点与它相关的两种模式。

[13:21] So, this is sort of the authoring mode where we can use regular Unity components to author data on the ECS side.
  所以，这是创作模式，我们可以在其中使用常规的 Unity 组件来创作 ECS 端的数据。

[13:26] Now, by going to this little circle icon right here, we can change this over to be runtime.
  现在，通过点击这里的小圆圈图标，我们可以将其更改为运行时。

[13:31] And runtime shows the actual ECS runtime data that is available to us in the editor.
  运行时显示了我们在编辑器中可用的实际 ECS 运行时数据。

[13:37] So, you'll see that things um are starting to look quite a bit different now.
  所以，你会看到事情现在看起来有点不同了。

[13:42] So, we have um kind of this local transform which defines the position, rotation, and scale within its local space.
  所以，我们有这种局部变换，它定义了其局部空间内的位置、旋转和缩放。

[13:48] So if it's you know part of any transform hierarchy this is kind of the you know relative values to its parent.
  所以，如果它是任何变换层次结构的一部分，那么这些就是相对于其父项的相对值。

[13:55] We also have the local to world and the local to world basically defines the world space position rotation scale of this object.
  我们还有局部到世界的变换，局部到世界的变换基本上定义了该对象的空间位置、旋转和缩放。

[14:04] Um now this is listed as a matrix 4x4 and this is kind of used
  嗯，现在这被列为一个 4x4 矩阵，它被用来

[14:14] for more efficient rendering and things like that on the GPU and you know we're typically not going to be modifying things on this local to world component directly.
  为了在GPU上更有效地渲染等等，你知道我们通常不会直接修改这个局部到世界组件上的东西。

[14:24] Unity actually um has its own little process of it converting this local transform into a local to world.
  Unity实际上有一个它自己的小过程，将这个局部变换转换为局部到世界。

[14:32] Um kind of scrolling down we have some other things related to render bounds the entities graphics physics world index render bounds render filter settings render mesh array scene section scene tag world render bounds.
  嗯，向下滚动，我们还有一些与渲染边界、实体图形物理世界索引渲染边界渲染过滤器设置渲染网格数组场景部分场景标签世界渲染边界相关的其他东西。

[14:43] You know most of these things are just kind of all Unity builtin things that allow you know the game to just kind of function as expected.
  你知道，这些东西大部分都是Unity内置的，可以让游戏按预期运行。

[14:53] However, as we go on and we start, you know, creating characters and enemies and gems and things of that nature, you know, we're going to start to create custom components, and we'll be able to inspect and edit these custom components from this inspector right here.
  然而，随着我们继续，你知道，创建角色、敌人、宝石以及类似的东西，你知道，我们将开始创建自定义组件，并且我们可以从这里的检查器中检查和编辑这些自定义组件。

[15:07] So, let's go ahead and just kind of populate this scene real quick with some of these boulders.
  所以，让我们快速地用这些巨石填充一下场景。

[15:09] So, just go ahead and just duplicate a bunch of these um just kind of around the screen
  所以，就去复制一堆这些东西，在屏幕周围。

[15:16] here.
  这里。

[15:18] Just so we have like, you know, a couple little environment assets, some kind of points of reference um for us for the player just kind of moving around within the game world here.
  这样我们就有了，你知道，几个小的环境资产，一些参照点，嗯，供我们，供玩家在这个游戏世界里四处移动。

[15:28] And I think that should just be plenty for right now.
  我想现在这样就足够了。

[15:29] But of course, you can add more later if you'd like.
  但当然，如果你愿意，以后可以添加更多。

[15:33] So, now let's actually get a player inside of our game world so we can have them, you know, walk around in this environment here.
  所以，现在让我们把玩家放到我们的游戏世界里，这样我们就可以让他们，你知道，在这个环境中走动。

[15:38] So again, we'll go up to this moon entity scene, rightclick, we'll create a new 3D object, and we'll just go ahead and start with a quad here, and then we can name this player.
  所以，再说一遍，我们将转到这个月亮实体场景，右键单击，我们将创建一个新的3D对象，然后我们开始创建一个四边形，然后我们可以给它命名为玩家。

[15:47] Um, so by the way, you will see that the inspector is showing invalid entity right now, and that's because we're still on the runtime mode.
  嗯，顺便说一下，你会看到检查器现在显示无效实体，这是因为我们还在运行时模式。

[15:56] So we do need to swap back to the authoring mode when we're changing around components on here.
  所以，当我们在这里更改组件时，我们需要切换回创作模式。

[16:00] Um, so again, I'm just going to remove this mesh collider.
  嗯，再说一遍，我将删除这个网格碰撞器。

[16:03] And then we can go ahead and uh pop open the mesh renderer.
  然后我们可以打开网格渲染器。

[16:07] Swap out the mesh for the astronaut material.
  将网格替换为宇航员材质。

[16:13] And I'm going to go ahead and increase the scale on this guy to 1.5.
  我将把这个家伙的比例增加到1.5。

[16:15] And I'm just
  我只是

[16:18] going to zoom in here.
  我将在这里放大。

[16:20] And we should see our nice little astronaut in the game world here.
  我们应该能看到游戏中我们可爱的小宇航员。

[16:23] When we get to the portion of character animations, I'm going to go over kind of like how the shader and animation works.
  当涉及到角色动画的部分时，我将介绍着色器和动画的工作原理。

[16:29] But right now, it's just kind of on one frame of animation, and it's just going to be stuck like this for now.
  但现在，它只是处于动画的一个帧上，并且暂时会卡在这里。

[16:36] Um, anyways, let me go ahead and add a box collider on this guy as well.
  嗯，总之，我将在此基础上添加一个盒子碰撞器。

[16:39] And we'll go ahead and set this to match the graphics.
  我们将对其进行设置以匹配图形。

[16:42] Um, so this kind of all lines up nicely here.
  嗯，所以这一切都很好地对齐了。

[16:46] Perfect.
  完美。

[16:49] And also don't forget to set the size of the box collider on the Z-axis to 0.15.
  另外，别忘了将盒子碰撞器在Z轴上的尺寸设置为0.15。

[16:55] And then another component that we are going to need on our player is a rigid body component.
  然后我们需要在我们的玩家身上添加的另一个组件是刚体组件。

[16:59] And this is because we want ourh character to interact with the physics simulation.
  这是因为我们希望我们的角色与物理模拟进行交互。

[17:04] So we're going to add a rigid body on here.
  所以我们将在上面添加一个刚体。

[17:06] Um for mass, let's go ahead and set this to a value of 100.
  嗯，对于质量，我们将把它设置为100。

[17:11] And for linear damping and uh angular damping, we can just type inf for infinity.
  对于线性和角度阻尼，我们可以输入inf（无穷大）。

[17:15] Um the idea is that we're
  嗯，我们的想法是

[17:21] basically going to be in complete control over, you know, how the character moves.
  基本上将完全控制，你知道，角色如何移动。

[17:26] And we don't want to apply any uh damping linearly or angularly.
  我们不想在线性或角向应用任何阻尼。

[17:29] And then we can also just go ahead and uncheck the use gravity box here.
  然后我们也可以在这里取消选中“使用重力”框。

[17:34] And that is all that we need for now.
  这就是我们目前所需要的一切。

[17:37] Uh we will be changing one other thing on here in just a second though.
  嗯，我们将在稍后在这里更改另一件事。

[17:41] So, let's actually get our player to move around in the game world.
  所以，让我们实际让我们的玩家在游戏世界中移动。

[17:45] Okay.
  好的。

[17:45] So, in the scripts directory, we're just going to go ahead and rightclick, go to create scripting, and we'll just create a regular MonoBehavior script, and we're going to be using this to author some things about the player.
  所以，在脚本目录中，我们将右键单击，选择创建脚本，然后创建一个常规的MonoBehavior脚本，我们将使用它来编写有关玩家的一些内容。

[17:55] Um, so actually what we're going to do is we're going to create kind of a general character authoring.
  嗯，所以实际上我们要做的就是创建一个通用的角色编写。

[18:03] And the idea with this is this authoring component is actually going to be used by both our player characters as well as the enemy characters within the game.
  这样做的想法是，这个编写组件实际上将由我们的玩家角色以及游戏中的敌人角色使用。

[18:11] Okay, so we can just open up this character authoring right here and just clear out any of the template stuff if it opens up with that.
  好的，所以我们可以直接在这里打开这个角色编写，如果它带有模板内容，就清除掉任何模板内容。

[18:19] And then so what we're going to do is we're first going to deal with
  然后，我们将首先处理

[18:23] actually moving this character around our game world.
  实际上是在我们的游戏世界中移动这个角色。

[18:25] So in order to move them around, we're going to need some data components for this.
  所以为了移动它们，我们需要一些数据组件。

[18:30] So let's go ahead and define our first data component.
  所以让我们开始定义我们的第一个数据组件。

[18:32] And so we can do that by saying public struct.
  所以我们可以通过说 public struct 来做到这一点。

[18:37] And this is going to be for the character move direction.
  这将是角色移动方向的。

[18:42] And this will implement the I component data interface.
  这将实现 I component data 接口。

[18:45] Uh you'll see that by default the I component data is showing up as red.
  呃，你会看到默认情况下 I component data 显示为红色。

[18:49] That's because we do need to include the using unity namespace.
  那是因为我们需要包含 using unity 命名空间。

[18:53] So this is kind of how we define these data components.
  所以这基本上是我们定义这些数据组件的方式。

[18:58] So typically we want to do a public struct of data components.
  所以通常我们想做一个 public struct 的数据组件。

[19:03] So this is going to be an unmanaged data component which means it's fully compatible with Unity's job system as well as the burst compiler.
  所以这将是一个非托管数据组件，这意味着它与 Unity 的作业系统以及 burst 编译器完全兼容。

[19:13] And this I component data interface, you know, basically signifies that this is a component that can be added to an ECS entity.
  而这个 I component data 接口，你知道，基本上表示这是一个可以添加到 ECS 实体的组件。

[19:20] All right, so next up we're going to go ahead and
  好的，接下来我们将继续

[19:24] actually add some data to this data component.
  实际上向此数据组件添加一些数据。

[19:26] The idea here is that we want to include a data type that has that stores the XY direction of where a character should be moving at any given time.
  这里的想法是，我们想包含一个数据类型，该类型存储了角色在任何给定时间应移动的 XY 方向。

[19:37] And then in a move system, we're going to use that as input data to know where to actually move our character.
  然后在移动系统中，我们将使用该数据作为输入数据，以了解实际移动角色的位置。

[19:44] So the type that we're going to be using is a public float 2.
  所以我们将要使用的类型是公共浮点数 2。

[19:46] And we can just call this value.
  我们可以称之为值。

[19:50] Um, so a couple things here.
  嗯，这里有几件事。

[19:53] So float 2 is actually part of the unity.mmathmatics namespace.
  所以浮点数 2 实际上是 unity.mmathmatics 命名空间的一部分。

[19:58] And so float 2 is very similar to a vector 2.
  所以浮点数 2 与向量 2 非常相似。

[20:00] It's just a single data type that consists of two floats, one for the x value, one for the y-value.
  它只是一个单一的数据类型，包含两个浮点数，一个用于 x 值，一个用于 y 值。

[20:07] And then I've also decided to call it value.
  然后我也决定称之为值。

[20:09] Um you see that that's a typical pattern that I use when I create a data component that just has a single variable in it.
  嗯，您可以看到，当我创建一个只包含单个变量的数据组件时，这就是我使用的典型模式。

[20:18] And um it just makes a little bit more sense when you're typing things out where to actually get this
  嗯，当您在输入内容时，了解实际获取此内容的位置会更有意义。

[20:24] character move direction, you know, we

[20:26] can just say character move direction

[20:28] value rather than being redundant and

[20:31] saying, you know, character move

[20:32] direction. Character move direction. And

[20:34] then we're going to go ahead and create

[20:36] one other data component. This will be a

[20:38] public strruct called character

[20:43] move speed. This will also be an I

[20:46] component data. And on here we'll just

[20:49] have a public float called value. And so

[20:52] in this case, this is going to, you

[20:54] know, define how quickly our character

[20:56] is actually going to move throughout the

[20:57] game world. Now, it is entirely possible

[21:00] to have both these values be on one

[21:04] single shared component. The reason in

[21:06] this case that I've chosen to separate

[21:09] them out is this character move

[21:11] direction. And this is going to be

[21:12] constantly updated with the direction

[21:15] that the character should be moving. For

[21:16] the player, you know, pressing the W and

[21:19] D keys. And then for the enemies, it's,

[21:21] you know, figuring out where the player

[21:23] is and moving in that direction. Whereas

[21:25] the character move speed, this value is

[21:28] basically just going to be fixed

[21:29] throughout the duration of the game. Um,

[21:32] so it is nice to kind of have this

[21:33] separation between data components where

[21:36] data is frequently changing versus data

[21:38] that's going to be staying the same

[21:39] throughout the duration of the

[21:40] application. And this kind of leads into

[21:42] some of the things related to data

[21:44] dependencies and you know reading and

[21:46] writing to certain components and some

[21:49] of the optimizations that can be made

[21:50] around that. Not going to be going too

[21:51] deeply into that, but again just the the

[21:54] point is to have this split between data

[21:56] that's frequently changing versus data

[21:58] that's not changing that often. So at

[22:00] this point, we've just defined the data

[22:02] components that we want. We're not

[22:03] actually adding these to any type of

[22:05] entity. So let's go ahead and do that

[22:07] now. So this will actually be inside the

[22:10] character authoring. We'll go ahead and

[22:12] create a nested private class here. And

[22:16] we can just call this baker. And it will

[22:18] implement baker. And inside these type

[22:22] brackets here, we'll pass in the type of

[22:25] the authoring component. So in this case

[22:27] it's character authoring and then inside

[22:31] this class here you'll see that we do

[22:33] have this red squiggly line and that's

[22:35] because we need to do a public override

[22:40] void called bake and then the type in

[22:44] here is again the authoring type. So

[22:46] this is

[22:47] character

[22:49] authoring and then we can just call this

[22:51] say authoring. So let me just take a

[22:54] step back and kind of explain what's

[22:55] going on here. So this character

[22:57] authoring, this is going to be a regular

[23:00] MonoBehavior component added to our

[23:03] authoring game object inside of Unity.

[23:05] And inside this baker, this kind of

[23:08] defines how we actually take this

[23:10] authoring data that we've defined and

[23:12] convert it over into ECS. So the first

[23:15] thing that we're going to do inside this

[23:17] bake process here is get a reference to

[23:20] the entity that's being baked. So in

[23:22] this case we can just say var entity

[23:25] equals get entity and then in here we

[23:28] need to pass in what are known as the

[23:31] transform usage flags. So the transform

[23:34] usage flags essentially define the ECS

[23:37] transform components that are added to

[23:40] an entity. The one that is most often

[23:42] going to be used are

[23:44] transform usage

[23:47] flags

[23:49] damic. And this is going to add all the

[23:51] data components necessary for this being

[23:54] an entity that can move around within

[23:56] the world as well as be part of

[23:58] transform hierarchy. So it can be a

[24:01] child of another object or other objects

[24:03] can be children of it. The other one

[24:05] that we're going to use um a little bit

[24:07] later on is the transform usage

[24:09] flags.none. And that just basically

[24:11] means you know hey don't apply any

[24:13] transform data to this component because

[24:16] it is a data only entity and we don't

[24:18] need to worry about its world position

[24:20] or anything of that nature. So anyways

[24:22] once we have the entity let's go ahead

[24:23] and start adding some components to it.

[24:25] So we can just go ahead and use the add

[24:28] component uh method here. And the type

[24:32] that we want to add is the character

[24:35] move direction. And then inside the

[24:39] parenthesis here, we can say want to add

[24:42] it to this entity. And so in this case,

[24:44] what's going to happen is this character

[24:46] move direction is now going to be added

[24:48] to our entity. Um, but it's just going

[24:50] to have some default values for float 2.

[24:53] And that's totally fine because we don't

[24:55] need to initialize it with any special

[24:57] values because during runtime, we're

[24:58] going to be constantly writing to this

[25:00] character move direction. Now, for

[25:02] character move speed, we need to do

[25:03] things a little bit different because we

[25:05] do want to define the character move

[25:07] speed through authoring. So, we can just

[25:10] go ahead and say add component again.

[25:13] Um, this time we're just going to say

[25:16] we're just going to go right to the

[25:17] parenthesis and say entity because this

[25:19] is the entity that we want to actually

[25:22] add the component to. And then just go

[25:25] ahead and say new character move speed.

[25:30] And then here we can actually define the

[25:32] values on the character move speed. So

[25:34] we can set value equal to say a value of

[25:38] five. So when we add this authoring

[25:40] component to an entity, it's going to

[25:42] add the character move direction with

[25:44] zero zeros for its value here. And then

[25:48] character move speed is going to set to

[25:50] a value of five. So let's come back over

[25:52] to Unity. Go ahead and select the

[25:54] player. We'll go to add component. And

[25:57] then we can now add that character

[25:59] authoring component. And you'll see this

[26:01] character authoring component just

[26:03] doesn't have any fields on it or

[26:04] anything. And when we actually go to

[26:07] enter play mode, we still do have our

[26:09] player character selected. And we can go

[26:11] over to the runtime mode here. And we

[26:14] can start scrolling down. And we s do

[26:16] see that we have this character move

[26:18] direction again set to 0 0. And we do

[26:21] have the character move speed set to a

[26:22] value of five. Um, now if you want, you

[26:25] can actually go ahead and change this

[26:27] move speed and character move direction

[26:29] in here, but right now these data

[26:31] components aren't actually doing

[26:32] anything. They're just data. Now, one

[26:34] thing that would be nice to have is the

[26:36] ability to adjust the character's move

[26:39] speed through Unity. So that way we can

[26:42] easily assign unique values to all of

[26:45] our different entities. So let's come

[26:46] back over to our code editor and on the

[26:50] character authoring component we'll go

[26:51] ahead and create a public float for move

[26:55] speed and then so that is going to

[26:57] expose a move speed value in the editor

[27:01] and then we can kind of set that through

[27:02] there. Now to actually use that let's go

[27:05] down to the character move speed and

[27:08] then we'll set value now equal to

[27:11] authoring domove speed. And so this

[27:14] authoring right here, it comes from the

[27:16] bake method right here. Um, because

[27:18] again, we're defining this with this

[27:21] authoring type. So it knows to look at

[27:23] the, you know, associated character

[27:25] authoring script and get the actual move

[27:27] speed that we set in here. So back in

[27:29] Unity, you'll see that our character

[27:30] authoring now has a field for move

[27:32] speed. And let's set this to a value of

[27:35] 3.5.

[27:36] And then now when we enter play mode and

[27:40] swap over to the runtime mode, you'll

[27:43] see that the character's move speed is

[27:45] now a value of 3.5. Now I should also

[27:48] mention a cool little thing about the

[27:50] authoring and baking workflow is if we

[27:52] do go back to the authoring side of

[27:54] things and we were to, you know, change

[27:56] this move speed, now it's up to

[27:58] 15.39. Um, we can actually go back to

[28:00] the runtime and you'll see that the

[28:02] character move speed does get updated.

[28:04] So, uh, that's kind of the nice thing

[28:06] about working with Unity in the editor

[28:08] here is that anytime we change any

[28:12] values inside the inspector, it will

[28:14] trigger a re-baking process and

[28:16] essentially update all those runtime

[28:19] values. Now, interesting enough, you

[28:21] will notice that when I exit play mode,

[28:23] you'll see that my move speed stays the

[28:25] same. It doesn't like revert to any

[28:26] previous values like you would typically

[28:29] expect in Unity. Um, the reason for this

[28:31] is because when we're re-triggering

[28:34] baking, that data is actually baked into

[28:36] the subscene. And that subscene is just

[28:38] an asset and the data on that asset is

[28:41] exactly the same when you're in play

[28:43] mode and you're outside of play mode.

[28:44] So, if we do want to reset this, you

[28:46] know, we can just set it to say 3.5 how

[28:48] we had it. And then let's go on to, you

[28:50] know, actually move our character around

[28:52] the game world. So, I'm actually going

[28:54] to show you the incorrect way to move

[28:56] characters around the game world. um and

[28:59] then show you the correct way after

[29:00] that. So what I'll do is under this

[29:03] character authoring monoBehavior here,

[29:05] I'm gonna go ahead and create a new line

[29:08] and just kind of scroll this out of view

[29:10] here. And so we'll be creating a first

[29:13] our first system which is a public

[29:16] partial strct called character move

[29:21] system and this is going to implement

[29:24] the I system interface just like that.

[29:28] So, a couple things to point out here.

[29:29] Uh, the reason we use the partial

[29:31] keyword is because Unity uses code

[29:33] generation to take all of the code that

[29:37] we're actually typing in into the system

[29:39] here and it does some code generation on

[29:41] it to actually write more efficient code

[29:43] from us. So, that is kind of one of the

[29:46] interesting things about how Unity ECS

[29:48] works is when we create systems in this

[29:50] way, our code is not actually the code

[29:52] that's being executed. um Unity is

[29:55] actually generating its own system code

[29:58] that uses a lot of the kind of lower

[30:00] level APIs that are way more verbose

[30:03] than the you know current system APIs

[30:05] and that is actually doing the things to

[30:07] modify the entity data and then this I

[30:10] system interface this is a system

[30:13] meaning that it's going to run on any

[30:15] type of data that we kind of define as

[30:17] our input data in our entity query and

[30:20] it's going to run against that data and

[30:23] perform any data transformations that we

[30:26] are also going to be defining here. So

[30:27] let's go ahead and create an update

[30:29] method here. So we can say public void

[30:32] on update and we have to say ref system

[30:37] state. So on update this is similar to

[30:40] just an update in a regular mono

[30:42] behavior. This is going to get called

[30:44] every single frame. And then the system

[30:46] state has some things kind of associated

[30:48] with the system that are required. Um,

[30:50] in some cases we are going to be using

[30:52] some things off this system state. Other

[30:54] cases we don't need to. But having this

[30:56] system state as an argument is still

[30:58] required for some of the code generation

[31:00] stuff that Unity does. So now let's

[31:02] create the transformation to actually

[31:03] move these entities through the world.

[31:05] Uh, so the first thing that we're going

[31:06] to need is delta time. And of course

[31:09] we're just going to be multiplying by

[31:12] delta time to sort of normalize for

[31:14] frame rate. Um, there is actually a

[31:17] little bit of a different way that we

[31:18] get delta time in Unity ECS rather than

[31:22] regular Unity game objects. And we can

[31:25] use this system API time. Delta time.

[31:29] Uh, now the system API is a really nice

[31:32] type to have. It's going to kind of

[31:34] provide a lot of nice little helper

[31:36] functions to us and we're going to be

[31:37] using a bunch of these throughout

[31:39] development here. So this one again

[31:41] basically just gives us the delta time

[31:43] of the current frame time and just

[31:45] multiplying by delta time will allow us

[31:48] to normalize our movement for frame

[31:50] right here. So we're going to go ahead

[31:51] and create what is known as an idiomatic

[31:54] for each. So we can just use the regular

[31:56] for each keyword. And then here we need

[31:59] to define a variable. Um I'm just going

[32:01] to go ahead and put a temporary name

[32:03] there for now. And you'll see why in

[32:05] just a second. And then we actually

[32:07] define the collection that we're running

[32:08] against. So we'll say in and then we'll

[32:10] again use our nice system API to create

[32:15] an entity query right here. And then

[32:17] inside these type brackets is where we

[32:20] actually define the components that we

[32:23] want to query for. So we're going to

[32:24] we're going to say you know hey find me

[32:27] all entities that have these specific

[32:30] components. So the first one that we're

[32:33] going to look for is this local

[32:35] transform. And um by the way you will

[32:38] see that it shows up in red and that's

[32:40] because we do need to include the using

[32:45] unity.transforms namespace. And so the

[32:48] local transform again this is going to

[32:50] have our current position and we're

[32:52] going to be able to update it to our new

[32:54] position. Um after that we do also want

[32:57] our character move direction and that's

[33:00] so we know which direction to move into

[33:03] on the given frame. And then finally,

[33:05] we'll also want our character move speed

[33:08] so we know how fast to move in that

[33:11] particular direction. And so there is

[33:12] one additional thing that we need to do

[33:14] and that's specific to this local

[33:16] transform type. Now, as I mentioned in

[33:18] this system, we're going to be updating

[33:20] the position of our local transform.

[33:23] Now, when we typically define our entity

[33:25] queries like this, it does not allow us

[33:27] to actually write to the underlying data

[33:30] on here. Again, this kind of goes back

[33:32] into some of the data dependencies that

[33:35] Unity keeps track of where it needs to

[33:38] know what types are being written to and

[33:40] what types are being read from in any

[33:42] given system so that it can you know

[33:44] order things around properly to you know

[33:47] make sure we don't have any race

[33:48] conditions where you know like one

[33:49] system is trying to write to data that

[33:51] another system is reading from at the

[33:53] same time and it can just end up in some

[33:55] weird situations where unexpected things

[33:58] start to happen which of course leads to

[34:00] very poor player experience. So what we

[34:03] need to do is actually add a specific

[34:06] type which is called the ref rw and then

[34:09] we kind of put this local transform in

[34:12] those angle type brackets there and then

[34:14] the idea here is this basically gives us

[34:16] read and write access to that component.

[34:18] Now we'll come back to my little uh

[34:20] temporary variable name here. And so

[34:23] actually what's happening here when

[34:24] we're defining this query with these

[34:27] three separate components. Uh this type

[34:30] you'll see is actually a tupal type. So

[34:33] it is it is a type with three different

[34:36] kind of like subtypes under it. Um so

[34:39] the first one is the ref rw for the

[34:41] local transform. The next is for the

[34:44] character move direction and the final

[34:45] one is for the character move speed. Now

[34:48] if we want you know we could say asdf

[34:51] item one to get access to that local

[34:53] transform item two to get access to the

[34:56] character move direction and item three

[34:58] for the character move speed. But that's

[35:00] like kind of annoying. You don't want to

[35:01] have to do that. So luckily what we can

[35:03] do is instead just delete that and add

[35:06] in some parentheses here. And then here

[35:09] we can just define nice names for each

[35:11] of these variables. So for the local

[35:13] transform we can just call this

[35:15] transform. For the character move

[35:17] direction, let's just call this

[35:19] direction. And then for the character

[35:21] move speed, we can just call this speed.

[35:23] So then now we have access to all the

[35:25] data that we need. Now what we need to

[35:27] do is actually apply this data

[35:30] transformation. So um again, if you'll

[35:32] remember this character move direction,

[35:34] this is in a 2D XY type. And this local

[35:38] transform, the position that we're

[35:40] updating is going to be in a

[35:41] three-dimensional type. So, what I'm

[35:43] going to do here is the first thing I'm

[35:44] going to do is say var move step 2D. And

[35:49] then we're just going to figure out, you

[35:51] know, how much we need to move in a 2D

[35:54] direction on each frame. So, we'll just

[35:57] go ahead and say direction value. And um

[36:02] I should point out that you know what's

[36:04] happening here is when we're accessing

[36:06] this direction, well, we're accessing

[36:09] this character move direction type. And

[36:11] to actually get the underlying value,

[36:14] remember that we named the value value.

[36:16] So we need to actually say dot value to

[36:18] get that underlying value. Um so here

[36:21] then we can also go ahead and multiply

[36:23] by our speed value and then multiply

[36:27] that all by delta time. So again that's

[36:30] just you know us figuring out how much

[36:32] we need to move on a 2D plane each

[36:35] frame. Now we need to actually go ahead

[36:38] and modify our transform. So

[36:41] unfortunately we can't just say

[36:43] transform.position just like that

[36:45] because um what's actually happening

[36:47] when we're accessing this transform is

[36:50] we're accessing a type of refrw and then

[36:53] kind of the subtype of that is local

[36:56] transform. So we actually have to say

[37:00] transform.rsw.position position and then

[37:02] the idea here is you know with this

[37:04] value RW we're accessing this local

[37:07] transform type giving us right access to

[37:10] it so we can update this position so

[37:12] here we can update the position by

[37:14] saying plus equals new float 3 and again

[37:19] this is a a float three type similar to

[37:22] a vector 3 within regular Unity it just

[37:25] has three floats for x y and z and then

[37:27] the nice thing about converting uh a

[37:30] vector 2 from 2D to 3D is if we're

[37:33] moving in the XY plane, we can just say

[37:36] pass in the move step 2D. And then we

[37:41] also need to pass in a Z value and we

[37:43] can just pass in zero because we don't

[37:45] need it to move on the on the Z plane at

[37:47] all. So anyways, that's actually all we

[37:49] need to do to move our character

[37:50] throughout the game world. So let's go

[37:52] back to Unity and test this out. So

[37:55] we'll go ahead and enter play mode and

[37:57] you'll see nothing happens. is our

[37:59] character is not moving at all. That's

[38:01] because we're not actually setting

[38:03] anything for the character move

[38:05] direction. Now, we're going to be

[38:06] getting into doing player input in just

[38:08] a second, but we can still test things

[38:10] out here by going over to our runtime

[38:12] mode and looking at our character move

[38:14] direction. And let's just set this to a

[38:16] value of one in the X direction. And

[38:18] you'll see that our character starts

[38:20] moving. Hooray. So, actually, you will

[38:22] remember that I did mention that I was

[38:24] going to show you the incorrect way to

[38:27] apply movement. And you know, while yes,

[38:29] this is simple and it looks like our

[38:31] character is moving nicely along the

[38:32] screen, um there is actually one major

[38:35] problem with it. So, what I'm going to

[38:37] go ahead and do is grab one of these

[38:39] boulders right here and place it just

[38:42] directly in front of the player. Now,

[38:44] actually, before we test this out, there

[38:46] is one thing that I completely forgot to

[38:48] do, and that is to set the layers, the

[38:51] physics layers for all of our entities.

[38:53] So, we can just go ahead and highlight

[38:55] all of our boulders and we'll change

[38:57] this to the environment layer and go to

[39:00] our player and change that to the player

[39:03] layer. Um, and then now based off of the

[39:05] collision matrix, players and

[39:08] environment elements should be able to

[39:10] collide with each other. So, let's go

[39:12] ahead and enter play mode and give it a

[39:14] test here. So, we'll go back to the

[39:17] runtime here and set the character's uh

[39:20] move direction to a value of one. And

[39:22] you'll see that it just moves right

[39:24] through that boulder like it was not

[39:27] even there. Um and even if we say, you

[39:30] know, change this move speed down to a

[39:32] quite low value and um you know, tried

[39:35] to move right through right into that

[39:37] boulder again, you'll see that we just

[39:39] move immediately right through it, which

[39:42] is not exactly what we want to happen.

[39:44] Now, the reason that this is happening

[39:46] is because when we're actually applying

[39:48] movement in this way where we're adding

[39:50] to the transform position, well, what

[39:52] we're actually doing is a teleportation

[39:55] move. And that basically means that

[39:57] we're saying, "Hey, let's take this

[39:58] entity and move it from this position

[40:01] immediately to this position." And even

[40:03] though we're kind of, you know,

[40:04] constantly doing this on a on a very

[40:06] small amount. So we're, you know, moving

[40:08] it only very slight movements, we're

[40:10] still kind of doing a a teleportation

[40:12] move, saying, you know, hey, move this

[40:14] to this frame, move this to this frame,

[40:17] right? And so because we're constantly

[40:19] overwriting this local transform value,

[40:22] it actually does not give Unity a chance

[40:24] to resolve these collisions to, you

[40:27] know, separate the two entities apart.

[40:28] So in order to actually get this to

[40:30] function properly, we need to use

[40:32] Unity's physics system to apply the

[40:35] movement rather than doing the movement

[40:37] directly on the local transform like

[40:39] this. So instead, what we're going to do

[40:41] is we're actually going to modify the

[40:43] character's physics velocity component

[40:46] and then we're going to basically have

[40:47] Unity handle all the collisions and

[40:49] everything like that. Let me just

[40:51] temporarily set the linear physics

[40:52] damping to zero. And that'll allow me to

[40:55] set a linear velocity of one. And you'll

[40:58] see that the player should go right up

[41:00] to the boulder, collide with the

[41:01] boulder, and then stop moving. So, let's

[41:03] come back to our character move system,

[41:05] and we'll just do a little bit of

[41:06] refactoring on this. So, rather than the

[41:09] local transform again, we're going to be

[41:11] writing to the physics velocity

[41:14] component. So, this is a component that

[41:16] Unity adds automatically when we have

[41:19] that rigid body component. Um, and so

[41:22] this is kind of what Unity uses to, you

[41:24] know, determine how things should be

[41:26] moving throughout the world. And to

[41:27] actually get access to this, we do need

[41:29] to um instead of using Unity's

[41:32] transforms, we can use

[41:34] Unity.physics. And then we can update

[41:36] this from transform to be velocity. And

[41:40] so we'll actually be handling velocity a

[41:42] little bit different. So I'll just

[41:43] delete out this whole line. So we can

[41:44] just say velocity. Again, we do need to

[41:47] do the value RW because we are writing

[41:50] to

[41:51] it. U because we're just setting the

[41:54] linear velocity. And rather than doing

[41:55] than doing a plus equals, we'll just do

[41:57] an equals because we're just saying, you

[42:00] know, hey, this is the velocity that it

[42:01] should be at this frame. And we don't

[42:03] want to like increase the velocity, you

[42:05] know, we're not like applying an

[42:06] acceleration force or anything like

[42:08] that. We're just saying, hey, this this

[42:10] is the velocity that should be at. Um,

[42:12] so again, we're just going to do

[42:13] something similar where we can say new

[42:15] float three, pass in our move step 2D uh

[42:21] for the first argument and then zero for

[42:23] the second argument because again we

[42:25] don't want it to move on the on the Z

[42:27] plane at all. Now one thing uh with

[42:29] setting velocity in this manner again

[42:32] we're just setting the direct velocity.

[42:34] It this has nothing to do with frame

[42:36] rate. We're just saying hey this is the

[42:37] speed that it should be moving at. So

[42:39] actually we do not need delta time. So

[42:42] we can just actually remove that al

[42:44] together. And then our move step 2D is

[42:46] just the direction value multiplied by

[42:49] the speedvalue. And then again just kind

[42:51] of applying that uh linear velocity

[42:53] directly. So back over to Unity, go

[42:56] ahead and enter play mode here. And we

[42:59] can switch over to our runtime inspector

[43:02] for the player. And uh we'll set our

[43:05] character's move direction to be a one

[43:07] in the X direction. in and you'll see

[43:09] that it moves nice and quickly and then

[43:11] runs into the boulder and then just

[43:13] stops right at the boulder, which is

[43:14] exactly what we want to see. So, at this

[43:16] point, we want to give the player the

[43:17] ability to actually move the character

[43:19] around through the gamer world. Um, you

[43:21] know, it's kind of annoying to go into

[43:22] the inspector and just, you know, edit

[43:25] directional movement values. You know,

[43:27] we actually want to use the W andd arrow

[43:29] keys, joystick, whatever to move the

[43:31] player throughout the game world. So,

[43:33] let's go ahead and do just that. So, I'm

[43:35] going to go to the settings directory.

[43:37] I'll do a right-click create and go down

[43:40] to input actions and we can call this

[43:43] say survivors input. And so we'll go

[43:46] ahead and double click to open up the

[43:48] survivors input. And you should see a

[43:50] nice little window like this. Let's go

[43:52] ahead and create a new action map. This

[43:54] will be for the player. And then I'll go

[43:56] ahead and just delete that action

[43:58] because we don't need it. And we'll just

[44:00] create a new action here. And we can

[44:02] call this move. And then the action type

[44:05] is actually going to be a value. And

[44:07] then for control type we want to say

[44:09] vector 2. So this basically means that

[44:11] you know we're applying input in a

[44:14] vector 2. So we're looking for X and Y

[44:17] input where the X input is say moving

[44:19] left and right and the Y input is moving

[44:21] up and down. So this is just us kind of

[44:24] listening for this input and then we can

[44:25] you know convert it over into our ECS

[44:27] data as needed. So we'll go ahead and

[44:29] open up this dropdown and create a new

[44:32] binding here. So we'll add an updown

[44:35] left right composite and then you'll see

[44:37] that we have this 2D vector right here.

[44:39] I will point out that again the

[44:41] composite type is a 2D vector and the

[44:44] mode is set to digital normalized. Uh

[44:46] the nice thing about having digital

[44:48] normalized is that this means that if

[44:50] you're saying holding up in a di

[44:52] diagonal direction by pressing the W and

[44:55] D keys then it's going to normalize that

[44:58] vector to just be a factor of one. So we

[45:00] don't end up in the problem where you

[45:02] know if we're going up diagonally then

[45:04] the x is a full one value and the y is a

[45:08] full one value. You know those are

[45:10] normalized to be a value of one. So it

[45:12] doesn't matter you know what direction

[45:14] that we're moving in the player will

[45:16] always be moving at the same speed. So

[45:18] here we can kind of define all of our

[45:19] bindings. So for upbinding we can press

[45:22] the W key down go to the listen and do

[45:26] the S key left listen A right listen D.

[45:31] Um go ahead and duplicate this. And

[45:35] actually I can rename this uh 2D vector

[45:38] here to WD and this one to arrow keys.

[45:43] And then here I can just define all the

[45:44] different arrow keys for up, down, left,

[45:46] and right. Um, so then once we're done,

[45:48] just go ahead and click save asset. And

[45:50] that's actually the only input that

[45:52] we're going to need because this is a

[45:53] survivors type game. And in survivors

[45:55] games, the player can only move and

[45:57] that's all they can do. So, uh, when we

[45:59] do have this survivors input selected,

[46:01] we do want to make sure that we check

[46:02] mark the box for generate car class and

[46:06] click apply. And then Unity should

[46:09] recompile here. uh we do need to do that

[46:11] because when we're gathering input from

[46:14] the ECS side, we do need to read the

[46:17] generated C class file in order to

[46:20] actually read that movement data and

[46:22] apply it to our character movement. So

[46:24] again, here's where we kind of reach a

[46:25] little bit of a split because we started

[46:26] out with this character authoring, which

[46:28] again is going to be able to apply

[46:30] movement to both player characters as

[46:32] well as enemy characters. So now we want

[46:34] to kind of start to split things up

[46:36] where we can kind of have a player take

[46:39] in input from the actual keyboard

[46:42] presses from the person playing the game

[46:44] and then the enemy it should kind of

[46:46] take in its own input from its its AI

[46:49] logic. So we kind of need to split

[46:51] things out to have a specific character

[46:53] now. So I'll come over to scripts and

[46:55] I'll just create a new C class here and

[46:58] I can call this player authoring. So I'm

[47:01] first going to just include a couple

[47:02] namespaces that I know that I'm going to

[47:04] need. So I'm going to need Unity engine

[47:07] and Unity. entities. And the first thing

[47:10] that I'm going to do is create what is

[47:12] known as a tag data component. And so we

[47:15] just define one of these by doing a

[47:17] public strruct. Uh we can call this

[47:20] player tag. And this will also implement

[47:23] I component data. Um, now with this, the

[47:26] special thing is that it doesn't have

[47:28] any actual data components in it. It's

[47:30] an empty data component, which is known

[47:32] as a tag data component. And this is

[47:34] going to allow us to basically find the

[47:38] player entity just by querying for any

[47:41] entity with the player tag. Um, so let's

[47:43] go ahead and actually add this to a

[47:45] player entity. So I will need to turn

[47:47] this player authoring into a

[47:49] MonoBehavior. And we'll go ahead and

[47:52] create that private baker class here.

[47:55] And then same thing as before, we do

[47:57] want to get a reference to the actual

[47:59] entity that we're baking. Um, so again,

[48:01] we can just say get entity passing in

[48:04] those transform usage flags. Again,

[48:08] we're going to be using the dynamic

[48:09] transform usage flags because we want

[48:11] the player to have all those uh usage

[48:13] flags as well. And then here we can just

[48:15] go ahead and do an add component and

[48:18] we'll add that player tag uh to the

[48:21] entity there. And so that's all we need.

[48:23] We don't need any like special

[48:25] additional data components to create our

[48:27] player input system. So let's go ahead

[48:29] and do that now. So this system is going

[48:30] to be defined a little bit differently

[48:32] than our previous one. So this is going

[48:34] to be a public partial class this time

[48:38] rather than a strruct. And we can call

[48:40] this the player input system. And this

[48:44] is going to be a system base. So the

[48:47] system base type systems are a little

[48:50] bit different. Um it's typically

[48:52] preferred to use the I systems which are

[48:54] the you know previous system type that

[48:56] we did. Now the system base type systems

[48:59] they work similar with some syntactical

[49:01] differences. Um, one of the the reason

[49:05] that I'm using a systembased system here

[49:07] is because with systembased systems, we

[49:10] actually can have managed types be

[49:12] member variables of the system base

[49:16] itself. Now, it's typically not best

[49:18] practice to have member variables for

[49:20] systems at all. You know, typically we

[49:22] want that nice separation between data

[49:24] and systems. However, for the case of

[49:27] input systems, I usually make an

[49:28] exception because it's really easy to

[49:31] just create an input system object and

[49:34] have it be a member variable of the

[49:36] system and that way we can read input

[49:38] from it really easily. So, um I'm sure

[49:41] this red line is probably bothering you.

[49:43] So, I can just do one of these to

[49:45] implement the required member, which in

[49:48] this case is just the protected override

[49:50] void on update. So, you see that again

[49:53] slight differences with the system base.

[49:55] It's a public void whereas this is a

[49:58] protected override void and this one

[50:00] does not take in that system state as a

[50:03] parameter. It doesn't take in any

[50:04] parameters actually. So what I'm going

[50:06] to do is I'm going to go ahead and

[50:07] create that member variable here. So

[50:09] we'll say private survivors input and we

[50:14] can just call this underscoreinput and

[50:16] then so we need to actually go ahead and

[50:18] create this input action. So we'll go

[50:20] ahead and create another

[50:23] protected override void. This one is

[50:26] called on create. And then in here we

[50:29] can actually create the input actions

[50:31] just by doing underscoreinput equals new

[50:35] survivors input. And then we could just

[50:38] do an

[50:40] underscoreinput. Just like that. So now

[50:42] in the on update which is going to be

[50:43] ran every frame. Let's go ahead and

[50:46] actually read input each frame. So we

[50:49] can say var current input is equal to

[50:54] our input here dot player again we're

[50:57] accessing that player map and want to

[51:00] access the move action on the player map

[51:03] and to actually read the value we can

[51:05] just do a read value and we do actually

[51:08] have to read this as a vector 2

[51:12] type and then what I like to do is just

[51:16] uh cast this to a float 2. Um, again, we

[51:20] are going to need the

[51:22] unity.mmathathematics namespace in order

[51:24] to use float 2. And then that way it's

[51:25] already available for us as a float 2.

[51:28] And then we can just set the players

[51:30] move direction. So the way that we're

[51:32] going to do this is do another of these

[51:34] for each queries. And in this case, I

[51:37] know that we're just writing to that um

[51:40] character move direction. So we can just

[51:42] say direction in system API.query query.

[51:47] Again, we're writing to this. So, we

[51:49] need to do the ref rw and it is the

[51:52] character move direction type like that.

[51:57] And then, so if we're to do just this,

[52:00] then that's going to be a little bit of

[52:01] a problem because when we get to uh

[52:04] having enemies later on, then this is

[52:06] also going to be writing to the uh

[52:10] enemies. So instead what we can do is

[52:13] also specify that we need the with all

[52:16] player tag just like that. And this

[52:18] means that the query is only going to

[52:20] match for any entity that has the

[52:23] character move direction and that player

[52:25] tag. So we know that we're only writing

[52:27] to the character's move direction. So

[52:29] here all we can do is just do a

[52:34] direction.rw do value is equal to the

[52:38] current input. So super easy. We're just

[52:40] applying the inputs directly to that

[52:42] character's move direction. And now

[52:44] we'll come back to Unity. We'll select

[52:46] our player and make sure we go to the

[52:48] authoring mode. And we will need to add

[52:50] in that uh player authoring script on

[52:54] there. And again, actually, we can look

[52:55] in the entity baking preview right here.

[52:58] And you can see that we are adding, you

[53:00] see that we are adding the character

[53:01] move speed and direction right there.

[53:04] And scrolling down, this also shows that

[53:06] we're adding the player tag as well. So

[53:09] now we are good to enter play mode. And

[53:12] you'll see that our character is

[53:13] stationary right now. And then if we

[53:16] start moving around, hey, look at that.

[53:17] Our player starts moving around the

[53:19] screen. Now, luckily, when we so run

[53:21] into these boulders, you know, it'll

[53:23] it'll stop us just fine. However, if we

[53:26] hit the kind of corner of the boulder,

[53:28] we can kind of start rotating around

[53:31] like this. And we don't necessarily want

[53:32] that to happen. You'll see actually I

[53:34] started kind of rotating towards the

[53:35] camera. So I'm I'm on a just like a

[53:37] totally different rotation plane and uh

[53:40] that that is not looking good. So we

[53:42] need to uh fix this ASAP. So what's kind

[53:46] of happening here is again when we

[53:48] switched over to this movement where

[53:50] we're just setting the physics velocity,

[53:52] you know, we're telling Unity to say,

[53:53] "Hey, you take care of handling all the

[53:55] collisions and everything like that."

[53:57] And it says, "Cool, I got you." And then

[53:59] so not only does it make it so we

[54:01] actually stop when we hit a wall, but it

[54:04] also is like, oh, hey, if you hit a

[54:06] wall, you probably want to apply some

[54:08] rotation there. And in, you know, most,

[54:10] you know, 3D games where something hits

[54:12] something, it's nice to have it kind of,

[54:14] you know, rotate off in a in a nice way.

[54:16] However, in a 2D game like this, we want

[54:19] to be like, no, we actually want to be

[54:21] in complete control of rotations. In

[54:23] fact, we just don't want it to, you

[54:25] know, rotate at all when we hit things.

[54:27] And you know if we do then yeah maybe

[54:29] let's do that rotation manually but

[54:30] we're like hey Unity don't worry about

[54:32] you know doing the rotation stuff for

[54:34] collisions like that. So the way this is

[54:36] actually accomplished is we can look

[54:38] into the runtime inspector here and

[54:41] scroll down and you'll see that we do

[54:43] have this physics mass and there is this

[54:46] option for inverse inertia. Um and

[54:50] you'll see that it has some values set

[54:52] on here. Right now we basically just

[54:54] want to say hey we we want uh

[54:56] essentially infinite inertia. So the um

[55:00] if we just set 00 0 so the inverse of

[55:04] inertia is 00 0 then that means that now

[55:07] when we move around the game world um

[55:10] and collide with the uh different

[55:12] boulders on the corners now we're

[55:15] actually not going to be rotating again.

[55:16] We're we're just kind of like disabling

[55:19] that feature essentially. Now, luckily

[55:22] this like inertia value isn't something

[55:24] that like is constantly changing

[55:26] throughout the duration of the

[55:27] application. All we need to do is just

[55:29] set this value once inside of an

[55:31] initialization step and then that value

[55:34] is just going to be set for the rest of

[55:36] the application. So, this is kind of a

[55:38] nice little concept that we'll be going

[55:40] into where we can use what are known as

[55:42] enable components and use those to do

[55:45] some initialization logic. Um, and

[55:48] again, we want this behavior to apply to

[55:50] both player characters as well as enemy

[55:52] characters. So, we're going to be doing

[55:54] this in the character authoring level.

[55:56] So, in the character authoring, um, up

[55:59] at the top, we'll go ahead and create a

[56:00] new data component here. So, this is a

[56:03] public strruct called

[56:06] initialize character flag. And this is

[56:10] an I component data. But we do also want

[56:13] this to be an I

[56:15] enable component. And um this can just

[56:18] be an empty component here. So that's

[56:21] kind of like one of the um patterns that

[56:23] I use is you know we we have tag

[56:25] components which are empty data

[56:27] components and we also have flag

[56:29] components which are empty data

[56:31] components but also have this I enable

[56:34] component on here. to explain how these

[56:36] enable components work. Um, it might be

[56:39] easier if I just go ahead and add this

[56:41] to the character right here. So, we can

[56:43] say add component

[56:46] initialize character flag and of course

[56:50] add that to the entity. So now when we

[56:52] enter play mode and go over to the

[56:54] runtime inspector for the player, you'll

[56:56] see that up top it lists all the tags.

[56:58] So these are the empty data components.

[57:01] So you'll see that we have the player

[57:02] tag here. And then we also have the

[57:04] initialize character flag. And you'll

[57:06] see that there is actually a checkbox

[57:08] next to it. And so this can be either

[57:11] enabled with the checkbox selected or

[57:13] disabled with the checkbox unselected.

[57:16] And so I should point out that this type

[57:18] of behavior is not unique to, you know,

[57:20] just empty tag components. If we scroll

[57:23] down, you can see uh Unity has this

[57:25] material mesh info as an enable

[57:28] component. And so we can, you know,

[57:30] enable disable it by selecting this

[57:31] checkbox. And you see that actually when

[57:33] we disable it, the character is no

[57:35] longer being rendered. And then when we

[57:37] enable it, it is being rendered. So

[57:39] that's kind of the cool thing about

[57:40] enabable components is we can use it as

[57:43] a way to query for a specific component

[57:46] that has a component but also has that

[57:49] component enabled. Um, and I don't know

[57:51] how much I want to get too deep into the

[57:53] technical details about some of the

[57:55] optimizations that Unity can make as far

[57:57] as enable components, but um, this is

[58:00] typically a better approach to enable

[58:03] and disable components rather than

[58:05] adding or removing components.

[58:07] Basically, because adding and removing

[58:09] components, um, induces what is known as

[58:11] a structural change. And that's a

[58:13] decently expensive operation because we

[58:16] actually need to take entity data from

[58:18] one place of memory and move it into

[58:20] another place in memory and it incurs

[58:23] like a sync point which makes all our

[58:25] job stop. So it's something that we're

[58:27] not going to get around eliminating

[58:29] structural changes but we just want to

[58:31] limit the number that we're doing. So

[58:33] anyways, back to this initialize

[58:35] character flag. The idea here is we're

[58:37] going to look for any character that has

[58:39] this initialized character flag. We're

[58:41] going to have them go through that

[58:43] initialization step where we're setting

[58:44] the inverse inertia. And then once we're

[58:47] done with that, we can disable this

[58:49] initialize character flag so that that

[58:51] initialization step will only happen one

[58:54] time and it's not like continuously

[58:56] running. So above this character move

[58:58] system, let's go ahead and create a

[59:00] public partial strct. This will be the

[59:03] character initialization system and this

[59:05] will also be an I system. And then

[59:09] inside here we'll do our typical public

[59:12] void on

[59:14] update ref system state and state. And

[59:19] so one cool thing that I'm going to do

[59:21] with this particular system is we're

[59:22] going to tell this to actually run

[59:24] towards the beginning of the frame. So

[59:26] we can specify that we want this to

[59:28] update in group type of

[59:33] initialization system group. And so

[59:36] Unity has some of these like highlevel

[59:39] system groups defined. So there's kind

[59:41] of the initialization system group which

[59:43] happens at the beginning of the frame.

[59:45] There's the simulation system group

[59:47] where most of our code logic is going to

[59:49] be stored. Um and if we don't specify a

[59:52] system group, it goes into this

[59:53] simulation system group. And then

[59:55] finally, there's the presentation system

[59:57] group, which happens towards the end of

[59:59] that player loop. So we're going to say,

[01:00:01] you know, any of our character

[01:00:02] initialization, we want this to run

[01:00:04] early on in the frame. Um because when

[01:00:06] we actually go to spawn new enemies,

[01:00:09] we're going to spawn those at the

[01:00:11] beginning of the frame and we want this

[01:00:13] initialization process to run before any

[01:00:16] of their additional actual game logic

[01:00:19] runs. So let's create our for each here.

[01:00:22] And we can say var asdf for a little

[01:00:24] placeholder there. And we want to go in

[01:00:27] the system

[01:00:29] api.query. And again we're going to be

[01:00:31] writing to a specific component. So

[01:00:34] we're going to be writing to the physics

[01:00:36] mass in this case. And another thing

[01:00:39] that we can do is to query for these

[01:00:42] enabable components. We can use another

[01:00:45] special keyword here which is the

[01:00:47] enabled

[01:00:49] refr. And in here we can pass in the

[01:00:52] type which again is the initialize

[01:00:57] character flag. And then we can just

[01:00:59] kind of close out that query right here.

[01:01:02] Um so the idea here is that when we

[01:01:04] define this query like this with this

[01:01:07] enabled ref rw this means that to in

[01:01:11] order for an entity to match this query

[01:01:14] this initialize character flag needs to

[01:01:16] be enabled and by using this enabled

[01:01:19] refr Rw this gives us the ability to

[01:01:22] disable this component from inside this

[01:01:25] system. There are other different ways

[01:01:26] that we can go about querying for

[01:01:28] enabable components and enabling them

[01:01:30] and disabling them. But in this way,

[01:01:33] it's nice to kind of streamline both

[01:01:35] those functionalities into just this one

[01:01:38] little block of text right here. So

[01:01:40] let's go ahead and change our

[01:01:42] placeholder. Again, this is going to be

[01:01:44] a tupole. So for physics mass, we can

[01:01:47] just call this mass. And then for the

[01:01:50] initialize character flag, let's say

[01:01:52] should initialize. So inside the for

[01:01:55] each body we can say mass do value rw

[01:01:59] because we're writing to this value. Um

[01:02:02] and we want to specifically write to the

[01:02:05] inverse inertia. And we'll set this

[01:02:08] equal to float 3.0. Um and again we're

[01:02:12] basically just zeroing out that inverse

[01:02:15] inertia which basically just disables

[01:02:17] the ability for things to rotate after

[01:02:20] collisions. Um and then finally we just

[01:02:23] need to disable that initialize

[01:02:25] character flag. So again we can access

[01:02:27] that with the should initialize. And

[01:02:30] then similar to the ref rw we can also

[01:02:32] do a value rw and then just immediately

[01:02:37] set this equal to false. So this kind of

[01:02:40] uh allows us to set the enabled state of

[01:02:44] a component. Um, so it is a little bit

[01:02:46] weird because it's a similar syntax to

[01:02:49] the the ref rw where we access valu, but

[01:02:53] we don't we're not accessing a specific

[01:02:54] variable just by doing this should

[01:02:57] initialize.valrw equals false. This is

[01:03:00] going to disable the initialized

[01:03:02] character flag. So now we can come back

[01:03:04] to Unity and enter play mode. And you'll

[01:03:06] see that already the initialized

[01:03:08] character flag has been disabled because

[01:03:10] that just happens immediately on the

[01:03:12] first frame of execution. And scrolling

[01:03:15] down, if we find the physics mass,

[01:03:18] you'll see the inverse inertia is set to

[01:03:20] 000. So, let's go ahead and test out our

[01:03:23] game here. And we can move over onto the

[01:03:26] corner. And you'll see that we are no

[01:03:28] longer moving around in any weird

[01:03:30] rotations. The collisions are all

[01:03:32] working exactly as

[01:03:34] expected. So, anyways, that is kind of,

[01:03:38] you know, how we can actually get this

[01:03:39] player moving around. But there is

[01:03:40] actually one final thing that I do want

[01:03:43] to showcase with the character move

[01:03:45] system before we move on to some other

[01:03:47] fun things. What we can actually do is

[01:03:49] because we're only using just regular

[01:03:52] simple unmanaged data types on here is

[01:03:54] we can actually enable the burst

[01:03:57] compiler just by saying burst compile

[01:04:00] just like that right above the on

[01:04:02] update. And you do see that we also need

[01:04:04] to say using unity. burst. And that

[01:04:08] gives us access to utilize the burst

[01:04:10] compiler here. Um, I mean, if we want,

[01:04:13] we can technically add this to our

[01:04:15] character initialization system because

[01:04:17] again, we're all using unmanaged type on

[01:04:19] these these things here. But, uh, yeah,

[01:04:21] this is going to give us much better

[01:04:23] performance for these particular

[01:04:24] systems. Uh, the character move system

[01:04:26] is going to be a much more important one

[01:04:28] to have it on because this one's going

[01:04:30] to be constantly running throughout the

[01:04:32] duration of the application on, you

[01:04:34] know, however many enemies we end up

[01:04:36] having. And so we can go back to uh play

[01:04:38] mode here and we can still just move our

[01:04:40] character around as expected. However,

[01:04:42] our character movement is now running

[01:04:44] with the burst compiler. Okay, so now

[01:04:46] that we got the character moving around,

[01:04:48] the next thing that we're going to do is

[01:04:49] actually get the camera to follow the

[01:04:51] player. Now we're going to do this using

[01:04:53] Cinem Machine. Now Cinem Machine, of

[01:04:55] course, is a regular Unity game objects

[01:04:58] concept. So, we're going to need to have

[01:05:00] a little bit of a bridge between the ECS

[01:05:03] side of things and the game objects side

[01:05:05] of things. Um, because we actually

[01:05:07] unfortunately can't have direct

[01:05:08] references from the things in the ECS

[01:05:12] subscene to things in kind of our main

[01:05:14] scene. So, we end up having to do a

[01:05:16] little bit of a workaround to kind of

[01:05:17] get this behavior uh to be working as

[01:05:20] expected. So, what I'm going to go ahead

[01:05:22] and do is just kind of in the main scene

[01:05:24] right here, I'm just going to go ahead

[01:05:25] and create an empty game object. And I

[01:05:28] can call this the camera target. And

[01:05:30] then from here, we can just go ahead and

[01:05:33] reset its position and all that. And

[01:05:36] then I'm going to go to the Cinem

[01:05:37] Machine camera and set the tracking

[01:05:39] target equal to that camera target right

[01:05:42] there. Uh you can see that these are all

[01:05:44] the, you know, other values that I have

[01:05:46] on here. And I do have the Cinem Machine

[01:05:48] follow set with a follow offset of -10

[01:05:52] in the Z direction. And so that

[01:05:54] basically moves the camera, you know, 10

[01:05:56] units away from uh our camera target

[01:05:59] here. So our camera target here is um

[01:06:02] just kind of at the center of the scene.

[01:06:03] And you'll see that as I move the camera

[01:06:05] target around now, the camera moves

[01:06:08] around the world. Now what we want to

[01:06:10] actually happen is that as our player

[01:06:12] moves around, we want our player to tell

[01:06:16] the camera target where to move and then

[01:06:18] the Cinem Machine camera is going to

[01:06:20] follow the camera target. So, it's kind

[01:06:22] of a little bit of a of a a chain of

[01:06:24] hierarchy here. So, what we're going to

[01:06:26] end up doing is again have a little bit

[01:06:28] of an initialization step for our player

[01:06:30] to essentially find this camera target.

[01:06:33] Once it finds this camera target, then

[01:06:35] it's going to store a reference to it.

[01:06:38] And then every frame, we're just going

[01:06:40] to update the position of that camera

[01:06:43] target based off of the player's world

[01:06:45] position. So, let's get into it. So,

[01:06:48] first thing I'm going to do in scripts

[01:06:49] is I'm just going to go ahead and create

[01:06:50] a new Unity script here. And I can call

[01:06:53] this the camera target singleton. And

[01:06:57] then I'm going to go ahead and create a

[01:06:59] singleton out of this by using the

[01:07:01] public static keywords for the

[01:07:05] camera target singleton type. And just

[01:07:10] call this instance here. And so I'm just

[01:07:13] going to go ahead and create a pretty

[01:07:14] simple singleton monoBehavior script

[01:07:17] where if there's already a singleton

[01:07:19] instance, we'll just go ahead and

[01:07:20] destroy the new one and print out a

[01:07:21] little warning to the console. Um,

[01:07:23] otherwise we'll just go ahead and set

[01:07:24] the instance equal to this. And then

[01:07:27] come back to Unity and we'll just go

[01:07:28] ahead and add this camera target

[01:07:30] singleton to the camera target. So let's

[01:07:33] go ahead and create a new data component

[01:07:35] and this will be a public strruct and we

[01:07:38] can call this

[01:07:39] camera target and this will be an I

[01:07:43] component data of course and in here we

[01:07:47] will need a public unity object ref of

[01:07:53] type transform and we can call this

[01:07:56] camera transform for now. So anyways,

[01:07:58] Unity object ref as you can imagine by

[01:08:01] the name this allows us to reference

[01:08:04] unity objects from the ECS side. Uh now

[01:08:07] you'll notice that this is a public

[01:08:09] strruct of I component data. There also

[01:08:11] is a concept of managed data components

[01:08:14] that uses a public class of I component

[01:08:17] data and we can have just references to

[01:08:20] any managed types within there. However,

[01:08:22] the advantage of using this Unity object

[01:08:24] ref is we can have just a regular

[01:08:27] unmanaged data component. And this Unity

[01:08:29] object ref essentially just acts as a

[01:08:31] pointer to this camera transform in this

[01:08:34] case. And so with these Unity object

[01:08:36] refs, we can reference any Unity object

[01:08:39] type, whether that be a game object, any

[01:08:42] component type, any scriptable object.

[01:08:44] And so it kind of provides a nice link

[01:08:46] between the ECS and game objects worlds.

[01:08:49] Now the cool thing about these is that

[01:08:51] these data components are not subject to

[01:08:54] you know certain restrictions about the

[01:08:57] job system and burst compiler. However,

[01:08:59] when we actually access the underlying

[01:09:01] data in that case then we're not able to

[01:09:05] utilize the job system and burst

[01:09:07] compiler for those things. But if we do

[01:09:09] have say other values inside this camera

[01:09:11] target and we want to reference those in

[01:09:14] a fully bursted context, we can totally

[01:09:17] do that. So, Unity object ref is a

[01:09:19] really handy way to well reference Unity

[01:09:22] objects. So, once we have our camera

[01:09:24] target defined, I'm going to show you

[01:09:25] actually an alternate method of doing an

[01:09:28] initialization step. So, we can do a

[01:09:31] public strruct for

[01:09:33] initialize

[01:09:35] camera target

[01:09:37] tag. And this also is going to be an I

[01:09:41] component data. And it's just an empty I

[01:09:44] component data because it's a tag

[01:09:46] component. And we're just going to go

[01:09:47] ahead and just add that initialize

[01:09:50] camera target tag to our player entity.

[01:09:53] And then I'm also going to go ahead and

[01:09:56] add component for the camera target

[01:09:59] itself. And uh we'll just assign this to

[01:10:02] the entity. So right now this this

[01:10:04] pointer in here is effectively going to

[01:10:05] be empty. And then during our

[01:10:07] initialization step then we're actually

[01:10:10] going to add the camera target in here.

[01:10:12] And then uh above this player input

[01:10:16] system, I'm going to go ahead and create

[01:10:17] a new public partial strct for the

[01:10:22] camera

[01:10:25] initialization system. And this is an I

[01:10:27] system. And we do also want this to

[01:10:29] update in group type of

[01:10:33] initialization system group. And a cool

[01:10:37] little optimization that we can do by

[01:10:38] having this initialize camera target tag

[01:10:41] is actually in the public void on

[01:10:45] create. This also by the way does need a

[01:10:48] ref

[01:10:50] system state called state. Um we can

[01:10:53] here actually use this system state by

[01:10:56] saying

[01:10:57] state require for update and pass in the

[01:11:03] initialize

[01:11:05] camera target tag. So this basically

[01:11:08] means that when we go ahead and create

[01:11:10] our public void on update, this means

[01:11:14] that this update method will only run

[01:11:17] when a entity with this initialize

[01:11:20] camera target tag exists in the game

[01:11:23] world. And so what's going to happen is

[01:11:25] we're going to run our initialization

[01:11:28] step and then we're going to remove this

[01:11:30] component from the entity and then this

[01:11:33] system is no longer going to update. So

[01:11:35] inside the on update, let's just go

[01:11:37] ahead and check to make sure that camera

[01:11:40] target singleton uh instance actually

[01:11:43] exists. So if it's equal to null, then

[01:11:46] just go ahead and say return. So this

[01:11:49] basically means that you know if the

[01:11:52] camera target singleton does not yet

[01:11:54] exist in the game world, we'll just go

[01:11:56] ahead and try again on the next frame.

[01:11:59] Uh the reason behind this is because

[01:12:01] sometimes the ECS world and game objects

[01:12:04] worlds loads at different times. So this

[01:12:07] provides a little bit of protection to

[01:12:09] say you know hey let's actually make

[01:12:11] sure that this camera target singleton

[01:12:13] exists before we you know try and get a

[01:12:15] reference for it and save a null

[01:12:16] reference which we don't want. So here

[01:12:18] let's actually get a reference to that

[01:12:20] uh camera target transform and we can

[01:12:23] get that from the camera target

[01:12:27] singleton

[01:12:29] uh

[01:12:31] instance transform. And so once we have

[01:12:34] that transform reference we can actually

[01:12:37] assign that. So then let's go ahead and

[01:12:39] create this uh for each here little

[01:12:42] placeholder of course in system

[01:12:45] API.query query and we do need to write

[01:12:49] to that uh camera target type and we

[01:12:53] also want to say with all of the

[01:12:57] initialize

[01:12:58] camera target tag and just force to make

[01:13:02] sure that it also has a player tag as

[01:13:04] well. So now let's go ahead and set the

[01:13:07] camera target right here. So we can say

[01:13:10] camera target do

[01:13:12] valuew dot camera

[01:13:16] transform equals to the camera target

[01:13:21] transform just like that. So that's

[01:13:24] actually us assigning the reference. Um

[01:13:27] now it is a little bit interesting. I'll

[01:13:28] kind of point out that we're actually

[01:13:30] assigning this to the Unity object ref

[01:13:32] type, but Unity kind of implicitly

[01:13:35] assigns this to that underlying

[01:13:37] transform pointer that we're saving. Um,

[01:13:39] so that's all we need to do for our

[01:13:40] initialization step. Now, we need to

[01:13:43] remove this initialize camera target tag

[01:13:46] from the player. Um, so we actually have

[01:13:48] to do this inside this for each loop

[01:13:50] here because we're kind of iterating

[01:13:52] over the player. However, um we can't

[01:13:55] just, you know, use like say the entity

[01:13:57] manager or something like that to remove

[01:14:00] the component inside this for each

[01:14:02] because that ends up changing the

[01:14:05] entities that we're querying against and

[01:14:07] it, you know, ends up throwing some

[01:14:09] errors in the editor if we try to remove

[01:14:11] this component while we're iterating

[01:14:13] over this collection. So, what we

[01:14:15] actually have to do is use an entity

[01:14:18] command buffer. I usually just call

[01:14:20] these ECB. And so we can just create a

[01:14:22] new entity command buffer just by saying

[01:14:25] new entity command buffer. And then we

[01:14:27] do have to pass in an allocator. And the

[01:14:30] allocator I recommend using for

[01:14:32] temporary things like this is the

[01:14:34] state.world update allocator. So if

[01:14:38] you're at all familiar with the

[01:14:39] different levels of allocation that

[01:14:41] Unity has. Um so there's temp temp job

[01:14:44] and persistent. And so temp is just, you

[01:14:46] know, when we need some temporary bit of

[01:14:48] memory, um, you know, just to be kind of

[01:14:51] used within a method like we're doing

[01:14:52] here. Um, so state.world update

[01:14:55] allocator actually works similar to the

[01:14:57] temp memory, but it is a little bit more

[01:15:00] efficient because it's uh technically

[01:15:01] like a rewindable allocator.

[01:15:04] Essentially, my understanding of it is

[01:15:05] that this is a block of memory that is

[01:15:08] available to us to use that is going to

[01:15:11] get cleared out every frame. So rather

[01:15:13] than using temp memory where we're

[01:15:15] creating new blocks of memory, um this

[01:15:18] state world update allocator allows us

[01:15:20] to reuse that same block of memory over

[01:15:22] and over and over again for simple

[01:15:24] little things like you know creating an

[01:15:26] entity command buffer where we need to

[01:15:28] you know just remove one component. Um

[01:15:30] so we actually do need a reference to

[01:15:33] the entity itself in order to remove a

[01:15:35] component from it. So at the end of this

[01:15:38] whole query, I'm going to do another

[01:15:42] uh entity access and this provides us

[01:15:45] access to the entity. Um so now this

[01:15:48] this changes our our camera target

[01:15:50] because it now becomes a tupil. So we

[01:15:53] have to put the parentheses around it

[01:15:55] here and also add in the entity as uh

[01:15:59] one of the arguments there. So here we

[01:16:01] can say ecb.ove

[01:16:04] remove component. And here we want to

[01:16:07] remove the

[01:16:08] initialize camera target tag and we're

[01:16:13] just removing that from the

[01:16:16] entity. Now this doesn't actually remove

[01:16:19] it. It just kind of records the

[01:16:21] operation of removing it. So the final

[01:16:24] step we need to do is do an

[01:16:27] ECB.playback. And in here we can pass in

[01:16:30] the entity manager. So we say state

[01:16:33] entity

[01:16:35] manager and then so we need to make sure

[01:16:37] to do that outside of this for each

[01:16:39] loop. So then it actually plays back all

[01:16:41] the operations that we've recorded

[01:16:43] inside this entity command buffer. So

[01:16:45] then we'll come back to Unity and enter

[01:16:47] play mode. And you can see that we can

[01:16:49] still move our character around. Now the

[01:16:50] camera is still not following the player

[01:16:52] because we're not saying any um we don't

[01:16:55] actually have any logic in place to

[01:16:57] actually move that camera target yet.

[01:16:58] However, we can uh check on a couple

[01:17:00] things here. So, if we actually go over

[01:17:01] to the player and in the runtime mode,

[01:17:05] we can actually see the initialize

[01:17:06] camera target tag uh is not showing up

[01:17:09] inside the tags here for the player,

[01:17:11] which is good. And if we go down to the

[01:17:13] camera target, we can see there's a

[01:17:15] camera transform and the ID is set to an

[01:17:18] instance ID of

[01:17:21] 73250. Um, now unfortunately the way

[01:17:25] that this kind of like pointer reference

[01:17:26] works is via these instance ids of these

[01:17:30] Unity objects. So it doesn't like give

[01:17:32] us a thing that we can click on and say,

[01:17:34] "Hey, you know, go to that object." Um,

[01:17:37] so we'll just remember this instance ID

[01:17:39] of -73 250. And if we go to the camera

[01:17:43] target, we can do the little three dot

[01:17:45] menu kind of in the upper right of the

[01:17:47] inspector and go to the debug inspector.

[01:17:50] and we'll see that the instance ID of

[01:17:52] this camera target does line up to that

[01:17:54] minus 73250 uh specifically for the

[01:17:58] transform of that camera target. So

[01:17:59] again, we're we're storing a reference

[01:18:01] to that transform. So now that we have a

[01:18:04] reference to the transform, all we need

[01:18:05] to do is now just update its position

[01:18:08] based off of the player's world

[01:18:09] position. So let's go after this camera

[01:18:11] initialization system and we'll create a

[01:18:14] new system. So this is going to be the

[01:18:17] public partial strct called

[01:18:21] move camera

[01:18:24] system and this of course is going to be

[01:18:26] an I system and so we'll go ahead and do

[01:18:29] a public void on update ref system

[01:18:36] state and we'll create our for each here

[01:18:41] um with a temporary name in system

[01:18:46] API.query. Um, so now remember we're

[01:18:49] going to be looking for the players

[01:18:51] world position and then applying that to

[01:18:54] that camera target transform. So to get

[01:18:57] the player's world position, um, we

[01:19:00] could technically use the local

[01:19:02] transform because we don't have the the

[01:19:04] player doesn't have any parent. So its

[01:19:07] local transform is going to be the same

[01:19:09] as its world transform. But I'm going to

[01:19:11] show you another way and this is

[01:19:12] actually by using the local to world

[01:19:15] component which I believe I mentioned um

[01:19:17] kind of early on and this does require

[01:19:20] us to use the uh Unity transform

[01:19:23] unity.transforms namespace as well. Um

[01:19:25] so the local to world no matter where

[01:19:27] you are in the transform hierarchy this

[01:19:29] is always going to be your world

[01:19:32] position rotation scale and all that.

[01:19:34] And then um importantly to make sure

[01:19:36] this is actually up to date, let's go

[01:19:38] ahead and uh tell this system to update

[01:19:43] after the type of

[01:19:47] transform system group. So the transform

[01:19:50] system group is something built into

[01:19:53] Unity that basically takes the local

[01:19:56] transform and any transforms of the

[01:19:59] parents and all that and it converts it

[01:20:02] into this local to world. So, and this

[01:20:04] local to world is actually what's sent

[01:20:06] off to the GPU to tell it to render that

[01:20:10] entity at a specific world position. Um,

[01:20:14] so again, this this transform system, it

[01:20:16] it takes all that local transform data

[01:20:18] and then updates it into the local to

[01:20:21] world. So, if we update it after that

[01:20:23] transform system, then we know that

[01:20:25] local to world component is going to

[01:20:27] match where that entity is going to be

[01:20:30] rendered on this specific frame. So, we

[01:20:33] have the world position. Now, again, we

[01:20:35] just need the camera target here um

[01:20:37] because we actually need to write to

[01:20:38] that transform. And so, let's go ahead

[01:20:40] and replace our temporary variables with

[01:20:44] transform and camera target. So, to set

[01:20:48] the camera's target position, we'll say

[01:20:51] camera target. And then on here,

[01:20:54] remember, we need to actually access the

[01:20:56] underlying Unity object refype, which we

[01:20:59] named camera transform. And then from

[01:21:02] that camera transform, we actually need

[01:21:03] to say dot value. And this is

[01:21:05] essentially us dreferencing that

[01:21:07] pointer. So this value actually comes

[01:21:10] from that Unity object ref. So if we

[01:21:12] hover over this value, you'll see that

[01:21:14] it is a type of transform and this is

[01:21:17] like the regular Unity game object

[01:21:19] transform. And then so to actually set

[01:21:21] the position on it, we can say position

[01:21:25] and make sure that's the lowercase p

[01:21:27] position because again we're accessing

[01:21:28] the unity transform type which calls

[01:21:32] position a lowercase p. And then we'll

[01:21:34] set this equal to the transform and

[01:21:38] again this is this transform associated

[01:21:41] with the local to world of the player.

[01:21:44] So we'll say

[01:21:46] transform.position and again that's a

[01:21:48] capital p position. So this is a float

[01:21:51] three and this is a vector three. Unity

[01:21:54] can automatically cast one to the other.

[01:21:56] No problem. Um I think the last thing

[01:21:58] that I'll just do real quickly is I'll

[01:22:00] just say with all player tag just to you

[01:22:03] know really force that we're running it

[01:22:05] on the players. Um even though again the

[01:22:07] only there should only be one of these

[01:22:09] camera targets but uh better safe than

[01:22:12] sorry. And then I'm going to further

[01:22:13] specify this query by saying with none

[01:22:16] initialize camera target tag. And that

[01:22:21] way if the camera target has not been

[01:22:24] initialized then this system will not

[01:22:26] run. So we'll go ahead and enter play

[01:22:28] mode here. And you'll see that our

[01:22:30] character is centered right in the

[01:22:32] middle of the screen even though he was

[01:22:33] offset a little bit before. So that's

[01:22:35] kind of a good sign. And now as we move

[01:22:37] around, yes, you'll see that the camera

[01:22:39] is following the player around. Um, so

[01:22:42] this is very exciting. It's a it's a

[01:22:44] great step in the right direction.

[01:22:46] However, I don't know how well you'll be

[01:22:47] able to see this over on the YouTubes.

[01:22:50] Let me see if I can zoom in the camera

[01:22:51] just a little bit here as we move

[01:22:53] around. You might be able to see, but

[01:22:55] our player has a little bit of kind of

[01:22:57] jittery movement. Um, it's not smooth.

[01:23:00] It's kind of stuttering and jittering

[01:23:03] and um, you know, it just doesn't look

[01:23:05] really good. Like, yeah, you can

[01:23:06] probably see it. It's pretty apparent

[01:23:08] right there. So, um, yeah. What is going

[01:23:11] on with that? Well, hey, glad that you

[01:23:12] asked because there is actually a really

[01:23:15] interesting explanation to this. So,

[01:23:17] remember how way back when when we

[01:23:20] changed over our character movement to

[01:23:23] actually utilize the Unity physics uh to

[01:23:27] set its movement position. Um, so again,

[01:23:29] every frame we're just writing to the

[01:23:31] velocity component and then Unity is

[01:23:34] using that velocity component to

[01:23:37] actually set its position. again

[01:23:39] accounting for any collisions or any

[01:23:41] other physics things that it might need

[01:23:43] to worry about. So what's actually

[01:23:44] happening is Unity's physics system that

[01:23:47] actually says you know hey this player

[01:23:49] needs to update to this position at this

[01:23:51] time whatever that actually runs in the

[01:23:53] physics system which runs in a fixed

[01:23:57] step simulation system group which means

[01:23:59] that those systems are only called on a

[01:24:02] fixed time step rather than once per

[01:24:05] frame which were actually being rendered

[01:24:07] to the screen. Now, this ends up being a

[01:24:09] little bit of a problem because there is

[01:24:10] kind of this mismatch between the

[01:24:12] player's motion, which is only being

[01:24:14] updated on that fixed time step, and

[01:24:16] what we're actually rendering to the

[01:24:18] screen, which is typically a much higher

[01:24:20] frame rate. So, with this discrepancy,

[01:24:23] we end up getting this kind of jittery

[01:24:25] movement. Now, luckily, there is

[01:24:27] actually a quite easy solution to this.

[01:24:29] So if we go onto our player here and we

[01:24:32] go back into the authoring mode and then

[01:24:34] go ahead and pop open the rigid body

[01:24:37] here, you'll see that there is this

[01:24:39] interpolate option here. So this little

[01:24:42] drop down here, there's interpolate and

[01:24:46] extrapolate. So if we actually um open

[01:24:49] up the entity baking preview to see all

[01:24:51] the things that get added here. Um, if

[01:24:53] we change interpolate over to

[01:24:56] extrapolate, you'll see that this

[01:24:58] actually adds this new thing here, which

[01:25:00] is this

[01:25:02] unity.physics.graphics

[01:25:04] integration.physics graphical smoothing.

[01:25:07] And so, this actually is a data

[01:25:09] component that's added that can kind of

[01:25:11] apply some additional smoothing to the

[01:25:14] character. Um, if we change over to

[01:25:15] interpolate, you'll see that we still

[01:25:17] have that physics graphical smoothing,

[01:25:19] but there's also a physics graphical

[01:25:23] interpolation buffer. And so, actually,

[01:25:26] we can kind of inspect some of these

[01:25:27] things here. So, the uh physics

[01:25:29] graphical smoothing here. Um, this looks

[01:25:32] at the current velocity and there's this

[01:25:34] apply smoothing and uh I believe that if

[01:25:38] this value is at anything above a one,

[01:25:40] then it knows to kind of apply some

[01:25:42] smoothing. And then uh the physical

[01:25:44] graphics interpolation buffer. This

[01:25:46] actually stores the previous transform

[01:25:48] and previous velocity. So it it kind of

[01:25:51] like either do an interpolation or

[01:25:54] extrapolation kind of like based off of

[01:25:56] its current position and current

[01:25:58] velocity to you know figure out where it

[01:26:00] should be. The whole idea behind this is

[01:26:03] that this actually kind of normalizes

[01:26:05] out the graphics for frame rate. So even

[01:26:07] though the entity is still technically

[01:26:10] moving on a fixed time step, the

[01:26:12] graphics don't make it look like that it

[01:26:14] is and it should be smooth movement. So

[01:26:17] um we'll go ahead and have interpolate

[01:26:20] be interpolate and go ahead and go full

[01:26:23] screen on here. And then now we can move

[01:26:26] around and the movement is way perfectly

[01:26:29] smooth now. And so if I'm zoomed in on

[01:26:32] this character here, you'll see that

[01:26:34] there is none of that um frame hitching.

[01:26:37] It is all perfectly smooth movement

[01:26:39] here, which is exactly what we want to

[01:26:41] see. Um now with us completely zoomed in

[01:26:44] on our character like this, you know, it

[01:26:46] is making it extremely apparent that we

[01:26:48] don't have animations uh set up in this

[01:26:51] project yet. So let's get into character

[01:26:54] animations next. Okay. Sorry for the

[01:26:56] flashbang, but just real quickly wanted

[01:26:58] to show um this beautiful art asset

[01:27:01] created by Sill over at Penzilla Design.

[01:27:04] And I just wanted to display kind of how

[01:27:06] these character animations work. So

[01:27:08] you'll see that this is one PNG for the

[01:27:10] sprite sheet. And down along the bottom

[01:27:13] we have the walking animation for the

[01:27:16] astronauts and then up top we have an

[01:27:19] idle animation where it just kind of

[01:27:20] like bounces up and down just a little

[01:27:22] bit. So the idea here is this is kind of

[01:27:24] a flipbook style animation. So we're

[01:27:26] just going to be kind of like playing

[01:27:27] through this frame by frame by frame and

[01:27:29] then we actually can have the ability to

[01:27:31] swap between the idle and moving

[01:27:34] animations when it it kind of changes

[01:27:36] its walking state. So we'll be getting

[01:27:38] into all this stuff here. And then so on

[01:27:40] the sprite itself just for its import

[01:27:42] settings um it's important that we have

[01:27:43] the sprite mode set to single because um

[01:27:47] we're actually not using like multiple

[01:27:49] sprites to play the animation. We're

[01:27:51] just kind of having this as as one

[01:27:52] sprite. And then with our shader, we're

[01:27:54] kind of setting the offset of where it

[01:27:57] should be displaying things to, you

[01:27:58] know, play the animation and the correct

[01:28:00] animation type. Um, I also do have the

[01:28:02] wrap mode set to repeat so that it, you

[01:28:05] know, actually repeats around because I

[01:28:06] think if this isn't set, then um you can

[01:28:09] get to like the last frame of animation

[01:28:11] and it like doesn't display. I don't

[01:28:13] know. It was kind of weird. Okay, so

[01:28:14] just going to go real quickly over how

[01:28:16] this shader works. Um, again, this is

[01:28:19] not really a course on shaders, and I'm

[01:28:21] not really a shader expert or anything

[01:28:23] like that, but um, this shader is the

[01:28:25] one that I have shipped with the uh,

[01:28:27] starter project files, so it will be

[01:28:29] included if you do want that. Um, the

[01:28:32] idea here is that we're again kind of

[01:28:33] doing our own flip book style animation,

[01:28:36] and we're going to be using this for

[01:28:38] both players as well as enemies in this

[01:28:41] project. Now, in my full DOT survivors

[01:28:44] asset store project, um I actually have

[01:28:46] two separate shaders, one for the

[01:28:48] players and one for the enemies, um and

[01:28:51] they can actually do a little bit more

[01:28:53] things than what we have right here. But

[01:28:55] this is just enough to get, you know, a

[01:28:57] nice simple walking animation going, um

[01:28:59] and be able to face the character in the

[01:29:01] appropriate direction and all that. So,

[01:29:03] anyways, just zooming in here. The first

[01:29:04] portion we're kind of like figuring out

[01:29:06] what animation frame we should play. Um,

[01:29:08] so I do have this global time global

[01:29:10] shader property. This becomes more

[01:29:13] relevant when we get to doing gameplay

[01:29:16] pausing at the very end. Um, because

[01:29:18] there is just kind of a a regular time

[01:29:20] node that we can use and we can feed

[01:29:23] this value into uh this portion right

[01:29:26] here. However, what ends up happening is

[01:29:28] the way that I implement gameplay

[01:29:29] pausing um that time value keeps

[01:29:33] incrementing. So then the animations

[01:29:35] still keep playing when the game is

[01:29:36] paused and it looks a little bit weird.

[01:29:38] So I have this global time uh property

[01:29:40] here and we'll be getting into this a

[01:29:42] little bit more after we just get our

[01:29:44] kind of like base animations going. Um

[01:29:47] the idea here is this is a global shader

[01:29:50] property. So all shaders kind of share

[01:29:53] this same value. And then the idea is

[01:29:56] that I actually have a system to tick

[01:29:58] this value up. And then when the game

[01:30:01] gets paused, then we pause the system to

[01:30:03] take that value up, which effectively

[01:30:06] pauses the animation. Kind of moving

[01:30:07] along here. So then you'll see that we

[01:30:09] we have like, you know, frames per

[01:30:11] second and frame count. And so this kind

[01:30:12] of just like takes these things into

[01:30:14] account um when we're figuring out which

[01:30:17] animation actually should be played. The

[01:30:20] animation index is just kind of us

[01:30:22] figuring out the index of what animation

[01:30:24] to play. So you'll remember that the

[01:30:26] walking animation was kind of down

[01:30:28] across the bottom. So that's effectively

[01:30:30] animation index zero. And then the idle

[01:30:33] animation was up top, which is animation

[01:30:35] index one. And then again, just kind of

[01:30:37] like figuring out how to slice things up

[01:30:39] by the uh tile count and and number of

[01:30:42] animation frames and all this uh to set

[01:30:45] the particular tiling and offset. And

[01:30:47] then we're just passing this into the UV

[01:30:50] of our sample texture 2D. And uh we're

[01:30:53] just sampling this sprite sheet right

[01:30:54] here. And this just immediately gets fed

[01:30:56] into the fragment portion of the shader

[01:30:58] here. Um I should point out for you know

[01:31:00] a couple of these material properties

[01:31:03] such as say the facing direction, this

[01:31:06] is known as a material property

[01:31:08] override. And the cool thing about these

[01:31:10] is this allows us to change the values

[01:31:13] of these on a per entity basis. So, of

[01:31:17] course, because every entity in the game

[01:31:20] is going to be facing, you know, either

[01:31:21] to the left or to the right, and we want

[01:31:24] that to be handled on an individual

[01:31:26] basis, we can have this facing direction

[01:31:28] right here. And you see that when we

[01:31:30] kind of open up the node settings on

[01:31:32] here that uh you know, we have the name

[01:31:34] of facing direction and the reference is

[01:31:36] this underscorefacing direction. And

[01:31:39] then the scope, we want this to set to

[01:31:41] hybrid per instance, which means that

[01:31:43] we're effectively overriding this

[01:31:45] property on a per instance basis. And

[01:31:48] the other thing that we need to uh

[01:31:50] facilitate with this is if we go to the

[01:31:52] graph settings, the um allow material

[01:31:55] override box should be checkarked. So

[01:31:57] that means that this material that any

[01:32:00] materials with this shader can have

[01:32:02] their properties overridden via these

[01:32:04] material property overrides. And uh the

[01:32:07] material property overrides are super

[01:32:08] cool because it's like all done through

[01:32:10] ECS that we can just manipulate these

[01:32:13] shader things. And uh so that makes it

[01:32:15] really nice to kind of do some cool

[01:32:17] things with these animations just

[01:32:19] directly through ECS code. So then if we

[01:32:21] actually go to the astronaut material

[01:32:23] right here um we can see that we just

[01:32:26] kind of have some general settings. Make

[01:32:28] sure that we set the alpha clipping on

[01:32:29] here so it doesn't you know render the

[01:32:31] background. You can see that we're just

[01:32:33] feeding in the sprite sheet right here.

[01:32:35] And then here's where we set frame

[01:32:36] count. So this is like the number of

[01:32:38] individual frames. The way that I have

[01:32:40] this shader currently set up is that,

[01:32:42] you know, it needs to be an equal number

[01:32:44] of frames for all the different

[01:32:46] animation types, which is totally fine.

[01:32:49] Um, animation index, you see this is set

[01:32:51] to zero. And then if we set this to one,

[01:32:53] you know, he switches over to the idle

[01:32:55] animation right there. Uh, the animation

[01:32:58] count is the number of unique animations

[01:33:00] that it has. Again, we just have walking

[01:33:02] and idle. So, this is set to two. Um,

[01:33:05] enemies are a little bit different

[01:33:06] because they only have walking

[01:33:08] animations. So, it'll set be set to one

[01:33:10] for the enemies. Frames per second is

[01:33:12] how quickly we want this animation to u

[01:33:15] actually play. Um, so this is 12 frames

[01:33:18] per second. And then you see facing

[01:33:20] direction is set to one means it's

[01:33:21] facing to the right. And if we set it to

[01:33:23] negative one, it'll be facing to the

[01:33:25] left. Um, spawn time just kind of allows

[01:33:27] me to uh have a little bit of an offset

[01:33:30] so the animations can be on different

[01:33:32] frames. But you can see that uh you know

[01:33:34] if we drag this we can get a little

[01:33:35] preview of what that animation looks

[01:33:37] like. And heck, I'll even show you the

[01:33:39] idle animation too here. So you can see

[01:33:41] it's just kind of bobbing up and down.

[01:33:43] So anyways, I don't think I'm actually

[01:33:45] using the spawn time in the this uh

[01:33:48] tutorial project. However, I am using it

[01:33:51] in the full dots survivors project. So

[01:33:54] anyways, that's just kind of like an

[01:33:55] overview about how all this stuff is set

[01:33:57] up. Um, remember that the player already

[01:34:00] has this astronaut material assigned to

[01:34:02] them. So as far as like materials and

[01:34:05] shader goes, like that that stuff is all

[01:34:07] set up as part of the starter project

[01:34:09] files. Um, so now we'll get into the fun

[01:34:12] part of actually playing this animation.

[01:34:14] So, it's actually going to be super easy

[01:34:16] to get started because you'll remember

[01:34:17] that I have that kind of global time

[01:34:19] shader property to actually increment

[01:34:21] the time um which will actually play

[01:34:23] these animations. So, that's what we're

[01:34:26] going to do now. And then the animation

[01:34:27] will start playing and then we can, you

[01:34:29] know, clean up a couple issues after

[01:34:30] that. And so I'm actually going to go

[01:34:32] ahead and go to the very bottom of this

[01:34:33] character authoring script below the

[01:34:35] character move system. And we'll create

[01:34:37] a new system which is going to be a

[01:34:40] public

[01:34:41] partial strct called the global time

[01:34:46] update

[01:34:47] system. And this is going to be an i

[01:34:50] system. And then here we're going to

[01:34:52] have a private static int for the global

[01:34:58] time shader property ID. And inside the

[01:35:04] public void on create, we can go ahead

[01:35:08] and set this uh global time shader

[01:35:13] property id equal

[01:35:16] to shader.property

[01:35:19] property to id and then we actually need

[01:35:22] to pass in the reference string for the

[01:35:25] global time. Um, if you may remember

[01:35:27] that was underscore global time. And

[01:35:31] then the idea with this is we're

[01:35:32] basically just caching this integer

[01:35:35] because um, we are going to need to um,

[01:35:38] inside the public void on update because

[01:35:42] actually every frame we're going to be

[01:35:44] doing a shader set global float. And

[01:35:50] here we can actually just use that

[01:35:51] cacheed integer which is the global time

[01:35:55] shader property ID. And then we can just

[01:35:58] set this equal to system API

[01:36:03] time. Time. Now this is going to get mad

[01:36:06] at us because elaps time is actually a

[01:36:08] double. So we'll just need to cast this

[01:36:10] to a float. So basically the system, you

[01:36:13] know, we're just caching the property ID

[01:36:15] for that global time string. And then

[01:36:17] each frame we can set the global

[01:36:20] property via that cached integer um to

[01:36:24] the current elapse time of the game. So

[01:36:26] now we'll enter play mode and you'll see

[01:36:27] that our character starts walking. So

[01:36:29] this is uh very exciting stuff because

[01:36:31] now we you know now that we're updating

[01:36:33] that global time property as it

[01:36:36] continues to update then the animation

[01:36:39] is able to progress and we have this

[01:36:41] nice little walk cycle. Now obviously a

[01:36:43] couple issues with that. our character

[01:36:45] is not moving, but they're still uh

[01:36:47] playing that walking animation. So here,

[01:36:50] we want them to actually play that idle

[01:36:51] animation. And then also, as we walk

[01:36:53] around, we're only facing to the right.

[01:36:55] So even if we're moving to the right,

[01:36:57] you know, we're still walking this way.

[01:36:58] So we're kind of like moonwalking right

[01:37:00] now. And um yeah, that's, you know,

[01:37:02] cool, but not exactly what we want. And

[01:37:05] then uh so yeah, we need to kind of

[01:37:07] resolve those two things where we're

[01:37:09] playing the correct animation as well as

[01:37:12] the correct facing direction. And so

[01:37:14] let's go ahead and tackle both of those.

[01:37:16] So the first one that I'm going to do is

[01:37:18] the facing direction because that one is

[01:37:20] just a little bit simpler. So it's kind

[01:37:22] of easy to explain how these material

[01:37:24] property overrides work. So we're going

[01:37:26] to need to implement this on the

[01:37:28] character authoring because we want both

[01:37:30] our player characters and enemy

[01:37:32] characters to be facing in the same

[01:37:35] direction that they're moving. So under

[01:37:36] character move speed I'm going to go

[01:37:38] ahead and create a new public strruct

[01:37:42] for the facing direction override and

[01:37:46] this is an I component data and in here

[01:37:50] we'll just have a single public float

[01:37:53] called value. Um now we're not quite

[01:37:56] done yet because we do need to add an

[01:37:58] attribute above here and this is going

[01:38:00] to be the material property attribute.

[01:38:04] Um, and we actually do not have this

[01:38:06] because we do need the using

[01:38:10] Unity. So once we have material

[01:38:12] property, you see we still have a red

[01:38:13] line under it and that's cuz we need to

[01:38:15] specify the actual material property

[01:38:18] that we would like to modify. And you'll

[01:38:21] remember that we do have to use the

[01:38:22] reference string and this is the

[01:38:25] reference string of the facing direction

[01:38:28] which is underscorefacing direction. And

[01:38:30] so you remember with the facing

[01:38:32] direction, when that value is one, we're

[01:38:34] facing to the right. And then when that

[01:38:35] value is negative one, we're facing to

[01:38:37] the left. So let's go ahead and add this

[01:38:39] to the character. So inside the baker

[01:38:42] for this character, say add component.

[01:38:46] And we'll add it to the entity for the

[01:38:48] new facing direction override. And here

[01:38:53] we can just set a default value of equal

[01:38:56] to one. Um because if we don't have a

[01:38:58] default value then the facing direction

[01:39:01] is going to be all wrong and I I don't

[01:39:03] even think the character displays

[01:39:04] actually. So we'll set a character

[01:39:06] override with a default value of one.

[01:39:08] And then if we enter play mode you see

[01:39:10] that our character is still playing with

[01:39:12] their animation. If we select player and

[01:39:14] let's go to the runtime data and we'll

[01:39:17] scroll down to the facing direction

[01:39:20] stuff. So we have facing direction

[01:39:21] override. So now if we set this to a

[01:39:23] value of negative one, you'll see that

[01:39:25] he's now facing to the left and one is

[01:39:28] to the right and zero is nothing. Um but

[01:39:33] so yeah, we're just basically going to

[01:39:34] be changing this between one and

[01:39:36] negative one depending on which way we

[01:39:38] want this character to face which based

[01:39:40] off of, you know, whichever way that

[01:39:41] they're moving. And so we can actually

[01:39:44] um go into our character move system and

[01:39:46] we can also just kind of handle this

[01:39:48] stuff inside of here because it is a

[01:39:50] fairly decent enough place to uh

[01:39:53] actually do that. So um this isn't

[01:39:56] actually a requirement, but I typically

[01:39:58] like to order things in my for each

[01:40:01] query with ones that we need readr

[01:40:04] access to first followed by ones that

[01:40:06] you know only need readonly access. Um

[01:40:08] again order doesn't matter. You can have

[01:40:10] them in any order that you want. This is

[01:40:12] just kind of a personal preference. So

[01:40:13] I'm going to do a ref rw on the facing

[01:40:18] direction override. And then we just

[01:40:21] need to add this to the tupole. So after

[01:40:23] velocity we can say facing direction.

[01:40:28] And then now we can actually write to

[01:40:29] the facing direction. Now we don't

[01:40:31] actually want the facing direction to be

[01:40:35] updated every frame. Um because again if

[01:40:38] the character is not moving, we don't

[01:40:41] want that facing direction to be set to

[01:40:43] zero. So what I just end up doing is I

[01:40:46] say if

[01:40:47] mathabs for absolute. So we take the

[01:40:50] absolute value of the move step

[01:40:55] 2D.x. So if that is greater than

[01:40:59] 0.15 then we can actually go ahead and

[01:41:02] set the facing direction

[01:41:06] um just again by accessing the value rw

[01:41:09] value and we can set this to the math

[01:41:13] sign and that's

[01:41:14] sign sign um passing in

[01:41:19] the

[01:41:20] movestep

[01:41:22] 2d.x. So the idea here is if movestep 2d

[01:41:27] is a negative number this math dos sign

[01:41:30] returns a negative 1. If movep2d.x is a

[01:41:34] positive number math dos sign returns a

[01:41:38] positive value and we just set the

[01:41:39] facing direction. And then again because

[01:41:41] we're checking to see if the absolute

[01:41:43] value is greater than this number then

[01:41:46] that means that you know we actually

[01:41:47] need to be moving for it to change the

[01:41:49] direction. Um whereas, you know, if

[01:41:51] we're just stationary, we still want it

[01:41:52] to just keep the same direction that it

[01:41:54] previously was facing. Okay, so let's

[01:41:56] enter play mode. And you can see that uh

[01:41:58] you know, we can move our character up

[01:42:00] and down. And they're still just staying

[01:42:01] in the same direction because again,

[01:42:03] we're just looking at the X direction.

[01:42:04] If we move to the right, we're still

[01:42:06] staying right. Okay, very good. And

[01:42:08] let's go to the left. And boom, we're

[01:42:10] going to the left. So now, uh if we go

[01:42:12] up and down, we're still facing to the

[01:42:14] left. If we stop, we're facing to the

[01:42:16] left. Um we can flip back over to the

[01:42:18] right and left. And then yeah, this is

[01:42:20] exciting stuff, right? Um, so yeah, you

[01:42:23] know, if we're moving at diagonal angles

[01:42:24] in one direction, it's going to just go

[01:42:27] in that correct direction. So again,

[01:42:28] it's kind of the X direction. Whatever

[01:42:31] it's facing, it's going to be facing in

[01:42:32] that direction. So yeah, that's that's

[01:42:34] that's cool stuff, man. Uh, now what's

[01:42:37] not cool stuff, though, is uh we're

[01:42:40] still walking in place. Again, we want

[01:42:41] our idle animation to play when uh we're

[01:42:44] just walking in place. Um, now this

[01:42:46] one's going to be a player specific one

[01:42:48] because only the players again have idle

[01:42:50] and walking animations whereas the

[01:42:52] enemies um only have walking animations.

[01:42:55] So there's going to be a couple things

[01:42:56] in order to get this to work. So one,

[01:42:58] we're still going to need a material

[01:43:00] property override so we can override the

[01:43:03] animation index that's currently

[01:43:04] playing. And number two, I'm just going

[01:43:06] to create a nice little helper enum for

[01:43:08] us that allows us to easily switch

[01:43:10] between the correct animations without

[01:43:12] having to remember the index each time.

[01:43:14] And then three, we're going to need a

[01:43:16] way to determine if the character is

[01:43:18] moving or stopped. So over in player

[01:43:21] authoring, I'm just going to create a

[01:43:23] couple of these things that we need

[01:43:25] here. So one, we're going to need the uh

[01:43:28] public strruct for the actual animation

[01:43:33] index override. And this should actually

[01:43:37] be an I component data.

[01:43:41] We do need to include the material

[01:43:43] property attribute on here. And the

[01:43:46] reference string is underscorean

[01:43:49] animation

[01:43:51] index. And here same as before, we do

[01:43:54] have a public float called value. Um,

[01:43:58] and then let's go ahead and create our

[01:43:59] little helper enum. So this is a public

[01:44:02] enum for the player animation index.

[01:44:08] And this can just be a bite enum. And

[01:44:11] we'll set uh movement equal to zero,

[01:44:15] idle equal to one, and we'll just say

[01:44:18] none equal to bite domax value. Um, so

[01:44:23] again, the animation indexes, they kind

[01:44:26] of start at the bottom of the sprite

[01:44:28] sheet. So movement is the first one. So

[01:44:30] that's the zero index. And then idle is

[01:44:32] the one above. So that's kind of the one

[01:44:34] index there. And then so we're just

[01:44:36] going to use this little helper enum for

[01:44:38] us to set the animation index. And then

[01:44:41] the last thing that we need to do is

[01:44:43] just scroll down to here and on the

[01:44:45] player do an add component. Uh we're

[01:44:48] going to go ahead and add that

[01:44:51] animation index override to that entity.

[01:44:55] Um so we can also do this inside the

[01:44:57] character move system. So I'm going to

[01:44:58] show you another another little handy

[01:45:01] thing that we can do sometimes. And

[01:45:03] actually I'm doing this slightly

[01:45:04] different in dot survivors. Like I think

[01:45:06] I have a specific system that checks the

[01:45:09] previous players input with their

[01:45:10] current player input and and sets the

[01:45:12] animations appropriately. But uh in this

[01:45:15] case I think it's totally fine for us to

[01:45:17] just continually set the animation index

[01:45:19] here. So let's go ahead and do another

[01:45:22] if check here. So we'll say if system

[01:45:25] API using our nice handy system API

[01:45:28] again uh has component player tag

[01:45:32] because again we want these to only

[01:45:33] apply to the players power to the

[01:45:35] players. Now we do need to pass in the

[01:45:38] entity here. So let's go back up to the

[01:45:41] for each and

[01:45:42] saywith entity access and then we need

[01:45:46] to add on entity to the tupole. And so

[01:45:49] basically we're saying if this entity

[01:45:51] has the player tag, let's say var

[01:45:55] animation override is equal to system

[01:46:00] API.get component RW animation index

[01:46:07] override of course get that from the

[01:46:09] entity. So what's happening here is we

[01:46:13] are using system API to get this

[01:46:16] animation index override component from

[01:46:19] this entity. Now there is just a regular

[01:46:22] get component method and this works

[01:46:24] totally fine. But the RW method is a

[01:46:27] nice handy one because this allows us to

[01:46:30] actually write to this component without

[01:46:33] having to like set back to it with an

[01:46:35] entity command buffer or entity manager

[01:46:37] or something like that. Um, so it kind

[01:46:39] of gives us a similar refw variable type

[01:46:43] and allows us to just modify it

[01:46:45] directly. So here we can actually just

[01:46:47] go ahead and say like animation override

[01:46:50] override

[01:46:52] valueRW value is equal to one. And so

[01:46:56] this is just going to force it to be in

[01:46:58] the idle animation. So now when we enter

[01:47:01] player mode, you'll see that our player

[01:47:02] is now playing the idle animation. So

[01:47:04] now we want to actually determine which

[01:47:06] animation to play based off of the

[01:47:09] character's current movement state. So

[01:47:11] let's go ahead and say var animation

[01:47:13] type is equal to and we can figure this

[01:47:16] out by looking at the move vector and

[01:47:19] basically see that if the character is

[01:47:21] moving. So we can do this by saying math

[01:47:26] dolength sq for length squared and we

[01:47:30] can just pass in the move step 2d and

[01:47:34] this gives us the essentially length of

[01:47:36] the vector. So if the character is not

[01:47:38] moving the length is going to be zero.

[01:47:40] If they are moving then the length is

[01:47:42] going to be some you know number greater

[01:47:45] than zero. And we're using length s

[01:47:47] squared as a little bit of an

[01:47:48] optimization because it's more efficient

[01:47:50] of uh calculating the length of

[01:47:53] something. And we can just say if this

[01:47:55] is greater than float.epsilon

[01:47:59] um which is just a a very small non zero

[01:48:03] number. So basically it means you know

[01:48:06] if we're greater than that we know that

[01:48:08] we're moving. So we can use the little

[01:48:10] uh turnerary operator here. So say

[01:48:12] question mark. And the first thing that

[01:48:15] we would want to apply is the player

[01:48:19] animation

[01:48:21] index movement. And again, the reason

[01:48:24] for that is, you know, if the move

[01:48:26] length is greater than a small number,

[01:48:28] we know that we're moving. Otherwise, we

[01:48:31] can set player animation index

[01:48:35] uh to idle. And so once we have our

[01:48:37] animation type, then we can actually

[01:48:40] assign that to the value here. Um, we do

[01:48:43] need to cast that to a

[01:48:45] float. That animation type right there.

[01:48:49] And then, so now basically again when

[01:48:51] the player is moving, the player's move

[01:48:53] animation index will be set. And then

[01:48:56] when they're not moving, the idle

[01:48:58] animation will be set. So we'll go ahead

[01:48:59] and enter play mode. And you see that we

[01:49:01] have the idle animation playing. And

[01:49:03] let's go ahead and start moving. And you

[01:49:05] see that the character's walking

[01:49:07] animation's playing. Um, and so now

[01:49:09] again, the walking animation and move

[01:49:11] direction are all uh set up correctly.

[01:49:14] And if we stop moving, it'll transition

[01:49:16] right over to the idle animation. So

[01:49:18] that's exactly what to happen. We have

[01:49:19] these nice transitions between the idle

[01:49:22] and moving animations. Again, all based

[01:49:25] off of shader properties, um, which are

[01:49:28] accessed through ECS, which is super

[01:49:29] super cool. By the way, if you are

[01:49:30] enjoying this tutorial so far, I do just

[01:49:32] want to remind you that you can find a

[01:49:34] whole lot more just like this over on

[01:49:35] the Unity asset store where I have the

[01:49:38] complete project files and documentation

[01:49:40] of my game survivors available for

[01:49:42] purchase. And this documentation

[01:49:44] includes written and video overviews of

[01:49:46] a few dozen of the most important

[01:49:48] concepts of the game. So you can learn

[01:49:50] more about, you know, creating a fully

[01:49:52] scoped project rather than just a simple

[01:49:54] little tutorial tech demo like this.

[01:49:56] But, you know, learn how some of these

[01:49:58] concepts fit into a game where we

[01:50:00] actually need to account for the scale

[01:50:01] of things and implement some, you know,

[01:50:04] interesting workarounds in order to get

[01:50:05] things working properly with Unity. I

[01:50:07] put so much into this project to make it

[01:50:09] as good as it can be. And I really think

[01:50:11] that it's going to be an excellent

[01:50:12] learning resource for anyone who wants

[01:50:14] to make a full game with Dots and ECS.

[01:50:16] So, check it out if you want, but if

[01:50:18] you're not interested, you know, a like

[01:50:19] and subscribe on this video is helpful,

[01:50:21] too. Okay, great. So, we can move our

[01:50:22] character around the screen. Now, let's

[01:50:24] actually add a little bit of a gameplay

[01:50:26] into this supposedly game. So, um let's

[01:50:29] start adding in some enemies. And these

[01:50:31] enemies are going to chase down the

[01:50:32] player and deal damage when they come in

[01:50:34] contact with the player. So, um let's go

[01:50:36] ahead and go over to the moon entity

[01:50:38] scene. And I'm just going to go ahead

[01:50:40] and do the same old create game object

[01:50:42] 3D object quad. Um because again, this

[01:50:45] going to be very similar to the player.

[01:50:47] And we can just call this

[01:50:49] alien or alien. Um, so anyways, we're

[01:50:52] going to go over to the authoring mode

[01:50:55] and go ahead and remove that mesh

[01:50:57] collider for now. On the mesh renderer,

[01:51:00] we will add the alien material. Um, this

[01:51:03] works very similar to the regular

[01:51:06] character material. Um, we can actually

[01:51:07] go over to this alien material here.

[01:51:10] And, uh, you can see that it's set up

[01:51:13] pretty similar to the astronaut. Um, we

[01:51:15] just have the alien sprite sheet. Sorry

[01:51:17] again for the flashbang, but it just

[01:51:18] looks a little better with the white

[01:51:20] background. Um, you can see that here's

[01:51:22] the aliens uh sprite sheet right here.

[01:51:24] And again, it just has that one single

[01:51:26] walking animation because the aliens are

[01:51:29] just going to be constantly moving.

[01:51:30] They're not ever going to be standing at

[01:51:31] idle. And then uh differences here is

[01:51:34] animation count is just set to one. And

[01:51:36] that's because we just don't have

[01:51:37] multiple animations and still uh running

[01:51:40] at 12 frames a second, frame count of

[01:51:42] eight frames total. So, fairly similar

[01:51:45] setup overall. Um, anyways, let's go

[01:51:47] over to this alien. I'm going toh bump

[01:51:50] up the scale on this to 1.5 on each

[01:51:53] axis. And let's get in nice and close to

[01:51:56] this alien right here. And add

[01:52:00] a box collider. And we'll go ahead and

[01:52:03] just go ahead and scale this down to

[01:52:06] roughly match the size of the alien

[01:52:09] itself right here. And then we'll just

[01:52:12] make sure and put a thickness of 0.15 on

[01:52:15] there. We'll go ahead and assign the

[01:52:17] alien to the enemy layer. So great. Now

[01:52:21] we have our little alien set up. Now

[01:52:23] let's go ahead and actually apply some

[01:52:25] logic to this alien to actually follow

[01:52:28] around the character. So you'll recall

[01:52:29] that in our character authoring, we just

[01:52:31] have this one central character move

[01:52:33] system. And the intention here is to be

[01:52:36] able to move both player characters as

[01:52:38] well as enemy characters. Um, now that

[01:52:41] where it comes to be a little bit of

[01:52:42] split is where we're actually writing to

[01:52:44] this character move direction. So you'll

[01:52:47] remember for the player we have the

[01:52:49] player input system where we're writing

[01:52:51] to the player's current input. So let's

[01:52:54] do some stuff specific to the enemy and

[01:52:56] we're going to create a system to

[01:52:58] essentially find the player and set its

[01:53:00] move direction based off of the

[01:53:03] direction to the player. So over here

[01:53:04] I'm going to go ahead and create a new

[01:53:06] car class. And this will be the enemy

[01:53:09] authoring. And on here we'll say using

[01:53:13] Unity

[01:53:15] engine and using

[01:53:18] Unity entities because I know that we're

[01:53:21] going to need both of those. So let's go

[01:53:23] ahead and create a public strruct

[01:53:27] enemy tag to be able to identify

[01:53:31] enemies. Of course, this is an I

[01:53:33] component data with no values on it. And

[01:53:36] then on the enemy authoring, we'll make

[01:53:38] sure this is a mono

[01:53:41] behavior. And um let's go ahead and just

[01:53:44] assign this enemy tag to the enemy via a

[01:53:47] baker. So we'll do a private class

[01:53:51] baker. So we'll get a reference to that

[01:53:54] entity here by doing a get entity

[01:53:57] passing in the transform

[01:54:00] usage flags

[01:54:04] damic and say add component of

[01:54:09] enemy tag to that entity. Now um a cool

[01:54:14] thing that we can do is that above this

[01:54:17] authoring component we can create an

[01:54:19] attribute which where we say require

[01:54:23] component type of character authoring.

[01:54:28] So, of course, these authoring

[01:54:29] components, they're all regular Unity

[01:54:31] MonoBehaviors. And regular Unity has

[01:54:34] this require component attribute, which

[01:54:37] means that if you add a MonoBehavior

[01:54:39] component to a particular game object,

[01:54:42] then it will require the existence of

[01:54:45] another MonoBehavior type. The cool

[01:54:47] thing about this is that if you were to

[01:54:50] say add the enemy authoring to the enemy

[01:54:53] game object, then it's also going to

[01:54:55] automatically include that character

[01:54:58] authoring mono behavior as well. Um, and

[01:55:00] that's again because the design of our

[01:55:02] game, we need some things that are

[01:55:04] specific to the enemy, but also we need

[01:55:06] some things that are shared by both

[01:55:08] enemies and players, which we kind of

[01:55:11] put under this sort of character

[01:55:13] umbrella. So, let's go over to the alien

[01:55:15] and we'll say add component. And we'll

[01:55:17] just go ahead and add the enemy

[01:55:19] authoring. And you see when we add the

[01:55:21] enemy authoring, it also automatically

[01:55:23] adds this character authoring where we

[01:55:25] can define a move speed. Now, let's go

[01:55:27] ahead and let's see. The player's move

[01:55:29] speed is 3.5. Let's go ahead and set the

[01:55:32] character's move speed to be 2.5. So,

[01:55:34] they'll be a little bit slower than the

[01:55:36] player, which is nice. Um, so now this

[01:55:39] kind of gives the alien enemy the base

[01:55:41] ability to move around. However, again,

[01:55:44] we're still not setting anything for

[01:55:46] that character move direction. So, let's

[01:55:48] go ahead and take care of that now. And

[01:55:49] one thing that I also forgot is we do

[01:55:51] need to add a rigid body component on

[01:55:54] here. Um, we can say set the mass to

[01:55:56] like 10. Um, linear and angular

[01:55:59] dampening are set to INF infinity. And

[01:56:02] we do not want to use gravity. And let's

[01:56:05] go ahead and set the interpolation mode

[01:56:07] to extrapolate to smooth out the enemy's

[01:56:10] movement. Um, in the main kind of dot

[01:56:13] survivors project I was working on, I

[01:56:15] did have this set to interpolate for the

[01:56:18] enemy characters. But, um, I did notice

[01:56:20] this weird bug where when I was randomly

[01:56:22] spawning them, they would sometimes

[01:56:24] occasionally just like spawn on the

[01:56:27] screen. Even though I was spawning them

[01:56:28] off screen, they would spawn at like

[01:56:30] random places on the screen. It was

[01:56:32] really weird. Um, but when I changed it

[01:56:35] over to use the extrapolate where it's

[01:56:37] not adding that additional buffer

[01:56:39] component, then the movement seems to be

[01:56:42] smooth and it's not spawning them all

[01:56:44] over. So, anyways, weird little quirk

[01:56:46] there. So, below the authoring, we'll

[01:56:48] create a new public partial strct called

[01:56:53] enemy move to player system. And this is

[01:56:57] going to be an i system. And then in

[01:56:59] here we can do a public void on update

[01:57:04] ref system

[01:57:07] state. And then so first thing that we

[01:57:10] want to do is actually get the position

[01:57:12] of the player. So we know how to

[01:57:15] actually move towards the player. So

[01:57:17] typically this would be where we might

[01:57:19] create say an idiomatic for each and be

[01:57:21] able to move towards the player.

[01:57:23] However, I'm going to do something a

[01:57:24] little bit different. And I'm going to

[01:57:26] use a job. And the reason that I want to

[01:57:29] use a job here is because the intention

[01:57:31] for this particular data transformation

[01:57:35] is that we're going to be running this

[01:57:36] across all enemies in the game every

[01:57:39] single frame. And we want to be kind of

[01:57:41] as efficient as possible when we, you

[01:57:44] know, get to the later stages of the

[01:57:45] game where we might have very very large

[01:57:48] numbers of enemies. Uh we kind of want

[01:57:50] to optimize for that case. So, by using

[01:57:53] the job system, this is going to allow

[01:57:55] us to create a job where we can actually

[01:57:57] have all the enemies calculate their

[01:58:00] direction to the player that they need

[01:58:02] to move uh in parallel to each other on

[01:58:05] different job threads. So, this is a a

[01:58:07] very nice handy feature to have. And so,

[01:58:10] I'm going to just kind of demonstrate

[01:58:11] how we actually do that here. So, even

[01:58:13] though we already have this enemy moveto

[01:58:15] player system, we're actually going to

[01:58:17] be creating a separate strruct outside

[01:58:19] of here. And then we're going to use

[01:58:21] that system to schedule this job. So

[01:58:24] this also is going to be a public

[01:58:27] partial strct. And this time we're going

[01:58:29] to call this the enemy move to player

[01:58:33] job. And rather than this being an I

[01:58:35] system or something like that, we're

[01:58:37] actually going to use an I job entity.

[01:58:41] Now, an I job entity. You'll see that

[01:58:44] there's a little red line under here.

[01:58:46] And that's because we need to have a

[01:58:48] private void called

[01:58:51] execute. And this execute method is

[01:58:54] where we're actually going to well

[01:58:56] execute the work that we need to. So um

[01:58:59] what we're doing here is rather than in

[01:59:02] a for each where we kind of create an

[01:59:05] entity query, we can use an implicit

[01:59:07] query as part of the job entity to find

[01:59:11] entities that match a certain query. So

[01:59:14] ultimately what are we trying to do with

[01:59:16] this job? Well, we want to write to the

[01:59:19] character move direction and uh we can

[01:59:22] just call this direction and we also

[01:59:25] need to know the current position of the

[01:59:27] enemy. So in that case we can get that

[01:59:29] from the local transform and we can just

[01:59:32] call this transform. Uh you'll see that

[01:59:35] we have a little red line under here

[01:59:37] because we need the unity.transform

[01:59:40] namespace. Now a couple things that we

[01:59:42] need to do here. Um again very similar

[01:59:44] to a regular idiomatic for each we need

[01:59:48] to specify when we're reading from and

[01:59:51] writing to these different component

[01:59:53] types. So before character move

[01:59:55] direction, uh we just use the ref

[01:59:57] keyword and that specifies that we're

[02:00:00] able to read from as well as write to

[02:00:02] the character move direction. And then

[02:00:04] also for local transform, we need to put

[02:00:07] in in front of it. And that basically

[02:00:09] means that we're just reading from that

[02:00:12] local transform. And the final piece

[02:00:14] that we're going to need is the actual

[02:00:15] position of the player. So we can do a

[02:00:18] public float

[02:00:20] 2 and of course just import

[02:00:23] unity.mmathathematics and we call this

[02:00:25] player position. Uh so the reason we can

[02:00:28] have player position outside of here

[02:00:30] like this is because this player

[02:00:32] position is going to be the same for

[02:00:34] every single enemy that we're iterating

[02:00:37] across. Now inside the execute when

[02:00:39] we're kind of like defining these

[02:00:41] parameters here, these ones are going to

[02:00:42] map to individual values for individual

[02:00:46] enemies. And then inside the execute, we

[02:00:49] can actually do the work that we need

[02:00:51] to. So first we need to figure out the

[02:00:55] vector to the player and we can get this

[02:00:59] from the player position minus the

[02:01:04] transform.position. So that's the

[02:01:05] position of the enemy character. Now you

[02:01:08] see that we do have a red line under

[02:01:09] here and that's because player position

[02:01:11] is 2D whereas the transform position is

[02:01:15] 3D. Uh so luckily we can just do a

[02:01:20] transform.position.xy and that

[02:01:22] essentially just returns the x and y

[02:01:24] components as a float too. And then we

[02:01:26] can do that subtraction. And so now we

[02:01:27] have the vector to the player. And then

[02:01:29] here we can set the

[02:01:31] direction value. And uh because we're

[02:01:34] using the ref keyword like this and not

[02:01:36] like an enabled refr Rw, we don't need

[02:01:39] to do the whole value rw thing. We can

[02:01:42] just set the value directly. And we want

[02:01:45] to set this equal to the vector to

[02:01:48] player. Um, but that's not quite it

[02:01:51] because the vector to player, it's going

[02:01:54] to be, you know, a longer, it's going to

[02:01:56] be a bigger value if it's further away,

[02:01:58] which is going to make the character

[02:02:00] move faster towards the player, which we

[02:02:02] don't exactly want. So, really, we just

[02:02:04] want to do a

[02:02:07] mathnormalize on here to make this

[02:02:09] vector a factor of one. So, that this is

[02:02:12] like literally just the direction to the

[02:02:13] player. And then again their move speed

[02:02:15] is going to be able to scale that vector

[02:02:17] out to determine the actual speed. And

[02:02:19] one more thing that we can do on this

[02:02:21] job is above it we can put the burst

[02:02:24] compile attribute and make sure to

[02:02:26] import

[02:02:28] unity. And the idea here is that this is

[02:02:30] going to be compiled using the burst

[02:02:32] compiler. So now this actually isn't

[02:02:35] going to do anything because we haven't

[02:02:37] scheduled the job yet. So let's go ahead

[02:02:38] and schedule the job. So let's go ahead

[02:02:41] and say var move to player job is equal

[02:02:46] to a new enemy move to player job. Now

[02:02:52] uh we can do this just fine. However, we

[02:02:54] do need to set this player position so

[02:02:57] we know you know actually where to move

[02:02:59] these enemies towards. So in here we can

[02:03:01] kind of do one of these uh

[02:03:03] initialization types and set the player

[02:03:07] position equal to for now let's just go

[02:03:09] ahead and set this to float 2.0 and we

[02:03:13] can just test out the player movement

[02:03:15] and then after that we can actually set

[02:03:17] it to be the actual player's

[02:03:20] position. Um so this is just creating

[02:03:23] the job strct. Now we actually have to

[02:03:25] schedule it. And so we can do this in a

[02:03:27] couple of ways. So we can say move to

[02:03:30] player

[02:03:31] job.run just like this. And when we do a

[02:03:34] move to player job.run, that means it's

[02:03:36] going to immediately execute this job on

[02:03:40] the main thread. Now that's not exactly

[02:03:42] what we want because that's pretty much

[02:03:43] the same thing as doing an idiomatic for

[02:03:46] each. So there's not really much of an

[02:03:48] advantage of using this run rather than

[02:03:51] an idiomatic for each. Um, we also do

[02:03:54] have the ability to do a schedule. And a

[02:03:57] schedule means that we're going to

[02:03:59] schedule this work to run on a worker

[02:04:02] thread, but it's only going to run on

[02:04:04] one single worker thread. And then the

[02:04:06] final one we can do is achedu parallel.

[02:04:08] And the idea behind this one is that if

[02:04:10] we have a bunch of enemies, it's going

[02:04:12] to be able to run this job on multiple

[02:04:16] worker threads across, you know, kind of

[02:04:18] all the available worker threads that we

[02:04:20] have. Now there are some limits and

[02:04:22] caveats to how this actually works but

[02:04:25] um in general when we have a very large

[02:04:27] number of entities um and we're using a

[02:04:30] job like this we typically want to aim

[02:04:32] for using the schedule parallel. Now

[02:04:34] doing this just like this uh works just

[02:04:36] fine like it it kind of hooks into

[02:04:38] Unity's dependency system uh as we want

[02:04:41] it to. Um however I just want to kind of

[02:04:43] like be a little bit more explicit about

[02:04:45] it right now and just kind of display a

[02:04:48] little bit about what's happening under

[02:04:49] the hood. So this schedule parallel it

[02:04:51] can actually take in an argument here

[02:04:54] for the uh state.d dependency and the

[02:04:58] state.d dependency basically just means

[02:05:00] you know hey these are all the incoming

[02:05:03] dependencies that I have from any

[02:05:05] previous systems. So it kind of like

[02:05:08] knows what other data components are

[02:05:11] being read from and written to. And so

[02:05:14] it basically allows us to tell this job

[02:05:17] to say, hey, we know which components we

[02:05:20] can safely access at a given time. And

[02:05:22] so that's great, but we also need to

[02:05:25] make sure that we are actually writing

[02:05:27] back to the dependency. So we can say

[02:05:32] state.dependency is equal to this job

[02:05:35] that we're scheduling here. So this says

[02:05:37] you know hey any dependencies that we're

[02:05:40] creating in this move job you know let's

[02:05:42] assign those back to the state

[02:05:44] dependency so that any further systems

[02:05:47] down the line they can know about the

[02:05:50] dependencies from this job. Now again if

[02:05:52] we do just the move to player jobs

[02:05:54] schedule parallel how we initially had

[02:05:56] it should kind of implicitly do this for

[02:05:59] us. Um, but again, it's just good, I

[02:06:01] think, to know kind of what's happening

[02:06:04] under the hood and how we can kind of be

[02:06:06] explicit about assigning the

[02:06:08] dependencies. And one thing that I

[02:06:10] actually forgot to do here is um on the

[02:06:13] enemy move to player job, we only want

[02:06:15] this to update against any enemies. And

[02:06:18] there's nothing in our query specifying

[02:06:20] that, you know, we're looking for only

[02:06:22] enemies. So, actually, there is a way

[02:06:24] that we can do this with another

[02:06:25] attribute. So we can say with all type

[02:06:28] of enemy tag and so this means that

[02:06:32] anything that has the enemy tag um we're

[02:06:34] going to kind of add that as a

[02:06:36] requirement to this implicit query

[02:06:39] that's created with these component

[02:06:40] types right here. So anyways we can come

[02:06:42] back to Unity and enter play mode here

[02:06:45] and you see that the alien will start

[02:06:46] moving towards uh basically just the

[02:06:48] center and then when it gets there it

[02:06:50] just kind of glitches out because it's

[02:06:52] moving back and forth so its facing

[02:06:54] direction is always the same. Um, and we

[02:06:56] can actually push them around and

[02:06:57] they'll basically just always move back

[02:06:59] towards that center position. So now

[02:07:01] let's go ahead and change things up a

[02:07:02] little bit so that the enemy actually

[02:07:04] just follows the player. So luckily

[02:07:06] that's pretty easy to do. So instead of

[02:07:08] just setting the player position equal

[02:07:09] to float 2.0, we need to actually get a

[02:07:14] uh reference to the player's position.

[02:07:15] So instead of just setting it to float

[02:07:17] 2.0, we'll actually get the player's

[02:07:19] position itself. So before getting the

[02:07:22] player's position, we actually need to

[02:07:24] get the player entity itself. And then

[02:07:26] from there, we can get its position. So

[02:07:29] we can use our handy system API to do a

[02:07:33] get singleton entity. And inside these

[02:07:36] type brackets, we can look for the

[02:07:38] player tag. And so this basically means,

[02:07:41] hey, let's find the singleton entity

[02:07:43] that has the player tag component. So a

[02:07:46] singleton entity all it really means is

[02:07:49] if we have a particular tag component

[02:07:52] that is only owned by one entity in the

[02:07:55] game then that can be considered a

[02:07:57] singleton component and then the get

[02:08:00] singleton entity just returns us that

[02:08:02] entity with that. Now there's nothing

[02:08:05] like stopping us from adding player tag

[02:08:08] to multiple entities. Um, so we can

[02:08:10] actually end up getting some errors

[02:08:12] where if we had like three entities

[02:08:15] tagged with player tag, we could no

[02:08:17] longer do get singleton entity because

[02:08:19] it doesn't know which one to return.

[02:08:21] Also, you will see that writer is giving

[02:08:22] me this little yellow line under here.

[02:08:25] And if we just do the little alt enter

[02:08:27] to implement that, then you see that it

[02:08:30] creates our onreate here. And then we do

[02:08:32] a state.require for update player tag.

[02:08:36] So we we did use this require for update

[02:08:39] earlier and um this is just kind of it

[02:08:41] saying you know hey if you're doing this

[02:08:43] get singleton entity well you better

[02:08:45] make sure that that player tag actually

[02:08:47] exists. So uh you know we'll make sure

[02:08:50] to do require for update player tag and

[02:08:52] that way if this player does not exist

[02:08:55] then this update will not get ran

[02:08:57] because you know we we're looking for an

[02:09:00] entity that doesn't exist. So anyways,

[02:09:02] once we have the player entity, let's

[02:09:04] actually get the player position. And we

[02:09:06] can do this again by using this handy

[02:09:08] system API get component. And let's get

[02:09:13] the local transform type here. And then

[02:09:17] inside we do need to pass in the player

[02:09:20] entity because we want to say, hey, get

[02:09:22] the transform from this player. Um, now

[02:09:26] this actually returns the full local

[02:09:28] transform here. So actually what we want

[02:09:30] to do is get the position off that local

[02:09:35] transform. And uh in fact we want the 2D

[02:09:38] version of this. So we can do

[02:09:41] aposition.xy. So then this player

[02:09:43] position if we hover over it it should

[02:09:45] now be a float to player position. And

[02:09:49] then in here instead of setting this to

[02:09:52] float 2.0 we'll just set this to the

[02:09:54] player position. Boom. Exciting. So now

[02:09:57] when we enter player mode, you'll see

[02:09:59] that the enemy is following the player.

[02:10:01] And as we move around, the enemy will

[02:10:03] continue updating its position. And you

[02:10:06] see that, you know, it's it's doing cool

[02:10:08] stuff like it's uh actually able to um

[02:10:12] you know, change its facing direction

[02:10:14] and all that. So uh yeah, this is uh

[02:10:17] this is looking really good. Um and by

[02:10:19] the way, the enemies can still go

[02:10:21] through the um little boulders uh even

[02:10:25] though the the players are unable to.

[02:10:27] That's just kind of a little bit of a

[02:10:29] game design thing. Uh, in Vampire

[02:10:31] Survivors, all the enemies can run right

[02:10:34] through all the environment elements.

[02:10:36] So, I just figured, you know, hey, let's

[02:10:37] let's stay true to the source material

[02:10:40] and uh, you know, obviously it's not

[02:10:42] that hard to like add the collision

[02:10:44] layer or something like that, but uh, it

[02:10:46] just um, it's kind of how the game plays

[02:10:48] and I I decided to keep that that way.

[02:10:50] Okay. So, while we're on the topic of

[02:10:51] doing things for the enemy, let's

[02:10:53] implement the ability for the enemy to

[02:10:55] attack the player. Now, to accomplish

[02:10:59] this, we're going to need to kind of

[02:11:00] implement some uh kind of underlying

[02:11:03] infrastructure for keeping track of

[02:11:05] damage. So, first let's go over to our

[02:11:07] character authoring and we're going to

[02:11:09] add a few new data components. So, we

[02:11:11] can just add these say below the facing

[02:11:13] direction override here. So let's have a

[02:11:15] public strruct for the characters max

[02:11:20] hit points and this is an I component

[02:11:23] data and this will be a public int value

[02:11:27] because we're just going to do integer

[02:11:29] hit points here and then we'll do a

[02:11:31] public strruct for the character current

[02:11:35] hit

[02:11:36] points also an I component

[02:11:39] data with a public int for value. Um,

[02:11:43] now the idea again of having this split

[02:11:46] between the max hit points and the

[02:11:48] current hit points is the max hit points

[02:11:50] is just going to stay the same

[02:11:51] throughout the duration of the

[02:11:52] application whereas current hit points

[02:11:54] is going to be updating as it's taking

[02:11:56] damage and maybe even healing itself and

[02:11:59] any of that. But um, in this we're just

[02:12:01] just uh taking damage. So on our

[02:12:03] character authoring let's go ahead and

[02:12:05] add a new public int field for hit

[02:12:09] points. And so these are going to be the

[02:12:11] uh amount of hit points that the

[02:12:13] character um basically has. So we can do

[02:12:17] add component to the entity for a new

[02:12:21] character max hit points. And here we'll

[02:12:25] set value equal to authoring hit points

[02:12:28] and do the same add component on the

[02:12:32] entity for the

[02:12:34] new character current hit points.

[02:12:38] The value is also going to be

[02:12:41] authoring.h hit points. So now we can

[02:12:44] come back to Unity and on our alien

[02:12:47] let's give it some amount of hit points.

[02:12:49] Let's give it say 10 hit points. And our

[02:12:52] player we can have more hit points. We

[02:12:53] can have say 100 hit points. And then um

[02:12:56] again these are going to be just the set

[02:12:59] to the current and maximum hit points

[02:13:00] for them. And so now we need a way to

[02:13:03] actually apply damage from one entity to

[02:13:05] another. Now, there's a lot of different

[02:13:06] ways that you can approach this. And the

[02:13:09] one that we're going to be using is kind

[02:13:10] of the damage buffer approach. And with

[02:13:13] the damage buffer approach, the idea is

[02:13:15] going to be that points of damage can be

[02:13:18] accumulated from several places

[02:13:20] throughout the frame. They could even be

[02:13:22] accumulated on separate threads. We're

[02:13:24] not going to be doing that in this

[02:13:25] video, but it still has the ability to.

[02:13:27] Um, so we're we're kind of adding all

[02:13:29] these points of damage into a damage

[02:13:30] buffer. And then we have a specific

[02:13:33] system in the frame that is going to go

[02:13:35] through that damage buffer and it's

[02:13:37] going to essentially apply all those hit

[02:13:40] points in the buffer to the current hit

[02:13:42] points. So rather than like having a

[02:13:45] bunch of systems that directly modify

[02:13:47] the current hit points, we can just have

[02:13:49] one system that handles dealing damage.

[02:13:52] Um, the nice thing about having that one

[02:13:54] central system that like actually

[02:13:56] applies the damage is within that

[02:13:58] self-contained system, you know, we can

[02:14:00] check to see if the uh character's

[02:14:03] health is below zero and then we know to

[02:14:05] destroy the entity or we can also play

[02:14:07] some special damage effects or, you

[02:14:09] know, just maybe handle different types

[02:14:10] of situations for uh different types of

[02:14:13] damage. So there's there's a lot of

[02:14:15] things that we can do with having a

[02:14:16] dedicated damage system. So it's a

[02:14:18] pretty good architectural approach in my

[02:14:20] opinion. So to actually create this

[02:14:22] damage buffer, um we're going to

[02:14:24] actually need another component type.

[02:14:26] And this is going to be a new component

[02:14:28] type. Um so we'll call this public

[02:14:30] strruct. And I'll say damage this frame

[02:14:34] is what I like to call it. And this is

[02:14:36] an

[02:14:38] Ibuffer element data. So by adding an

[02:14:42] Ibuffer element data buffer to an

[02:14:45] entity, um this is kind of the concept

[02:14:48] known as a dynamic buffer. A dynamic

[02:14:50] buffer is a way to associate array-like

[02:14:54] data with a specific entity. And in this

[02:14:57] case, we're going to be adding points of

[02:14:59] damage in this buffer. We're kind of

[02:15:01] accumulating them in this buffer and

[02:15:04] then again applying that damage to the

[02:15:07] character. So in the damage this frame

[02:15:09] um actual component here, we'll have one

[02:15:13] public int field for value. And again,

[02:15:16] this is going to be essentially points

[02:15:18] of damage that are added to uh the

[02:15:20] character when we want to damage them.

[02:15:22] Um to actually add this dynamic buffer,

[02:15:24] we can use the add buffer and then just

[02:15:27] add in the type which is the damage uh

[02:15:31] this frame and then we can add this of

[02:15:34] course to the entity. Um by the way if

[02:15:37] we have a dynamic buffer that we want to

[02:15:39] prepopulate with data this actually

[02:15:41] returns a value here and then so we can

[02:15:44] say you know like asdf at0 equals new

[02:15:48] damage this frame yada yada yada so we

[02:15:50] can actually you know populate this

[02:15:52] dynamic buffer inside the authoring

[02:15:54] component if we wanted to. Um but again

[02:15:57] we're not starting with any damage. So

[02:15:59] now let's actually create a system where

[02:16:01] we can kind of accumulate these points

[02:16:03] of damage and apply them to the

[02:16:05] character's current hit points. So let's

[02:16:07] scroll just down to the bottom here uh

[02:16:09] below our global time update system and

[02:16:12] I'll create a new public partial strct

[02:16:16] for the process damage this frame system

[02:16:21] which also is an I system. Um, so the

[02:16:24] idea here is that inside our on update

[02:16:28] that we're going through this buffer and

[02:16:30] applying these points of damage towards

[02:16:32] the current hit points. So here we can

[02:16:35] do just a regular for each. I'll put a

[02:16:38] little placeholder here in system

[02:16:41] API.query query and then um again in

[02:16:44] this case we're going to be writing to

[02:16:47] the uh character current hit points and

[02:16:51] we also do need access to the dynamic

[02:16:55] buffer of that damage

[02:16:58] this frame type. So basically what we're

[02:17:01] doing here is we need to write to this

[02:17:03] current hit points and then to get

[02:17:05] access to this damage this frame buffer

[02:17:08] we say dynamic buffer of type damage

[02:17:11] this frame. So now we can go here and

[02:17:14] just change this to hit points and

[02:17:17] damage this frame. So what we'll do

[02:17:21] first is we'll say if damage this frame

[02:17:24] and if we actually hover over this, this

[02:17:26] is actually the dynamic buffer damage

[02:17:28] this frame. And so the dynamic buffer,

[02:17:30] it allows us to check to see if it is

[02:17:33] empty. Um, so if it is empty, just go

[02:17:36] ahead and continue because we don't need

[02:17:38] to do any further damage processing on

[02:17:40] this particular entity. Otherwise, we're

[02:17:42] just going to go ahead and do another

[02:17:44] for each. And so this time, we're going

[02:17:46] to say for each damage in damage this

[02:17:50] frame. We're going to go ahead and

[02:17:52] accumulate these points of damage

[02:17:54] against our hit points. So we'll say hit

[02:17:57] points dot value RW value minus equals

[02:18:02] damage do value. So again, this damage

[02:18:06] type what we're calling right here, this

[02:18:08] essentially represents one element of

[02:18:11] this damage this frame type. And then so

[02:18:14] by saying damage value, we're actually

[02:18:17] getting that underlying integer value

[02:18:19] for the damage to apply. So again, we're

[02:18:21] just kind of subtracting our hit points

[02:18:23] by that damage. And then what we want to

[02:18:27] do is just say damage this

[02:18:30] frame. here. And then that's just going

[02:18:32] to clear out the damage buffer so we're

[02:18:35] not, you know, continually applying

[02:18:36] damage. And then so we're going to be

[02:18:38] adding one other thing to this system

[02:18:40] later on um when it relates to actually

[02:18:42] destroying entities. Um but for now,

[02:18:44] this is enough to test. And you know,

[02:18:45] let's just go ahead and add the burst

[02:18:47] compile attribute on here as well. Okay,

[02:18:50] so let's enter play mode. And this is

[02:18:51] actually only going to work if I pause

[02:18:53] the game. Um because remember, we're

[02:18:54] clearing out that damage buffer every

[02:18:56] frame. And so, um, it's not going to

[02:18:59] give us enough time to test adding a

[02:19:02] point of damage, um, inside this damage

[02:19:04] buffer here, um, to, you know, actually

[02:19:07] confirm that it's working. So, first of

[02:19:09] all, I do want to point out that the

[02:19:10] character's current hit points and max

[02:19:12] hit points are both set to 100. And we

[02:19:14] can see this dynamic buffer of damage

[02:19:17] this frame here. So, we can uh, expand

[02:19:19] out this buffer and say add element. And

[02:19:22] we can add element zero. and we'll say

[02:19:25] add five points of damage. So, I will go

[02:19:28] ahead and resume execution and pause

[02:19:30] again. So, you'll see that the damage

[02:19:32] buffer cleared out. And if we go up to

[02:19:35] our maximum uh or our current hit

[02:19:37] points, the value is now down to 95. Uh

[02:19:40] whereas max hit points were not

[02:19:41] changing. So, it's still at 100. Now, we

[02:19:44] can also go back to this um dynamic

[02:19:46] buffer. We can add an element here. So,

[02:19:48] we can say add five points of damage.

[02:19:51] And let's add another element of 10

[02:19:54] points of damage. So this should damage

[02:19:56] for a total of 15 points this frame. So

[02:19:59] I'm just going to go ahead and uh play

[02:20:01] and pause and go back here. And you see

[02:20:03] that both of those points of damage have

[02:20:06] now been accounted for and the character

[02:20:09] is now down to 80 hit points. So next

[02:20:11] up, I think the next logical thing would

[02:20:13] be to have the enemies apply points of

[02:20:16] damage to the character. And so we're

[02:20:18] going to do that by using collision

[02:20:19] events. And now collision events are a

[02:20:22] kind of Unity ECS specific concept where

[02:20:25] anytime uh two colliders and at least

[02:20:28] one of them needs to have a rigid body

[02:20:30] similar to just you know regular

[02:20:31] collisions in Unity that when those two

[02:20:34] objects collide um for every frame of

[02:20:38] them being overlapped we're actually

[02:20:40] going to raise what is known as a

[02:20:41] collision event. And this collision

[02:20:44] event, we can basically inspect this

[02:20:47] collision event and determine which

[02:20:49] entities this collision event is in

[02:20:51] between. And if this collision event is

[02:20:54] between a player and an enemy, well,

[02:20:56] we're going to have the enemy apply

[02:20:58] points of damage to the player. So,

[02:20:59] actually to get this to work in the

[02:21:02] regular Unity authoring components, we

[02:21:04] need to go onto the alien and make sure

[02:21:06] we go to our box collider and we'll have

[02:21:09] is trigger unchecked because we want it

[02:21:12] to be a physical collider, not just a

[02:21:13] trigger. But the um provides contacts.

[02:21:17] We do need to check mark that. It is a

[02:21:19] little bit weird the wording of it. um

[02:21:21] inside the kind of Unity's custom

[02:21:24] physics authoring components through

[02:21:25] that samples tab um there actually is a

[02:21:28] little bit of a dropdown where you can

[02:21:29] say collide rays collision events um and

[02:21:33] so that's the same as what this

[02:21:34] configuration is where we have the is

[02:21:37] trigger not checked and provides

[02:21:39] contacts checked that means it's going

[02:21:41] to properly raise collision events when

[02:21:44] this entity intersects with another

[02:21:47] collider so I'm going to go ahead and

[02:21:48] add in two additional data components

[02:21:51] ments here. So the first one is a public

[02:21:54] strruct for the enemy attack

[02:21:57] data and this is just a regular I

[02:22:00] component data and we're going to have

[02:22:02] two fields on here. So the first is

[02:22:04] going to be a public integer for hit

[02:22:07] points. So this is how many hit points

[02:22:09] of damage it's going to be dealing when

[02:22:11] it collides with the player. And then we

[02:22:13] also want a public float for cooldown

[02:22:17] time. And again, because these collision

[02:22:20] events are constantly being raised as

[02:22:23] the uh player and character are

[02:22:25] colliding with each other, you know, we

[02:22:27] don't want to apply points of damage

[02:22:29] every single frame or, you know, even if

[02:22:31] it was dealing one hit point, you know,

[02:22:33] it would like vaporize the player in

[02:22:35] like 1 second of collision, which is not

[02:22:37] exactly the best gameplay that we want.

[02:22:39] So this cooldown time is going to allow

[02:22:41] us to have a little bit of time between

[02:22:45] the um enemy dealing out attacks. Now

[02:22:48] how I've chosen to make this cooldown

[02:22:50] time work is we actually have a another

[02:22:53] component on here which is the enemy

[02:22:57] cooldown

[02:22:59] expiration

[02:23:01] timestamp. And so this also is an I

[02:23:04] component data but it is also an

[02:23:06] enabable component. And in here we're

[02:23:09] going to store a public double called

[02:23:12] value. Now you may remember that the

[02:23:14] elapsed time that we get from Unity is a

[02:23:18] double. So we're going to store this

[02:23:20] expiration time stamp as a double. Um,

[02:23:22] the idea here is that with this

[02:23:24] component, when an enemy goes on

[02:23:27] cooldown, that will set the value of

[02:23:30] this equal to the current game time plus

[02:23:34] the cooldown time to get the time that

[02:23:37] the cooldown essentially expires. And

[02:23:39] I've also chosen to use this as an

[02:23:41] enabable component. So we can have a

[02:23:44] specific portion of our enemy attack

[02:23:46] system look for anything with this

[02:23:48] cooldown expiration timestamp and enable

[02:23:51] it or disable it. And then basically if

[02:23:53] this is enabled it means the enemy is on

[02:23:55] cooldown and it's not able to attack.

[02:23:58] But if it's disabled it's not on

[02:24:00] cooldown and it is able to attack. So

[02:24:02] let's just go ahead and add this enemy

[02:24:04] data here. Um so in the enemy authoring

[02:24:07] we're going to need two new fields here

[02:24:08] for the public int. We'll say attack

[02:24:12] damage here and then

[02:24:15] public float

[02:24:17] for cooldown time. And so we'll go ahead

[02:24:21] and do an add component on the entity

[02:24:24] for a new

[02:24:27] enemy attack data. And by the way, this

[02:24:30] is kind of one of the naming conventions

[02:24:32] that I typically like to use where if a

[02:24:34] data component requires multiple fields,

[02:24:37] um, I'll I'll typically end the type

[02:24:40] name with the word data. So then we'll

[02:24:42] actually assign the data. So hit points

[02:24:45] is equal to authoring dot attack damage

[02:24:49] and then the cooldown time is equal to

[02:24:54] authoring.cooldown time. And then we

[02:24:56] also need to make sure that we're adding

[02:24:58] in the component for the enemy cooldown

[02:25:03] expiration timestamp. Of course, adding

[02:25:06] that to the entity. And then um we don't

[02:25:09] want our entity to be on cooldown by

[02:25:11] default. So what we can actually do is

[02:25:13] we can say set component enabled uh

[02:25:18] passing in the enemy cooldown

[02:25:21] expiration timestamp. And so by default

[02:25:25] when we add this component to the entity

[02:25:28] it's going to be enabled by default and

[02:25:30] we want it to start out as disabled

[02:25:32] because we don't necessarily want the

[02:25:33] enemy to spawn with the cool down

[02:25:36] enabled. Um even though the cooldown

[02:25:38] time is is relatively short but um still

[02:25:41] it's nice to show this uh handy little

[02:25:43] API where we can say set component

[02:25:46] enabled and we pass in the component

[02:25:48] type which of course is the enemy

[02:25:50] cooldown expiration timestamp. Uh, so we

[02:25:53] have to specify the entity and we can

[02:25:56] pass in true or false if we want it to

[02:25:58] be enabled or disabled. So we'll set it

[02:26:00] to false for disabled and then again

[02:26:03] it'll spawn being disabled. So anyways,

[02:26:06] moving down below the enemy moveto

[02:26:08] player job, let's go ahead and create a

[02:26:11] public

[02:26:14] partial strct for the enemy attack

[02:26:18] system. And this is an I system. So here

[02:26:21] we'll go ahead and do our on update

[02:26:23] public void on

[02:26:26] update ref system

[02:26:30] state. And so first thing we'll do is

[02:26:33] we'll check to see if the entity is on

[02:26:35] cooldown. We'll see if its expiration

[02:26:37] timestamp has elapsed. So we'll go ahead

[02:26:40] and do a for each here. Little

[02:26:42] placeholder in system API.query.

[02:26:46] And so we'll look for anything that has

[02:26:48] that enemy

[02:26:49] cooldown expiration timestamp. And we're

[02:26:53] also going to um do that enabled ref RW

[02:26:59] against the enemy cooldown ex expiration

[02:27:04] timestamp. So the purpose of having this

[02:27:07] kind of twice in there is um this one it

[02:27:11] allows us to actually access the

[02:27:12] underlying data. So we can actually see

[02:27:15] what the cooldown expiration timestamp

[02:27:17] is. And then this enabled refrw of this

[02:27:22] allows us to set the enabled disabled

[02:27:24] property. Now having both of these

[02:27:27] things in here like this means that this

[02:27:29] component does need to be enabled. So

[02:27:32] this system is only going to run on

[02:27:34] entities that are currently on cooldown.

[02:27:36] If the entity is not on cooldown, this

[02:27:39] system will not run for them. So let's

[02:27:42] go ahead and just change this here. So

[02:27:44] we can just call this the

[02:27:46] expiration timestamp and then we'll say

[02:27:50] cool down enabled for the uh enabled

[02:27:55] refw. Um so now we need to check what

[02:27:58] the current elapsed time is and we can

[02:28:01] get that from system API

[02:28:04] time. Time. So let's say if the

[02:28:09] expiration timestamp do value is greater

[02:28:13] than the current elapsed

[02:28:16] time. So meaning that the expiration

[02:28:18] timestamp is still further off in the

[02:28:21] future. Just go ahead and say continue

[02:28:24] and then we'll continue to the next

[02:28:25] iteration. Now if this check returns

[02:28:28] false where the elapse time is greater

[02:28:31] than the expiration

[02:28:32] timestamp then we know that it should be

[02:28:35] off cooldown. So we'll go ahead and say

[02:28:38] cooldown enabled value RW equals false

[02:28:43] and then so cool down will be disabled

[02:28:44] and then that entity will no longer be

[02:28:47] iterating in this part of the for each

[02:28:49] loop here. So again this is just us kind

[02:28:51] of like figuring out if the entity

[02:28:53] should be on cool down or not. And now

[02:28:56] what we'd like to do is apply damage to

[02:28:58] the player when the enemy comes in

[02:29:01] contact with the player. Now, like I

[02:29:03] mentioned, Unity ECS has this concept of

[02:29:05] collision events. And so what we're

[02:29:08] going to be doing right now is actually

[02:29:10] scheduling a job to react to these

[02:29:13] collision events. So outside of the

[02:29:15] attack system here, we'll do another uh

[02:29:18] public strruct. This does not need to be

[02:29:20] a partial strruct. And we can call this

[02:29:22] the enemy attack job. And this is going

[02:29:26] to be a special job type of I

[02:29:29] collision events job. And uh make sure

[02:29:33] that that is actually a a plural

[02:29:35] collision events. And so this actually

[02:29:37] does require the Unity physics name

[02:29:39] space. And you'll see we have a little

[02:29:41] underline under here because we need to

[02:29:43] implement the specific uh missing

[02:29:45] members. In this case, it's this public

[02:29:47] void called execute which takes in a

[02:29:50] collision event and we have access to it

[02:29:52] via this collision event. Now, the first

[02:29:54] thing that we need to do in our

[02:29:56] collision event is we need to make a

[02:29:58] determination because inside this

[02:30:01] collision event we have uh two entities.

[02:30:04] There's entity A and entity B. And we

[02:30:08] don't know anything about entity A or

[02:30:10] entity B. We just know that it's it's

[02:30:12] two entities that are part of this

[02:30:14] collision event. That means they're

[02:30:15] they're colliding actively colliding

[02:30:17] with each other on this frame. So what

[02:30:19] we need to do is we need to figure out

[02:30:22] which one of these entities is the

[02:30:24] player and which one of these is the

[02:30:26] enemy and also confirm that this

[02:30:28] collision event is actually between an

[02:30:30] enemy and a player and it's not like you

[02:30:32] know collision between two enemies or

[02:30:34] it's not a collision between the player

[02:30:36] and an environment rock. So we're going

[02:30:39] to kind of take care of both of those

[02:30:40] things at once. Now, the way that we do

[02:30:43] that is unfortunately we don't have

[02:30:44] access to our handy system API from

[02:30:48] inside of this job here. So, when we're

[02:30:50] doing, you know, these things like

[02:30:51] system

[02:30:52] API.get component, there's also a system

[02:30:56] API.component where we can see if an

[02:30:59] entity has a particular component. Um,

[02:31:01] so again, we can't use that here. So we

[02:31:03] have to do what system API is actually

[02:31:05] doing under the hood and that is

[02:31:08] creating what is known as a component

[02:31:10] lookup and that component lookup allows

[02:31:13] us to check to see if an entity has a

[02:31:16] component if it does have a component.

[02:31:18] We can actually get the active values

[02:31:20] for that component. We can even set the

[02:31:22] values on that component. Um, so it's

[02:31:25] kind of a nice thing to do and it kind

[02:31:27] of goes a little bit a little lever

[02:31:29] deeper than what Unity kind of hides

[02:31:31] away in some of the uh nice code

[02:31:33] generation stuff with system API. So

[02:31:35] we'll go ahead and define this as a

[02:31:38] public component lookup. So the first

[02:31:41] one that we want to do a lookup against

[02:31:43] is the player

[02:31:45] tag. So again, we have to figure out,

[02:31:47] you know, which of these entities is the

[02:31:49] player. So we can call this the player

[02:31:51] lookup. And one thing that we do need to

[02:31:53] do is before this actually add the

[02:31:56] readonly attribute. Um you'll see that

[02:31:59] there actually multiple types of

[02:32:01] readonly. And so we want to import the

[02:32:04] unity.colctions readonly attribute. And

[02:32:07] so if we just go up to the top you'll

[02:32:08] see that there is just using

[02:32:10] unity.colctions. If you have that in

[02:32:12] there then it should import the correct

[02:32:14] readonly type because there's again

[02:32:16] multiple readonly types. And this

[02:32:18] readonly tag again, it kind of feeds

[02:32:20] into some of the things I've hinted at a

[02:32:22] little bit about dependency checking

[02:32:24] where, you know, we can kind of provide

[02:32:26] some additional information to Unity's

[02:32:30] safety dependency checking to say, you

[02:32:33] know, hey, we're only reading from this

[02:32:35] component. And let's do the same thing

[02:32:37] um for the enemy. Rather than doing the

[02:32:39] enemy attack, we can actually kind of

[02:32:41] kill two birds with one stone here. And

[02:32:43] we'll say uh enemy attack data. And this

[02:32:46] enemy attack data is going to give us

[02:32:48] access to, you know, how many points of

[02:32:51] attack damage we want to deal to the

[02:32:53] player. So in this case, we can call

[02:32:56] this say the uh attack data lookup. And

[02:32:59] so we'll just start with these. We're

[02:33:00] going to add a couple more in just a

[02:33:02] second here. But uh so again, the first

[02:33:04] thing we need to figure out is which

[02:33:06] entity is the player

[02:33:08] entity and which entity is the enemy

[02:33:12] entity. So we can do that by just a

[02:33:15] couple quick little if else checks. So

[02:33:17] we can say

[02:33:18] if player lookup has component passing

[02:33:24] in the collision event entity A. So this

[02:33:29] says you know hey if if the entity A has

[02:33:33] that player tag that we can look up from

[02:33:35] the player lookup then we kind of know

[02:33:37] that entity A is the player. Um, but we

[02:33:40] also want to check to see if the attack

[02:33:44] data

[02:33:45] lookup has component the collision

[02:33:51] event entityb and I just realized I'm

[02:33:54] doing a should do a lowercase c

[02:33:56] collision event because it's the uh

[02:33:58] actual type that we're doing on here. Um

[02:34:00] so anyways if this is the case then we

[02:34:02] know that the player entity is equal to

[02:34:06] collision event entity A and the enemy

[02:34:12] entity is the entity B from this

[02:34:16] collision event and then so we also kind

[02:34:18] of need to check the opposite case. So

[02:34:21] we can actually do an else if player

[02:34:25] lookup has component collision event

[02:34:28] entity B and then attack data lookup has

[02:34:32] the entity A. Then again this is now the

[02:34:35] opposite case where the player entity is

[02:34:37] entity B and the enemy entity is entity

[02:34:41] A. And then we can say else return. So

[02:34:45] again, what's happening here is we're

[02:34:47] just determining which entity is the

[02:34:50] player entity, which enemy is the en

[02:34:52] enemy entity. And then this else return

[02:34:54] basically means that if the collision

[02:34:57] event is not between one player and one

[02:35:00] enemy, then it's it's a different

[02:35:02] collision that you know maybe is valid

[02:35:04] somewhere else. But right now, we're

[02:35:06] just looking for collisions between

[02:35:09] enemies and players so we can deal

[02:35:11] points of damage to the enemy. So, um,

[02:35:14] at at this point, we've kind of figured,

[02:35:16] you know, hey, we know that the

[02:35:17] collision event is between a player and

[02:35:19] an enemy. Now, let's actually deal some

[02:35:22] points of damage. And again, we don't

[02:35:23] want to constantly deal damage. We only

[02:35:25] want to deal damage when the enemy is

[02:35:28] not on cooldown. So, how do we determine

[02:35:30] if the enemy is on cooldown or not?

[02:35:33] Well, you'll remember that we set this

[02:35:34] up as an I enable component. And anytime

[02:35:38] that this cooldown expiration timestamp

[02:35:40] value is enabled, then we know that it

[02:35:42] is on cooldown and it cannot attack.

[02:35:44] However, it's if it's disabled, it can

[02:35:47] it can attack. So, what we can do is

[02:35:49] back in the enemy attack job. Let's go

[02:35:51] ahead and add another public component

[02:35:55] lookup. And this one is going to be for

[02:35:58] the enemy

[02:36:00] cooldown

[02:36:03] expiration timestamp. And then we can

[02:36:05] just call this say the cooldown lookup.

[02:36:09] And you'll notice that I did not add the

[02:36:11] readonly attribute in front of it. And

[02:36:13] that's because when we actually apply an

[02:36:15] attack to an enemy, we're going to be

[02:36:17] setting a new value for this uh cooldown

[02:36:20] expiration. Um but we'll do that uh

[02:36:22] after we actually apply the damage. So

[02:36:25] anyways, after the the whole if else

[02:36:27] deals here, so we can do a little

[02:36:29] another if check here to say if cooldown

[02:36:33] lookup dot is component enabled and pass

[02:36:38] in our enemy entity because again we

[02:36:41] figured out who our enemy entity is from

[02:36:43] this step right above here. So again

[02:36:45] we're basically just saying you know is

[02:36:47] the component that we're have the lookup

[02:36:50] on this enemy cooldown expiration is

[02:36:52] this enabled? If it is on that enemy

[02:36:55] entity, let's go ahead and return

[02:36:57] because the ent the entity is on

[02:37:00] cooldown and we don't want to apply any

[02:37:01] damage. Now, if it's not on cooldown,

[02:37:04] let's actually apply some damage to the

[02:37:06] player. Before we actually apply the

[02:37:08] damage, let's just go ahead and reset

[02:37:09] the cooldown timer cuz we're kind of

[02:37:11] dealing with that right now. So, first

[02:37:13] we need to get the attack data. And

[02:37:16] remember, we already have the attack

[02:37:18] data lookup. So we can say attack data

[02:37:23] lookup and the way that we actually get

[02:37:24] a particular component is we can

[02:37:26] actually kind of use the um array

[02:37:30] indexers almost to pass in the entity.

[02:37:34] In this case it's the enemy entity and

[02:37:36] then that returns us the attack data

[02:37:39] from this attack data lookup. So what

[02:37:41] we're doing is we're saying you know we

[02:37:43] have this this whole data lookup here.

[02:37:45] we can pass in the entity to it and then

[02:37:49] it's going to return the data component

[02:37:52] for that entity of that type. So you see

[02:37:55] this attack data is that enemy attack

[02:37:57] data type and we can do something

[02:37:59] similar where we want to say set the

[02:38:01] data again we're setting the data on

[02:38:04] this uh cooldown lookup and so we're

[02:38:07] setting this for the enemy entity and we

[02:38:09] can set this equal to a new enemy

[02:38:13] cooldown

[02:38:15] expiration timestamp. And here we can

[02:38:18] just directly set the value here equal

[02:38:21] to uh elapsed time which we don't have

[02:38:25] yet but we can get plus the attack data

[02:38:29] dotcooldown time. And so anyways we'll

[02:38:32] just define a field up here for the

[02:38:36] public double elapsed time and then we

[02:38:41] can set this when we actually go to

[02:38:42] schedule the job. So anyways let me just

[02:38:44] kind of recap what I'm doing right here.

[02:38:46] So first we're getting the attack data

[02:38:48] and we know that from the attack data we

[02:38:51] can get the cooldown time you know how

[02:38:54] long it is between the next attack and

[02:38:57] then we can actually set this cooldown

[02:38:59] expiration timestamp equal to the

[02:39:02] current game time plus that additional

[02:39:04] cooldown time. And the way we set that

[02:39:07] for that entity is by going to our

[02:39:09] cooldown lookup indexing it by that

[02:39:12] entity itself and we can directly set

[02:39:15] that data right there. And again the

[02:39:17] reason that we can do this on this type

[02:39:19] is because we do not have the readonly

[02:39:21] attribute on here. For these readonly

[02:39:23] ones we're only able to read data from

[02:39:26] these. We can't actually write data to

[02:39:29] these components. And again that just

[02:39:31] kind of goes into the uh safety

[02:39:33] dependency checking that Unity does. Now

[02:39:35] you will remember that there is actually

[02:39:37] one other thing that we need to do and

[02:39:39] that is to enable this component. And so

[02:39:42] we can again use the cooldown lookup and

[02:39:45] do a set component enabled. And then in

[02:39:49] here we pass in the entity which is the

[02:39:52] enemy entity. And then we pass in true

[02:39:55] or false for true if we want it enabled,

[02:39:57] false if we want it disabled. And we do

[02:39:59] want it to set to true so that it does

[02:40:01] go on cool down. So, at this point, um,

[02:40:04] we're basically when we collide with the

[02:40:06] player, we're just going on cooldown

[02:40:08] again, but we're not actually applying

[02:40:10] any damage. Now, you remember that the

[02:40:12] way that we apply damage is not by

[02:40:14] directly modifying the current hit

[02:40:17] points component of the player. It's by

[02:40:19] adding points to that damage buffer. So,

[02:40:22] let's go up to the top of the jobruct

[02:40:24] and go ahead and create a new uh lookup

[02:40:27] here. This time it's actually going to

[02:40:29] be a buffer lookup. And that's because

[02:40:32] the

[02:40:33] damage this frame is a dynamic buffer.

[02:40:37] And so we can just call this the damage

[02:40:40] buffer lookup. And then scrolling back

[02:40:43] down

[02:40:44] here, go ahead and just at the very

[02:40:47] bottom and we'll say uh get the player

[02:40:50] damage buffer here. And so we can get

[02:40:52] this equal by grabbing the damage buffer

[02:40:57] lookup that we just created here and

[02:40:59] passing in the player entity. So once we

[02:41:02] have the damage buffer from the player,

[02:41:04] we can just say player damage

[02:41:08] buffer.add and this will add a new

[02:41:12] damage this frame. And the value of this

[02:41:17] is going to be equal

[02:41:19] to

[02:41:20] attack data cuz remember we already

[02:41:23] grabbed the attack data from the enemy

[02:41:25] entity right here uh hit points and that

[02:41:28] is going to add a new point of damage to

[02:41:32] our player. So if we went over to Unity

[02:41:34] right now it's not going to work. Why is

[02:41:36] that? Because we're not actually

[02:41:39] scheduling this job. So um inside this

[02:41:42] enemy attack system we're going to

[02:41:44] schedule this job. So below this for

[02:41:47] each we can go ahead and say create a

[02:41:50] new we can create a new attack job equal

[02:41:54] to new

[02:41:57] enemy attack job. And then here we have

[02:42:00] to set all these component and buffer

[02:42:02] lookups and the elapse time and all

[02:42:04] that. So we can do that just all right

[02:42:06] in here. So we'll just go top to bottom.

[02:42:08] So, we'll start with the player lookup.

[02:42:11] And we can get these again because we're

[02:42:13] back in our system. We can use our handy

[02:42:15] system

[02:42:17] API.get component lookup. And we pass in

[02:42:21] the type. And this is the player tag.

[02:42:25] And then inside the parentheses, we'll

[02:42:27] pass in true or false for is read only.

[02:42:30] Again, because we are doing read only on

[02:42:33] this, we'll go ahead and pass in true

[02:42:35] here. Um, next up we'll get the attack

[02:42:38] data

[02:42:39] lookup. Also using our system API to do

[02:42:43] a get component lookup against that

[02:42:46] enemy attack data. And this also is read

[02:42:51] only. So we'll pass in true there for

[02:42:53] the cooldown lookup. We get this by

[02:42:56] doing a system API.get get component

[02:43:00] lookup for the enemy

[02:43:04] cooldown

[02:43:06] expiration

[02:43:07] timestamp. And this one we can just

[02:43:10] leave that empty because it defaults to

[02:43:12] false. Um, and the final lookup that we

[02:43:14] need is the

[02:43:16] damage buffer lookup. We can set this

[02:43:20] equal to system

[02:43:24] API.getbuffer lookup this time. And in

[02:43:28] here the type is damage this frame. And

[02:43:32] then is readon is false by default. And

[02:43:35] we do need to write to it. So we'll

[02:43:38] leave it blank like that. So anyways, um

[02:43:40] actually we also do need to add in one

[02:43:43] more thing which of course is the

[02:43:45] elapsed time. And we already did grab

[02:43:48] the elapse time up here. So we can just

[02:43:52] do elapse time like that. So once again

[02:43:55] this is us just kind of creating the job

[02:43:57] strct. Now we actually need to schedule

[02:44:00] the job and it's a little bit unique how

[02:44:02] we have to do this for um these

[02:44:05] collision events jobs because we need

[02:44:07] something known as the simulation

[02:44:09] singleton which is part of uh unity

[02:44:11] physics. So we'll say var

[02:44:14] simulation singleton is equal to system

[02:44:19] api.get get singleton and the type is

[02:44:22] just the

[02:44:24] simulation singleton like that and then

[02:44:27] writer is going to give us a little

[02:44:28] warning because we should add the

[02:44:30] required for update on the simulation

[02:44:33] singleton so we don't end up having like

[02:44:35] any null singletons or anything so uh

[02:44:37] once we have the simulation singleton

[02:44:39] now we can actually schedule the job so

[02:44:41] again I'm going to be really explicit

[02:44:43] and set the state dependency equal to

[02:44:46] this new attack job doc

[02:44:50] schedule in here. We can pass in the

[02:44:52] simulation

[02:44:54] singleton as well as the state.

[02:44:57] Dependency just like that. So the job

[02:45:00] has been scheduled. Uh let's go ahead

[02:45:02] and add just a couple more things here.

[02:45:04] So above this enemy attack job, we can

[02:45:06] add in the burst compile attribute on

[02:45:08] there. Um also when we're scheduling the

[02:45:11] job inside the on update, we can make

[02:45:13] this burst compiled as well. And one

[02:45:16] other kind of important thing is that

[02:45:18] above the enemy attack system, we need

[02:45:21] this to update in a very specific uh

[02:45:24] portion of the frame. So we'll say

[02:45:26] update in group type of physics system

[02:45:31] group. And we do need to import the uh

[02:45:35] physics.systems namespace. And we do

[02:45:38] want this to update after the type of

[02:45:42] physics

[02:45:44] simulation group. But we do need this to

[02:45:47] also update

[02:45:49] before type

[02:45:51] of after physics system group. So the

[02:45:57] idea here is that this needs to update

[02:45:59] at this specific portion of the frame

[02:46:01] where we're inside the physics system

[02:46:03] group. But after this physics simulation

[02:46:07] group because um I believe that inside

[02:46:11] this physics simulation group is where

[02:46:13] all these collision events are

[02:46:15] effectively detected and raised and we

[02:46:18] want to kind of run our own logic in the

[02:46:21] same frame that that collision event

[02:46:22] happened but after it already happened

[02:46:26] um for that frame. So having them after

[02:46:28] the physics simulation group will kind

[02:46:30] of ensure that that collision event has

[02:46:33] been properly raised and that we're able

[02:46:35] to kind of detect it in the proper spot

[02:46:38] in the frame. So anyways, I know that

[02:46:40] was a lot of code, but hopefully it was

[02:46:42] more or less straightforward. Uh let's

[02:46:43] go back to Unity and test it out. So

[02:46:45] when we are back in Unity, we need to

[02:46:47] make sure that we go to the enemy

[02:46:49] authoring and let's say apply five

[02:46:51] points of damage. And we'll set this to

[02:46:53] a cool down of 0.5 seconds. So every 0.5

[02:46:58] seconds is going to apply five points of

[02:47:00] damage to the player. So I'll go ahead

[02:47:02] and click on the player and I'm going to

[02:47:04] just have this set up in runtime mode so

[02:47:06] we can kind of enter play mode and

[02:47:08] immediately monitor those current hit

[02:47:10] points. And when the enemy collides with

[02:47:12] the player, you'll see that it's ticking

[02:47:13] down by five units every um half a

[02:47:17] second about. And again, the idea with

[02:47:20] this is that we're adding to the

[02:47:22] player's damage buffer. And um actually

[02:47:25] you can't see because we're you know

[02:47:27] kind of adding and clearing the buffer

[02:47:29] in the same frame. So it still says that

[02:47:31] the dynamic buffer has zero elements in

[02:47:33] it. Um so that's not entirely accurate

[02:47:35] because it it's adding and removing them

[02:47:37] in the same frame and then it's using

[02:47:40] that buffer to actually decrement the

[02:47:43] hit points in here. Now obviously we're

[02:47:45] way in the negative right now. So um I

[02:47:48] think the next logical portion would be

[02:47:50] to actually implement entity

[02:47:52] destruction. Okay. Okay. So, I'm just

[02:47:53] going to go ahead and create a new

[02:47:55] script here. And we'll go ahead and make

[02:47:57] this a strruct. And we can call this the

[02:48:00] destroy entity system. And we're going

[02:48:03] to be using the destroy entity system

[02:48:05] for multiple things. So, it's going to

[02:48:07] be used for um players, enemies, as well

[02:48:10] as the experience point gems um and some

[02:48:13] of the player attacks as well. So, it's

[02:48:14] going to be used for kind of a lot of

[02:48:16] things in here. Um so, this is going to

[02:48:18] be an eye system type. And we do need to

[02:48:21] include the

[02:48:25] unity. Right

[02:48:26] here. Um, and we do need to make this a

[02:48:29] public partial strct. And so actually

[02:48:32] before we even do anything in the

[02:48:34] destroy entity system, I'm going to go

[02:48:36] ahead and create this uh public strruct

[02:48:39] for the destroy entity flag. And so this

[02:48:44] is an I component data, but it's also an

[02:48:47] I enable component data. And the purpose

[02:48:50] of this destroy entity flag is this is

[02:48:53] going to be added to any entity that can

[02:48:55] be destroyed. And then we'll have it set

[02:48:58] to disabled by default. And then when we

[02:49:00] enable the component then it basically

[02:49:03] means that this entity should be

[02:49:04] destroyed and this destroy entity system

[02:49:07] will actually destroy that entity. Now

[02:49:09] this kind of goes back into you know the

[02:49:11] idea of having like one central system

[02:49:13] for things because you know there could

[02:49:15] be multiple places in code where we say

[02:49:17] destroy a particular entity. However um

[02:49:20] having this nice one central destruction

[02:49:23] system allows us to do some additional

[02:49:25] logic. You know maybe want to play a

[02:49:27] sound effect or spawn an item or some

[02:49:30] other things when the entity gets

[02:49:31] destroyed. So this kind of allows one

[02:49:34] place for all that logic to live. So

[02:49:36] I'll go ahead and do a public void on up

[02:49:40] update here. And uh this does need ref

[02:49:44] system

[02:49:45] state. And so what we can do is just

[02:49:48] create a simple for each

[02:49:51] here in system

[02:49:55] API.query. And we're going to look for

[02:49:56] anything with that destroy entity

[02:50:00] flag. And we also do need with entity

[02:50:04] access as well. So we can actually

[02:50:06] destroy the entity. So let's go ahead

[02:50:09] and assign some names for these. Now the

[02:50:11] destroy entity flag, it doesn't have any

[02:50:13] data. So we can just name this

[02:50:15] underscore because we're not even using

[02:50:16] it. Um but we still do need it part of

[02:50:20] the query right here. Um and then for

[02:50:22] the entity, we can just call this

[02:50:24] entity. Now to actually go ahead and

[02:50:26] destroy the entity again we can't just

[02:50:29] say entity manager destroy entity right

[02:50:32] here because that would be modifying

[02:50:35] with this for each iteration and we

[02:50:38] would get some issues with that. So we

[02:50:41] do have to use an entity command buffer.

[02:50:42] Um I showed previously how to create our

[02:50:45] own entity command buffer. In this case,

[02:50:48] I'm going to show you how to use one of

[02:50:49] the built-in entity command buffers

[02:50:51] because um Unity has a couple of

[02:50:55] built-in entity command buffers that

[02:50:57] execute at specific points in the frame.

[02:51:00] And in this case, we can utilize these

[02:51:02] to destroy entity at the end of the

[02:51:04] frame. And so, we'll get that with this

[02:51:06] um end simulation entity command buffer

[02:51:09] system. This is a I'm going to warn you

[02:51:11] ahead of time, this is a very verbose uh

[02:51:13] type name. So we can get this by system

[02:51:17] API

[02:51:18] dot get singleton and we have to type in

[02:51:22] the end simulation entity command buffer

[02:51:28] systems singleton and then of course

[02:51:30] writer is going to yell at us to say we

[02:51:33] should include the require for update

[02:51:36] which we should and so that just gets us

[02:51:38] the entity command buffer system and

[02:51:40] then from that we actually have to

[02:51:42] create the um actual entity command

[02:51:46] buffer. And so we can do that by getting

[02:51:48] the end

[02:51:50] ECB

[02:51:52] system.create command buffer. And then

[02:51:55] in here we have to pass in this world

[02:51:58] unmanaged variable which you can get

[02:52:00] from

[02:52:01] state.world unmanaged. It's just

[02:52:04] required for creating those types of

[02:52:06] entity command buffers. Now, because

[02:52:08] this is a built-in entity command

[02:52:10] buffer, we don't need to actually

[02:52:12] manually call ECB.playback,

[02:52:15] um, Unity does that at that specific

[02:52:18] point in the frame, which happens, as

[02:52:20] you can imagine, at the end of the

[02:52:22] simulation system group. So, in here, we

[02:52:24] can just say end

[02:52:27] ECB.destroy entity and pass in the

[02:52:30] entity. So, right now, this is obviously

[02:52:32] a very simple system. We're just looking

[02:52:34] for anything with this destroy entity

[02:52:36] flag and then destroying it. However, a

[02:52:39] little bit later on, we're going to be

[02:52:40] adding some additional logic in here.

[02:52:42] Um, now there's a couple little cleanup

[02:52:44] things that I like to do here. Um, we'll

[02:52:46] have this update at a specific portion

[02:52:48] of the frame here. So, we'll say update

[02:52:51] in group type of simulation system

[02:52:55] group. Now, I did mention that by

[02:52:58] default, um, when you create a system,

[02:53:01] it will spawn into the simulation system

[02:53:03] group if you don't specify it otherwise.

[02:53:06] Um, but I do want to point this out

[02:53:07] because we can

[02:53:09] say order last equals true. And this

[02:53:14] basically means that it forces it to

[02:53:17] update at the end of that simulation

[02:53:19] system group. And again, because we are

[02:53:21] scheduling the destruction of entities

[02:53:23] inside this um end simulation entity

[02:53:26] command buffer, let's make sure this

[02:53:28] actually

[02:53:30] updates before that uh end

[02:53:36] simulation entity command buffer system.

[02:53:41] Um because that's the system that

[02:53:42] actually plays back things that we're

[02:53:44] kind of recording in there. So anyways,

[02:53:46] we have this uh destroy entity system

[02:53:48] set up. Now, let's actually go ahead and

[02:53:51] add this destroy entity flag to our

[02:53:53] characters. So, back in our character

[02:53:55] authoring, we can just go to the

[02:53:58] character authoring and down at the

[02:54:00] bottom, we'll do an add component. Add

[02:54:03] that destroy entity flag. Add that to

[02:54:08] the entity. And then again, we want this

[02:54:10] disabled by default. So we can do a set

[02:54:13] component enabled on the destroy entity

[02:54:18] flag entity false. So disabled by

[02:54:23] default. Now let's go to our good old

[02:54:26] process damage this frame system. And

[02:54:28] again here's where we have a nice little

[02:54:31] method of checking to see um if hit

[02:54:36] points do value ov value. By the way, I

[02:54:41] don't think we've used this value Roro

[02:54:43] yet. Um, but it basically when we have

[02:54:46] access to a component via this ref RW,

[02:54:49] we can say value RO and that just gives

[02:54:52] us read only access to it to basically

[02:54:54] just specify that we're not writing to

[02:54:56] it. So anyways, if the hit points do

[02:54:58] value is below zero, then in here we can

[02:55:03] do a system API set component enabled

[02:55:08] the

[02:55:10] destroy entity flag on the entity to

[02:55:15] true. So we will disable it. Now we do

[02:55:18] actually need to get a reference to that

[02:55:20] entity. So at the end of our little for

[02:55:23] each here with

[02:55:25] entity

[02:55:27] access and then add the entity here. And

[02:55:31] actually you might notice a slight

[02:55:33] little bit of a flaw with this kind of

[02:55:35] thinking here where um you know we're

[02:55:39] checking to see if the hit points are

[02:55:40] below zero and then we're saying set

[02:55:42] component enabled destroy entity flag.

[02:55:45] Well, as of right now, there's not

[02:55:47] anything actually forcing this query to

[02:55:50] say, "Hey, this this must have a destroy

[02:55:53] entity flag." So, we would end up

[02:55:55] getting some errors if this system did

[02:55:57] happen to run on an entity that did not

[02:56:00] have this destroy entity flag. So, we

[02:56:02] can kind of add that as a requirement

[02:56:05] here. Now you might be thinking hey we

[02:56:07] can just modify this query to say uh

[02:56:10] with all uh

[02:56:13] destroy entity flag and that's like oh

[02:56:17] hey you know we're we're saying you know

[02:56:19] make sure that that entity flag exists

[02:56:21] on that entity. Um unfortunately yes it

[02:56:24] does mean that but it also means that

[02:56:26] that component must be enabled for this

[02:56:28] query to match. Um, luckily there is a

[02:56:31] method that we can uh use instead which

[02:56:34] is the width present. And this basically

[02:56:37] means that this entity must have the

[02:56:40] destroy entity flag but it doesn't care

[02:56:42] whether it is enabled or disabled. So

[02:56:45] now let's come back to Unity. We'll

[02:56:46] enter play mode and the uh enemy will

[02:56:49] come down towards the player. You see

[02:56:50] the hit points tick down down down down.

[02:56:54] And once it reaches zero, if all goes

[02:56:56] well, our player should get destroyed.

[02:56:58] Boom. There goes our player. Um,

[02:57:00] actually, I think I did notice one

[02:57:03] slight bug in our logic. We're checking

[02:57:05] to see if the value is less than zero,

[02:57:07] but really it should be less than or

[02:57:09] equal to zero. And then, so once again,

[02:57:11] we'll watch the character hit points

[02:57:12] tick down, down, down, down, down. And

[02:57:15] then once it reaches zero, then this

[02:57:17] character, the player character will be

[02:57:19] destroyed. So, we no longer have a

[02:57:21] player character in the world. And the

[02:57:23] enemy just walks off into space. So, um,

[02:57:26] now let's go ahead and start adding a

[02:57:28] little bit of additional logic to our

[02:57:29] destroy entity system. So, early on you

[02:57:32] will remember that we do have this game

[02:57:34] UI and we do indeed have a game over

[02:57:37] panel on here. Um, luckily I've kind of

[02:57:39] already thought about this where um, we

[02:57:42] do indeed have a show game over UI which

[02:57:47] just calls this show gameover UI co-

[02:57:49] routine which just sets this game over

[02:57:52] panel to active after one and a half

[02:57:54] seconds. So let's go to the destroy

[02:57:56] entity system here. And what we can do

[02:57:59] is you know before we schedule the

[02:58:01] destruction of this entity we can say if

[02:58:04] system API has

[02:58:08] component player tag entity. So if the

[02:58:12] entity has a player tag aka if the

[02:58:14] entity is a player you can say game UI

[02:58:18] controller instance.show

[02:58:21] show game over

[02:58:24] UI. And that's just because I have this

[02:58:27] uh game over UI set up as a singleton

[02:58:30] MonoBehavior right here. So now if we

[02:58:32] play the game and the enemy starts

[02:58:34] attacking the player, after a little

[02:58:36] bit, the player should get destroyed any

[02:58:39] second now. And then after one and a

[02:58:42] half seconds, we show this game over

[02:58:44] screen. And then it should all be wired

[02:58:46] up where if we click the quit button, it

[02:58:48] returns us to the main menu. And then we

[02:58:50] can click play and then it will return

[02:58:53] us to the actual game and we can play

[02:58:55] around again. And um so yeah, we have,

[02:58:58] you know, a little bit of a game loop.

[02:58:59] We have a a game over state, but uh we

[02:59:02] don't actually have the ability to fight

[02:59:04] back. So let's implement the ability for

[02:59:06] the player to actually fight back. All

[02:59:07] right. So we're going to start over in

[02:59:09] Unity. And in the moon entity scene, I'm

[02:59:11] just going to do as we've done a few

[02:59:12] times before and create a new 3D quad.

[02:59:16] And this is going to be the plasma

[02:59:19] blast. So this plasma blast, we can go

[02:59:23] ahead and go back to the regular

[02:59:24] authoring view right here. And of

[02:59:27] course, we'll remove this mesh collider

[02:59:29] and actually go to the mesh renderer and

[02:59:32] we'll add the plasma blast mesh. Uh the

[02:59:36] plasma blast mesh is or the plasma blast

[02:59:39] material I should say is just a regular

[02:59:41] unlit material that just kind of

[02:59:44] displays this little plasma blast

[02:59:46] sprite. So on here, let's go ahead and

[02:59:48] add a sphere collider. And the sphere

[02:59:51] collider is nice because this should

[02:59:52] basically just uh wrap right around this

[02:59:57] plasma blast. So I'm not even going to

[02:59:58] do any adjustments to that. That looks

[03:00:00] great. Um what we do need to do is we

[03:00:02] want to check mark the box for is

[03:00:04] trigger. Now, um, we're going to do

[03:00:07] something similar to how the enemy

[03:00:10] entities can deal damage to the player.

[03:00:12] Um, except we're going to be using

[03:00:13] trigger events rather than collision

[03:00:15] events. The reason that I want to use

[03:00:17] collision events here is because I want

[03:00:19] these, uh, attacks to essentially be

[03:00:21] able to pass through the uh, entities,

[03:00:25] but only deal damage to them one time.

[03:00:28] Now, I'm kind of setting things up this

[03:00:30] way, even though I'm not really fully

[03:00:31] utilizing it in this tutorial project,

[03:00:33] but in the full dots survivors project.

[03:00:36] Um, we can have attacks that have a lot

[03:00:39] more kind of modularity to them where

[03:00:41] they can, you know, deal damage to

[03:00:43] multiple entities and so on. And so it

[03:00:45] just makes sense to have these as a

[03:00:46] trigger so they can kind of pass through

[03:00:49] the other entities without like, you

[03:00:50] know, hitting the enemy and then it, you

[03:00:53] know, kind of registering as a collision

[03:00:55] in that case because we don't

[03:00:56] necessarily want that to happen. And

[03:00:58] then one last thing that I do need to do

[03:01:00] is on the layer, make sure we change

[03:01:03] this to the player attack layer. And we

[03:01:05] of course just don't want this like

[03:01:07] plasma blast in the scene. We actually

[03:01:09] want to spawn instance of these. And we

[03:01:12] can do that by using the prefab

[03:01:14] workflow. So I'm actually going to go

[03:01:15] ahead and create a new folder here for

[03:01:18] prefabs. And in this prefab folder,

[03:01:21] let's just go ahead and drag this plasma

[03:01:23] blast. And we can delete the plasma

[03:01:25] blast out of the main scene here. So

[03:01:28] what it's going to happen is our player

[03:01:29] is now going to have a reference to this

[03:01:32] plasma blast. And then we're going to be

[03:01:34] able to spawn these into the world. So

[03:01:36] let's come back over to the IDE and

[03:01:39] below this player animation index. We're

[03:01:42] going to go ahead and do something

[03:01:43] similar to the player what we did for

[03:01:46] the enemy. So we'll do a public strruct

[03:01:49] here and we can call this the player

[03:01:51] attack data. And this is going to be an

[03:01:54] I component data. And then on here,

[03:01:58] rather than storing an integer for the

[03:02:00] hit points, we're going to store a uh

[03:02:03] public entity reference for the attack

[03:02:08] prefab. And then uh with this prefab on

[03:02:11] there, we can instantiate instances of

[03:02:13] it in the world. And then similar to the

[03:02:16] enemies, we're also going to have a

[03:02:18] public float for the cooldown time.

[03:02:24] And also we're going to do that same

[03:02:25] type of thing where we have a

[03:02:28] public strruct for the player

[03:02:33] cooldown

[03:02:35] expiration timestamp which is an I

[03:02:38] component data and again this is going

[03:02:40] to have a public double uh just called

[03:02:44] value. So let's go ahead and add both of

[03:02:46] these things to the player. Now, um this

[03:02:49] is something we actually haven't done

[03:02:50] yet is by storing a prefab reference.

[03:02:53] And again, we're going to be authoring

[03:02:54] these prefabs through game objects. You

[03:02:56] saw that when we created that plasma

[03:02:58] blast um prefab, we just dragged it into

[03:03:01] a prefabs folder like we would with any

[03:03:03] regular game object. So, what we're

[03:03:05] actually going to do on the player

[03:03:06] authoring, we'll create a new field for

[03:03:09] a public game object for the attack

[03:03:13] prefab. And then basically inside our

[03:03:16] baker, we're going to not only are we

[03:03:18] already doing the conversion for the uh

[03:03:22] player game object to the player entity,

[03:03:24] but we're also going to convert this

[03:03:26] attack prefab from a game object to an

[03:03:29] entity. And here we can also do a public

[03:03:31] float for the cooldown time. And so

[03:03:35] let's go ahead and add our components

[03:03:38] here. So we'll say add component to the

[03:03:42] entity. This is going to be the new

[03:03:44] player attack data. And on here, the

[03:03:48] attack prefab will set this first. And

[03:03:51] so we can't just say, you know,

[03:03:53] authoring. Prefab because that attack

[03:03:56] prefab from the authoring is a game

[03:03:59] object. And we need to store this as an

[03:04:01] entity. Now, we can actually convert

[03:04:02] this in the same way that we're

[03:04:04] converting the player entity just by

[03:04:05] doing a get entity. And then for the

[03:04:08] first argument for this, we can pass in

[03:04:10] the game object that we want. So we'll

[03:04:12] say authoring dot attack prefab and then

[03:04:17] the second argument we still need to put

[03:04:19] the transform usage flag. So transform

[03:04:22] usage

[03:04:24] flags dynamic and then that again is

[03:04:26] going to give that attack entity all the

[03:04:29] necessary transform components that we

[03:04:31] want. And then we can just set the

[03:04:33] cooldown time um from the

[03:04:37] authoring cooldown time as we would

[03:04:40] expect. So that's us setting the player

[03:04:42] attack data. And then we also just need

[03:04:44] to do an add component against

[03:04:46] the player cooldown expiration

[03:04:52] timestamp and add that to the entity.

[03:04:54] And um by the way, I should just point

[03:04:56] out a slight difference is um I'm not

[03:05:00] setting this player cool down expiration

[03:05:02] timestamp as an enable component just

[03:05:04] because um I'm just going to check this

[03:05:06] value every time in the system and and

[03:05:08] that's totally fine for these uh players

[03:05:10] like this. So anyways, let's um actually

[03:05:12] go about creating our player attack

[03:05:15] system. So below player input system,

[03:05:18] looks like we can go ahead and do a

[03:05:21] public partial strruct for the player

[03:05:25] attack

[03:05:26] system. And we'll make this an I system

[03:05:30] and do a public void on update ref

[03:05:35] system

[03:05:36] state. And so here we'll define our for

[03:05:40] each var with a little placeholder in

[03:05:42] system

[03:05:44] API.query. So, we're going to want to

[03:05:46] query for a couple things. The first one

[03:05:48] is we'll do a ref rw on that player

[03:05:53] cooldown expiration timestamp. And uh

[03:05:57] that's basically because in this system,

[03:05:59] you know, we're just kind of controlling

[03:06:01] when the player is shooting out attacks

[03:06:03] and it is all going to be self-contained

[03:06:05] in here. So, we can, you know, check the

[03:06:07] time stamp, set the time stamp, do what

[03:06:09] we need to do. Um, next up, we will need

[03:06:11] that player attack data. So we need so

[03:06:14] we know um what prefab to spawn and what

[03:06:18] our cooldown time is. We also need to

[03:06:20] know where to actually spawn the attack

[03:06:23] prefab. And we can just get the uh local

[03:06:26] transform of the player right there. And

[03:06:28] uh that should be all that we need for

[03:06:30] the time being. And so let's go ahead

[03:06:32] and assign some names to these

[03:06:34] variables. Let's say

[03:06:37] expiration timestamp. uh for player

[03:06:40] attack data we call

[03:06:42] this attack data and

[03:06:47] transform for the transform. So first

[03:06:50] thing we can do is say if the

[03:06:52] expiration timestamp

[03:06:56] do value is greater than the

[03:07:01] elapsed time then just go ahead and

[03:07:04] continue. And so that means we do need

[03:07:06] to figure out the elapsed time. System

[03:07:10] API

[03:07:12] time.elapsed time. So next up, we can

[03:07:16] get the spawn position. And we can just

[03:07:19] get this from the

[03:07:22] transform.position. Just like that. And

[03:07:24] then to actually spawn a new instance of

[03:07:27] this, we're going to need an entity

[03:07:29] command buffer. And typically when we're

[03:07:31] spawning things, we want to spawn things

[03:07:34] on the next frame. Um, that's because

[03:07:37] there ends up being a little bit of a

[03:07:38] weird glitch where if we spawn them on

[03:07:40] the current frame, some particular

[03:07:42] things aren't going to be updated. So,

[03:07:44] it it will spawn so it will like display

[03:07:47] the prefab at the origin position even

[03:07:51] though we're like setting the position

[03:07:52] differently. Um, there are multiple ways

[03:07:54] that you can work around this, but what

[03:07:56] I typically like to do is just spawn

[03:07:58] things at the beginning of the next

[03:08:00] frame. And then again, if we have any

[03:08:02] initialization logic, that can all take

[03:08:04] place before any other systems happen.

[03:08:07] So, in order to spawn something on the

[03:08:09] next frame, we can actually use a

[03:08:12] special entity command buffer. Um, and

[03:08:15] this is one of Unity's entity command

[03:08:17] buffers. So, we have to get this by

[03:08:19] grabbing the ECB system. So again we do

[03:08:22] a system

[03:08:23] API.get singleton and this is the begin

[03:08:28] initialization entity command buffer

[03:08:32] systems singleton. And so this is kind

[03:08:35] of the uh entity command buffer that

[03:08:37] will update at the beginning of the next

[03:08:40] frame. And then uh next thing that we

[03:08:42] need to do is actually create the entity

[03:08:44] command buffer by saying ECB equals ECB

[03:08:48] system.create

[03:08:50] create command buffer and again just

[03:08:53] pass in that

[03:08:54] state.world unmanaged and then we could

[03:08:57] also create an onreate method right here

[03:09:00] where we say state.require for update

[03:09:02] begin initialization entity command

[03:09:04] buffer system.s singleton um just to be

[03:09:06] extra sure that that exists in the

[03:09:08] world. Um, so now to spawn the entity,

[03:09:11] let's go back into our for each and we

[03:09:13] can use that entity command buffer that

[03:09:15] we just created and do an instantiate.

[03:09:18] And then in here we can pass in the

[03:09:21] attack prefab that we want. And so we

[03:09:23] can get that from the attack

[03:09:26] data. Prefab. And so that will just kind

[03:09:29] of instantiate it off into the world.

[03:09:31] Now we need to actually set some data on

[03:09:33] it. And so to do that, we can actually

[03:09:35] get that uh new attack here just by

[03:09:39] getting that value off of that ECB

[03:09:41] instantiate. Now, this is actually not

[03:09:43] going to return the actual entity

[03:09:46] because you'll remember that entity

[03:09:47] command buffers is just us recording the

[03:09:51] operations. So at this point, the entity

[03:09:54] doesn't actually exist. We're just

[03:09:56] saying, hey, I want to instantiate this.

[03:09:58] Um, but that instantiation hasn't

[03:09:59] actually happened yet. Now, this value

[03:10:02] that we get returned, this technically

[03:10:03] is an entity type, but it's going to be

[03:10:06] an invalid entity because it uses just

[03:10:09] kind of like a essentially a temporary

[03:10:12] index. So, the entity command buffer can

[03:10:15] still do operations with it, but it's

[03:10:16] not the actual like entity index. Um, so

[03:10:20] that's just, you know, something to keep

[03:10:21] in mind where if you're like storing

[03:10:23] references to entities, you actually

[03:10:25] can't get it from this ECB instantiate.

[03:10:28] But in this case it doesn't matter

[03:10:29] because we just need to set some data on

[03:10:31] it. So we can do an ECB set component

[03:10:36] and in here we pass in that entity which

[03:10:38] is the new attack and then we want to

[03:10:41] set its local transform. So we can

[03:10:44] actually just say local

[03:10:46] transform dot from

[03:10:49] position and then pass in the spawn

[03:10:52] position and that will create a local

[03:10:54] transform component setting it at the

[03:10:56] spawn position setting its uh

[03:10:59] orientation to quturnian.identity and

[03:11:01] its scale to one by one by one. So that

[03:11:04] will um just spawn the entity in the

[03:11:07] world. And then the last thing that we

[03:11:08] want to do is just set the expiration

[03:11:12] timestamp for the player. So we can just

[03:11:14] set this to value rw value is equal to

[03:11:18] uh the current elapsed time which we

[03:11:21] already have. We can just set this equal

[03:11:23] to the current elapsed time which we

[03:11:26] already have and then add in the uh

[03:11:29] cooldown time which we also get from the

[03:11:31] attack data. So this will just

[03:11:33] effectively reset that time stamp. So

[03:11:35] the next time it comes around, then this

[03:11:37] time stamp will be off into the future

[03:11:40] and then you know we'll kind of

[03:11:42] basically skip over that until the

[03:11:45] expiration time stamp is up at which

[03:11:47] time we can spawn a new attack. So back

[03:11:50] over in Unity, we can go to the player

[03:11:52] and then we can assign this attack

[03:11:54] prefab. So, we can just grab the plasma

[03:11:56] blast game object right there. And let's

[03:11:58] set this to a cool down time of 1

[03:12:01] second. So, now let's go ahead and enter

[03:12:06] play mode here. And you can see as we

[03:12:08] move around about every 1 second, it's

[03:12:10] going to spawn one of these plasma

[03:12:13] blasts uh just right where the player

[03:12:14] is. So, let's just go ahead and stick

[03:12:16] with the player attack system for now.

[03:12:18] And what we're going to do is find the

[03:12:20] closest enemy to the player and we're

[03:12:22] just going to be shooting an attack

[03:12:23] directly towards it. So the first thing

[03:12:25] that we need to do is kind of like

[03:12:27] figuring out this sort of like detection

[03:12:29] area of where we want to locate the

[03:12:31] enemies. Um in this case what we can do

[03:12:33] is just we'll take the spawn position

[03:12:35] and then we'll just add an offset in the

[03:12:38] XY axis and we can use that as a sort of

[03:12:40] like detection box that we're locating

[03:12:42] these enemies in. And then once we have

[03:12:45] that whole list of enemies, then we can

[03:12:47] just iterate through that list of

[03:12:48] enemies and figure out which one is the

[03:12:50] closest to the player. So let's go back

[03:12:52] to the player attack data and we're

[03:12:54] going to add an additional field in

[03:12:56] here. And this will be a

[03:12:58] public float three for detection size.

[03:13:03] And so I'm actually just going to in the

[03:13:04] player authoring define a public float

[03:13:08] for detection size. And then I'm just

[03:13:11] going to go down here into the player

[03:13:13] attack data and we'll set

[03:13:16] detection size equal to a new float

[03:13:20] three and we can just pass in our single

[03:13:25] authoring.detection size in there. Um,

[03:13:28] and when we actually author it this way

[03:13:30] where we just pass in a single float for

[03:13:33] this float 3, it will assign X, Y, and Z

[03:13:36] all to that detection size. So then back

[03:13:39] in the uh player attack system here then

[03:13:43] we can figure out the uh var min detect

[03:13:48] position is equal to the spawn position

[03:13:51] minus attack

[03:13:53] data detection size. And then we can do

[03:13:58] the same thing here but define a max

[03:14:01] detect position. And in this case we're

[03:14:04] going to be adding in the attack data

[03:14:06] detection size. So once we have these

[03:14:08] minimum and maximum positions, we're

[03:14:11] going to execute what is known as an

[03:14:13] overlap axis aligned bounding box. So

[03:14:16] this is going to create an axis aligned

[03:14:18] bounding box. And we're just going to

[03:14:20] say, hey, are we overlapping with any

[03:14:24] enemy entities? So the way we actually

[03:14:26] have to do this is create a var aabb

[03:14:29] input. We'll set this equal to a new

[03:14:33] overlap a a bb input. And so we actually

[03:14:36] do need uh

[03:14:38] unity.physics to get this overlap AABB

[03:14:41] input. And from here we can define the

[03:14:45] AABB equal to a new AABB. The min

[03:14:50] position is going to be the min detect

[03:14:54] position. And then for max this will be

[03:14:56] the max detect position. So when we

[03:15:00] define this overlap AABB input just like

[03:15:03] this, this is going to return us every

[03:15:06] single collider within that bound

[03:15:09] bounding box. Now we don't exactly want

[03:15:11] that because we don't want to, you know,

[03:15:13] return colliders for uh the player, any

[03:15:17] of the environment elements, any of the

[03:15:19] player attacks. In this case, all we

[03:15:21] care about is the enemies. So we can

[03:15:24] actually set a uh filter on here and we

[03:15:27] can set this equal to a filter which we

[03:15:30] can actually just store inside this uh

[03:15:32] player attack data. So let's come back

[03:15:34] up to our player attack data and we'll

[03:15:37] add

[03:15:39] a public collision filter and just call

[03:15:43] this collision filter. And then so we

[03:15:45] actually have to set up this collision

[03:15:47] filter inside the authoring. So actually

[03:15:50] before we add this uh player attack

[03:15:52] data, we need to basically figure out

[03:15:55] the layer mask for the enemy. Um now

[03:16:00] it's a little bit weird how we have to

[03:16:01] do this. It's kind of nice when we use

[03:16:03] Unity's custom authoring components

[03:16:05] because we can um assign these values a

[03:16:08] lot easier through like different

[03:16:09] physics templates and things like that.

[03:16:11] Um but you'll remember that you know

[03:16:13] we're using the regular Unity authoring

[03:16:15] and we're using these layers up here. So

[03:16:18] we need to you know figure out the index

[03:16:21] of the layer um but then basically get

[03:16:24] the bit mask of the layer. So we're

[03:16:26] getting a unsigned integer with the bit

[03:16:30] at index 7 set uh for the enemy because

[03:16:34] we only want to collide with enemies. So

[03:16:36] first thing we need to actually get is

[03:16:38] the enemy layer. And so we can do that

[03:16:41] with just the regular Unity layer

[03:16:45] mask name to layer. And in here we can

[03:16:49] pass in the name of the layer which is

[03:16:51] enemy. And so again that just returns us

[03:16:54] if we hover over this this is just an

[03:16:56] integer. So this basically will just

[03:16:57] return us the number seven because enemy

[03:17:00] is layer number seven. Now we need to

[03:17:02] essentially convert this to a bit mask.

[03:17:04] And we can just do this by doing a um

[03:17:07] enemy layer mask equal to um it has to

[03:17:12] be an unsigned integer. So u n t and

[03:17:15] then we can do

[03:17:18] math.p to enemy layer. So this is

[03:17:22] basically just saying you know 2 to the^

[03:17:24] of 7 and then that will return us the

[03:17:28] layer mask where the bit at index 7 is

[03:17:32] set to value one. So then we can now

[03:17:35] actually create our collision filter. So

[03:17:36] we'll say far attack collision filter

[03:17:41] equal to a new collision filter. And

[03:17:45] then here we have to set what layer it

[03:17:48] belongs to and what layer it collides

[03:17:50] with. So for belongs to um this doesn't

[03:17:53] really matter so much. So we can just

[03:17:54] say u

[03:17:58] nt.ax value. So that's basically just

[03:18:01] setting the digit of one for every

[03:18:03] single thing in there. And then we want

[03:18:06] it to collide with. So collides with

[03:18:10] equals the enemy layer mask which we

[03:18:15] have there. So, um, this is kind of one

[03:18:18] of those things where it's it's a little

[03:18:19] bit of a disconnect between, um, Unity's

[03:18:23] kind of traditional layer setup and how

[03:18:25] Unity ECS kind of exposes these layers

[03:18:29] and collisions to us under the hood. Um,

[03:18:31] but it is uh, ultimately not all that

[03:18:33] difficult to deal with. So anyways, once

[03:18:35] we have this attack collision filter,

[03:18:37] let's just go ahead and go back to this

[03:18:39] player attack data and assign our

[03:18:43] collision filter equal to that attack

[03:18:46] collision filter. Yay. Okay, so now we

[03:18:51] have the collision filter. So we can um

[03:18:54] you know come back to here where we're

[03:18:56] setting up this overlap AABB input and

[03:18:59] then assign the filter equal to the

[03:19:02] attack

[03:19:05] data.colision filter and actually I do

[03:19:07] need just need to put a comma after

[03:19:08] that. So now this this overlap aabb

[03:19:11] input defines an axis aligned bounding

[03:19:15] box with a minimum position and a

[03:19:17] maximum position both in world space and

[03:19:20] then the collision filter on that is

[03:19:23] only going to be enemies. So the idea is

[03:19:25] we're going to execute this overlap aabb

[03:19:28] using this as input and it's going to

[03:19:31] return us a collection of all the

[03:19:34] enemies within that bounding box. So to

[03:19:37] actually execute this uh overlap AABB we

[03:19:41] need the physics world singleton. So

[03:19:44] actually I typically do this outside of

[03:19:46] the for each here. So we can say

[03:19:48] var physics world singleton is equal to

[03:19:54] system

[03:19:56] API get singleton and again as you can

[03:19:59] imagine this is the physics world

[03:20:03] singleton and writer of course will want

[03:20:05] us to uh add the require for update to

[03:20:08] ensure that that actually exists here.

[03:20:10] Um, so now we can come back down here

[03:20:13] into our for each after we created the

[03:20:16] AABB input. And so we can just do a

[03:20:19] physics world

[03:20:23] singleton.overlap

[03:20:25] Aabb. And then in here we can pass in

[03:20:28] the input. So a AAB input and then here

[03:20:33] is where we actually will get an output

[03:20:36] of the native list. uh you see this is a

[03:20:39] native list of integers for all hits. So

[03:20:42] we uh first actually need to define that

[03:20:44] here. So we can say var overlap hits

[03:20:49] equals

[03:20:50] new native list of integer. Uh you'll

[03:20:55] see that we do need to require the

[03:20:58] unity.colctions type for the native

[03:21:00] list. We do also have to specify the

[03:21:02] allocator. Um and again we can use that

[03:21:05] nice rewindable allocator which is the

[03:21:08] state.world update allocator. So once we

[03:21:12] have that defined let's go ahead and say

[03:21:14] ref overlap hits. And the idea here is

[03:21:18] that it will run this overlap aabb and

[03:21:22] it will add all of the u enemies to this

[03:21:27] overlap hits native list right here. Now

[03:21:30] you will notice that this native list is

[03:21:32] of type int. So all it really is is an

[03:21:35] index and then also using our physics

[03:21:37] world singleton we can essentially look

[03:21:40] up into the physics world um based off

[03:21:44] of this integer body index to uh get

[03:21:47] some information about it. We can

[03:21:49] actually get a reference to the entity

[03:21:51] or in this case all we really need to

[03:21:52] know is the position of the enemy so we

[03:21:55] can find the closest one. And so

[03:21:57] actually by the way this uh overlap a

[03:22:00] aabb if we uh hover over this here

[03:22:02] you'll see that this returns a bool. So

[03:22:05] what we can do is we can say wrap this

[03:22:07] in an if statement here and we'll say if

[03:22:12] not physics overlap physics world

[03:22:15] singleton overlap aabb meaning that this

[03:22:18] did not actually collide with anything

[03:22:20] um just go ahead and say continue and

[03:22:22] then that way the attack doesn't

[03:22:24] actually get spawned if there's no

[03:22:27] enemies on screen and then next time an

[03:22:30] enemy does come on screen we don't need

[03:22:32] to wait for the cool down to happen

[03:22:34] again cuz we're not like you know

[03:22:35] resetting the cool down here. So this is

[03:22:37] just a way to say you know early out if

[03:22:39] we didn't detect any enemies in this

[03:22:42] radius here. So now again we've kind of

[03:22:44] got the list of integers for all these

[03:22:47] physics bodies. Now let's say hey let's

[03:22:50] go through this list and let's find the

[03:22:52] closest one. So first we can keep a

[03:22:55] reference to the max distance uh sq. I'm

[03:22:59] going to use the square distances on

[03:23:01] here. So, we'll set this to float.max

[03:23:05] value. And then we'll figure out the uh

[03:23:07] closest position, closest

[03:23:10] enemy position, and we'll just set this

[03:23:13] to float 3.0 for default. And then here,

[03:23:17] let's go ahead and iterate through that

[03:23:19] list of overlap hits. So for each var

[03:23:23] overlap hit in overlap hits. So let's

[03:23:28] get the current enemy position that

[03:23:30] we're iterating over. So we can say var

[03:23:33] cur enemy position uh is equal to let's

[03:23:37] go to that

[03:23:39] physics world singleton. And on here we

[03:23:43] can say bodies and then we'll pass in

[03:23:46] the index here. In this case it's just

[03:23:48] the current overlap hit. And then um

[03:23:51] once we have the body we can

[03:23:54] sayworld from body pos to actually get

[03:23:58] the position of that physics body in the

[03:24:01] world. So that's again the current enemy

[03:24:03] position. Now let's actually figure out

[03:24:05] the distance squared between that

[03:24:08] position and the player's position. So

[03:24:11] var distance to

[03:24:14] player sq is equal to math.dist

[03:24:18] distance sq and so here we can just pass

[03:24:22] in the spawn position and the cur enemy

[03:24:26] position and that will get the distance

[03:24:28] squared of that. Now there is a ever so

[03:24:32] slight optimization that we can do

[03:24:33] because we only care about the xy axis.

[03:24:36] We can just compare distance on the xy

[03:24:39] axis. Again very very slight

[03:24:41] optimization there. So anyways, once we

[03:24:43] have the distance to player squared, we

[03:24:45] can compare that distance to player

[03:24:50] SQ with the max

[03:24:54] distance SQ. So if the current distance

[03:24:58] is less than the max distance, we'll uh

[03:25:01] go ahead and set the new max distance

[03:25:03] equal to this current distance to player

[03:25:08] SQ. And we'll also save that position.

[03:25:10] So we'll set that closest enemy position

[03:25:14] equal to that cur enemy position and

[03:25:19] then so we can save that and then so you

[03:25:21] know basically once this for each loop

[03:25:23] is complete we'll know the the position

[03:25:26] of the closest enemy and then we'll fire

[03:25:30] the attack towards that. So how do we

[03:25:32] actually do

[03:25:33] that? Well, it's more math. I'm sure you

[03:25:36] guys love all this math. Um so anyways

[03:25:38] we'll get the vector to the closest

[03:25:40] enemy. So say var vector to closest

[03:25:45] enemy. Um we can get that by the closest

[03:25:49] enemy position minus the spawn position.

[03:25:53] So this effectively gets us the vector

[03:25:55] from the player to that closest enemy.

[03:25:58] And so once we have that vector to

[03:26:00] closest enemy, let's figure out the

[03:26:03] actual angle to closest enemy and we'll

[03:26:08] use the good old

[03:26:10] mathan 2 function. Um, and here we can

[03:26:13] pass in the vector to close closest

[03:26:18] enemy. uh first pass in the Y and then

[03:26:22] vector to closest enemy and then pass in

[03:26:26] the X on that. Um so the AAN 2, this is

[03:26:30] kind of a fairly commonly used method in

[03:26:33] game development where we can pass in XY

[03:26:36] coordinates and it's going to return us

[03:26:38] an angle in radians facing towards that

[03:26:41] vector. Um, definitely don't go

[03:26:43] overboard using this ATN 2 because from

[03:26:45] what I understand it's a fairly

[03:26:47] expensive method to call. Um, but you

[03:26:50] know, if we're only running it once

[03:26:51] every half second or so, when we're

[03:26:54] determining a new enemy to uh fire

[03:26:57] towards and we're just doing it one

[03:26:58] time, this is not really that big of a

[03:27:00] deal. So, when we have our angle to

[03:27:02] closest enemy, now let's actually get

[03:27:04] the spawn orientation. So, to actually

[03:27:06] calculate the spawn orientation, we can

[03:27:09] do a

[03:27:10] quatian. spoiler. Make sure this is the

[03:27:13] lowercase q quaternian cuz that's the um

[03:27:16] nice burst compatible type. And so we're

[03:27:19] going to apply zero in the x and the y

[03:27:23] and then pass in the angle to closest

[03:27:26] enemy right in there for the z. Um and I

[03:27:30] should just again call out that when we

[03:27:32] do this lowercase q

[03:27:34] cotaterturnian. This takes in values in

[03:27:37] radians. And luckily this aan 2 returns

[03:27:40] us this angle in radians. So we don't

[03:27:42] need to do any conversions or anything

[03:27:43] there. Okay. So once we have this spawn

[03:27:46] orientation

[03:27:47] um well we need to apply this to the

[03:27:49] local transform of that new attack. Now

[03:27:52] luckily there's this nice little uh

[03:27:54] method that we can do here. So instead

[03:27:56] of doing um local transform from

[03:27:59] position we can do a from position

[03:28:03] rotation. And then in here we can pass

[03:28:06] in the second argument of the spawn

[03:28:09] orientation. And so with that we should

[03:28:11] now spawn the attack facing towards the

[03:28:14] enemy. So we'll come back over to Unity

[03:28:16] and let's go ahead and increase this

[03:28:17] detection size to a value of 10. So this

[03:28:20] is basically going to make a um you know

[03:28:23] if you think about it it's going to be

[03:28:24] 10x 10 by 10 each direction offset. So

[03:28:28] really the box is going to be 20x 20x 20

[03:28:30] uh that we're going to be looking at.

[03:28:33] And then so let's go ahead and enter

[03:28:34] play mode here. And we can see that it

[03:28:38] is successfully detecting enemies

[03:28:40] because we shouldn't be spawning um the

[03:28:43] actual attack prefabs if the uh enemy is

[03:28:47] not spawning. And it's going to be kind

[03:28:49] of hard to tell because like this is a

[03:28:51] fairly circular attack type, but you can

[03:28:54] see that they're at different kind of

[03:28:55] angles. And so, you know, again, it

[03:28:58] should be facing towards the enemy at

[03:29:00] all times. And let's actually see if we

[03:29:02] go uh get the enemy off screen here. We

[03:29:05] probably should stop. Um and yeah, you

[03:29:07] see that we do stop spawning those uh uh

[03:29:11] little attack prefabs. And then once the

[03:29:13] enemy is back on screen, we'll start

[03:29:15] spawning them again. So great.

[03:29:16] Everything is working good so far. So

[03:29:18] now that we actually are spawning these

[03:29:21] plasma blasts in the world facing

[03:29:24] towards the enemy, let's go ahead and

[03:29:26] actually move them towards the enemy.

[03:29:27] Now, um, this is actually going to be

[03:29:29] quite easy because we just we're already

[03:29:30] have the orientation pointed towards the

[03:29:33] enemy. Now, we just need to move them

[03:29:34] straight forward. Okay. So, I'm going to

[03:29:36] go ahead and create a new C script here.

[03:29:39] This is going to be a class. And I'll

[03:29:41] call this the plasma blast authoring

[03:29:45] because this is going to uh apply to our

[03:29:47] plasma blast. And we'll go ahead and add

[03:29:49] in the name spaces for Unity engine and

[03:29:55] Unity. keys and we'll make this plasma

[03:29:58] blast authoring a mono behavior. So now

[03:30:02] we're going to define some data. So this

[03:30:03] will be a public strruct for the plasma

[03:30:08] blast

[03:30:10] data which is going to be an I component

[03:30:13] data and we'll have public float fields

[03:30:16] for the move speed and a public int

[03:30:20] field for the attack damage. So, I'll go

[03:30:24] ahead and create a private class Baker

[03:30:30] Baker plasma

[03:30:33] blast authoring. And actually, we'll go

[03:30:36] ahead and add some public fields in here

[03:30:39] for the move speed and attack damage.

[03:30:42] And let's do our good old entity

[03:30:43] conversion by doing the get entity,

[03:30:47] passing in the

[03:30:48] transform usage flags.

[03:30:52] dynamic and we can go ahead and add

[03:30:55] component to that entity which is a new

[03:30:59] plasma blast data. And we'll define the

[03:31:04] move speed equal to authoring domove

[03:31:08] speed and

[03:31:10] attack damage for authoring dot attack

[03:31:16] damage. So this adds kind of all the

[03:31:18] necessary components for moving and

[03:31:20] damaging. And so let's actually go ahead

[03:31:22] and create a system. And so this will be

[03:31:24] under here. And this will be a public

[03:31:28] partial strruct for the move plasma

[03:31:33] blast system. And this is going to be an

[03:31:36] I system. And we'll do our public void

[03:31:39] on update in here. Ref

[03:31:44] system state. And we can just do a

[03:31:47] regular for each here. And we'll do a

[03:31:50] little var placeholder

[03:31:52] in system

[03:31:56] API query. Um, in this case, we want to

[03:31:59] move our plasma blast. So, of course,

[03:32:01] we're going to be writing to a

[03:32:03] component, and we're going to be moving

[03:32:04] it by updating the local transform. Um,

[03:32:09] now go ahead and import that. Now, it's

[03:32:12] totally okay in this case to update the

[03:32:14] local transform for these plasma blasts.

[03:32:17] So, these take place in the physics

[03:32:18] simulation, but they're triggers. So, it

[03:32:21] doesn't matter like they're not going to

[03:32:22] hit a wall and they should stop at the

[03:32:24] wall. So, you know, we're totally good

[03:32:26] with updating the local transform rather

[03:32:29] than updating the velocity component.

[03:32:31] And then we also do need to read from

[03:32:34] that uh

[03:32:35] plasma blast data here. And that is all

[03:32:39] we need there. So let's go ahead and

[03:32:42] give some names here for the transform

[03:32:46] and just call that data. That's totally

[03:32:48] fine. And it's going to be a very very

[03:32:50] simple one. Uh we of course do need a

[03:32:53] delta time from system API

[03:32:57] time. Time and we'll set the

[03:33:04] transform.rw.position. And remember,

[03:33:06] we're going to be doing a plus equals

[03:33:07] here because we're going to take its

[03:33:09] current position and increment it by

[03:33:11] some amount. And um what I've actually

[03:33:13] found is the

[03:33:15] um proper thing we need to do to

[03:33:18] essentially get it to move towards the

[03:33:20] character is we can do a

[03:33:21] transform value

[03:33:25] oight. And this actually, interestingly

[03:33:28] enough, will move it towards the player.

[03:33:30] is just kind of like how the orientation

[03:33:32] works of it if it's spawning in a

[03:33:34] two-dimensional plane and all that. Um,

[03:33:36] and then we can multiply this by data

[03:33:39] move speed and of course multiply that

[03:33:41] by delta time. Okay, so we willh come

[03:33:44] back over to Unity and select our plasma

[03:33:46] blast prefab and we'll add a new

[03:33:49] component which is going to be this uh

[03:33:51] plasma blast authoring. And here we can

[03:33:54] set a move speed. Let's say a move speed

[03:33:56] of 7.5. Nice and quick. And we'll have

[03:33:59] this deal, say, four points of damage.

[03:34:01] So, actually take a couple of hits to

[03:34:03] destroy these enemies here. And um so

[03:34:06] yeah, we can go ahead and enter play

[03:34:08] mode here. And you can see, boom, we're

[03:34:12] already moving immediately towards these

[03:34:15] um enemies. So, no matter what uh where

[03:34:18] they're at, it's always going to find

[03:34:20] the closest one and just shoot that

[03:34:22] plasma blast right towards it. Now,

[03:34:25] we're not dealing any damage to these.

[03:34:27] And again, we kind of have to create a

[03:34:29] little trigger system for that to

[03:34:31] actually deal the damage. So, let's go

[03:34:32] ahead and take care of that now. So, to

[03:34:34] actually deal the damage, I'm going to

[03:34:35] create a new system here. And this is

[03:34:38] going to be a public partial strct for

[03:34:43] the plasma

[03:34:45] blast attack system, which is another I

[03:34:49] system. And uh just a heads up, we are

[03:34:52] going to be scheduling another job. So

[03:34:55] we'll call this the public strruct

[03:34:58] plasma

[03:35:00] blast attack job which is an I trigger

[03:35:05] events

[03:35:07] job and so this I trigger events job

[03:35:10] this comes from

[03:35:11] unity.physics and we can imp implement

[03:35:14] the uh member here. So again this is um

[03:35:17] going to be very similar to a collision

[03:35:19] events job but it takes in this trigger

[03:35:21] event rather than collision event. So

[03:35:23] let's get to uh going on our job strruct

[03:35:26] here and then we can worry about

[03:35:28] scheduling it afterwards. So again we

[03:35:30] need to figure out which entity is the

[03:35:34] uh plasma blast entity and which entity

[03:35:38] is the enemy entity. And so you'll

[03:35:41] remember that in order to get these

[03:35:43] inside of a nice little uh if else here

[03:35:46] is we have to get a bunch of component

[03:35:48] lookups. And so we'll do a couple of

[03:35:50] these readonly component lookups. um do

[03:35:54] need to make sure that we implement the

[03:35:56] uh uh Unity collections readonly type

[03:35:59] here. And we're going to do a public

[03:36:02] component lookup. And let's get one for

[03:36:05] the plasma blast. We of course have the

[03:36:08] plasma blast data on there. And so we

[03:36:11] can call this the

[03:36:13] plasma

[03:36:15] blast lookup. And let's do one for the

[03:36:19] enemy as well. And this component lookup

[03:36:21] can just look up the enemy tag. And this

[03:36:25] can be our enemy lookup. And of course,

[03:36:27] we do want to apply points of damage to

[03:36:31] the enemy. So, let's do another public

[03:36:34] um without the readonly attribute for

[03:36:37] the buffer lookup against the damage

[03:36:41] this frame. Yeah, damage this frame. And

[03:36:44] we call this the damage buffer lookup.

[03:36:49] And again, this damage this frame is

[03:36:51] applied to all characters. So the enemy

[03:36:53] already has this damage this frame. So

[03:36:55] anyways, let's figure out which which is

[03:36:57] which. And so let's go ahead and say if

[03:37:00] plasma blast lookup has component

[03:37:05] passing in trigger event entity a very

[03:37:10] similar to collision event. Um and the

[03:37:14] enemy

[03:37:16] lookup has component trigger event

[03:37:21] entity

[03:37:23] B. Then in that case we know the

[03:37:26] plasma blast entity is the trigger event

[03:37:33] entity A and the enemy entity is the

[03:37:38] trigger event entity B. So let's go

[03:37:42] ahead and copy paste to this and then

[03:37:46] we'll change this to plasma blast is

[03:37:49] entity B and enemy is entity A and we'll

[03:37:56] change this to B and change this to A

[03:38:00] and then again say

[03:38:01] else return meaning this trigger event

[03:38:04] is between something other than exactly

[03:38:07] one plasma blast and exactly one entity.

[03:38:09] And then here, all we need to really do

[03:38:11] is just apply points of damage to that

[03:38:14] enemy. So, let's go ahead and say uh var

[03:38:18] attack damage is equal to the plasma

[03:38:23] blast lookup at the

[03:38:27] plasma blast entity attack damage. So,

[03:38:33] that's you know how many points of

[03:38:34] damage that we're actually going to

[03:38:35] deal. Again, just looking at that

[03:38:37] through this plasma blast lookup, you

[03:38:39] know, which gives us the plasma blast

[03:38:41] data for that plasma blast. So, we know,

[03:38:43] you know, how much damage to actually

[03:38:45] apply. Then we just also need the enemy

[03:38:49] damage buffer. And we can get that by

[03:38:52] the damage buffer lookup at enemy entity

[03:38:58] and the

[03:38:59] enemy damage buffer. We can just

[03:39:03] add a

[03:39:05] new damage this

[03:39:09] frame passing in the value equal to

[03:39:12] attack

[03:39:13] damage. So that again will kind of add a

[03:39:17] point of damage to the player. So uh

[03:39:20] let's go ahead and schedule this job. So

[03:39:22] back in our plasma oplast attack system

[03:39:25] can do our good old public void on

[03:39:29] update ref system

[03:39:32] state. So say

[03:39:34] var attack job is equal to a new plasma

[03:39:39] blast attack job. In here we can uh get

[03:39:43] all of our buffer lookups. So we'll say

[03:39:46] plasma

[03:39:48] blast lookup is equal to system

[03:39:52] API.get component lookup and we want to

[03:39:56] look up the plasma blast data. Next for

[03:40:00] the enemy lookup, we'll get the uh

[03:40:03] system

[03:40:04] api.get component lookup against the

[03:40:07] enemy tag. By the way, we do need to

[03:40:10] pass in true for is readon on both of

[03:40:14] these. For the damage buffer lookup, we

[03:40:18] can get this with system

[03:40:21] API.getbuffer lookup on the damage this

[03:40:26] frame like that. And of course, once we

[03:40:29] have our attack job, we can schedule it

[03:40:31] by doing an attack

[03:40:33] job. schedule. Um, however, we do need

[03:40:37] to pass in that simulation singleton.

[03:40:40] So, we can get that by saying var

[03:40:43] simulation singleton is equal to system

[03:40:47] API.get

[03:40:50] singleton simulation

[03:40:54] singleton and in here we can pass in the

[03:40:58] simulation singleton. And I do also like

[03:41:01] to pass in the state.ependent dependent

[03:41:05] C and assign this back to the

[03:41:10] state.dependent C as well. And let's

[03:41:13] just go ahead and require for update

[03:41:15] that simulation singleton. So that's us

[03:41:17] scheduling the job. And then you know

[03:41:20] again because this is a trigger events

[03:41:22] job, we do need to update this at the

[03:41:25] appropriate place in the frame um to

[03:41:28] properly detect collision events for the

[03:41:29] frame. So say update in group type

[03:41:36] of physics system group and we do need

[03:41:39] to import that uh type there. So we'll

[03:41:42] say update after type of physics

[03:41:47] simulation group and then

[03:41:50] finally update

[03:41:53] before type of after physics system

[03:41:59] group. Boom. Okay. So, now we can try it

[03:42:02] out. Um, there's going to be one more

[03:42:04] thing that we need to do to finalize

[03:42:06] this. So, we're going to go ahead and

[03:42:08] enter play mode. And this actually going

[03:42:09] to be incorrect. I think it's going to

[03:42:11] be like kind of, you know, blinking or

[03:42:13] miss it. Um, the player or the enemy

[03:42:15] should get vaporized right away. Um,

[03:42:17] even though our attack damage is four,

[03:42:18] and the alien has a hit points value of

[03:42:22] 10. So, in theory, it should take three

[03:42:24] hits. But, uh, actually, I think it's

[03:42:26] going to take just one. So, let's go

[03:42:28] ahead and enter play mode and see what

[03:42:29] happens. So, boom. And then boom, it

[03:42:32] just vaporized on the first one if you

[03:42:33] saw it. So, I'll go ahead and play it

[03:42:35] again um just so you don't miss it. And

[03:42:37] he's gone. So, anyways, good that

[03:42:39] we're applying damage. Bad that we're

[03:42:42] seemingly applying too much damage. So,

[03:42:44] do you know why that is? Well, that is

[03:42:46] because this uh plasma blast, you know,

[03:42:49] we don't have any type of like cool down

[03:42:51] on this plasma blast. Um, and it would

[03:42:53] be kind of weird to apply cooldown to

[03:42:55] like an attack like in world like that.

[03:42:58] Um, so what actually we're going to do

[03:43:01] is instead we're just going to go ahead

[03:43:03] and just destroy the plasma blast as

[03:43:05] soon as it hits the enemy entity. Um,

[03:43:08] because what's happening right now is

[03:43:10] every frame that that plasma blast is

[03:43:14] colliding with that enemy, it's applying

[03:43:18] a new point of damage. So, it really

[03:43:20] just takes three frames of overlap,

[03:43:22] which happens very quickly, to apply all

[03:43:25] the damage necessary to destroy that

[03:43:27] enemy. So, um, all we need to do is

[03:43:30] actually pretty easy. We already have

[03:43:31] all the, uh, kind of infrastructure in

[03:43:33] place for this. So, we'll go back to the

[03:43:35] plasma blast and we'll add the

[03:43:39] component of the destroy entity flag

[03:43:45] uh, to this entity. And also make sure

[03:43:47] we say set

[03:43:49] component

[03:43:52] enabled destroy entity flag on the

[03:43:57] entity to false. So it's set to uh false

[03:44:01] by default. And then basically when we

[03:44:03] detect these uh trigger events we can go

[03:44:06] ahead and enable that um appropriate

[03:44:09] flag. So we'll do another public

[03:44:13] component lookup and this one is going

[03:44:15] to be the destroy entity flag. So we

[03:44:19] call this the destroy entity lookup. And

[03:44:23] then so down at the bottom after we're

[03:44:25] applying our damage, let's go ahead to

[03:44:27] that uh destroy entity lookup and we can

[03:44:32] do a set component enabled on the plasma

[03:44:37] blast entity. and set that to true. And

[03:44:41] by the way, why didn't anyone tell me

[03:44:43] I'm using capitals for these? I I don't

[03:44:45] usually use capitals for those. So, we

[03:44:47] can set those to lowercase for plasma

[03:44:50] blast and enemy entity. Um, so yeah,

[03:44:53] anyways, we just need to go ahead and

[03:44:54] assign this component lookup. So, back

[03:44:57] in the system here, and by the way, this

[03:44:59] cannot be a read only because we are,

[03:45:01] you know, actually changing the uh

[03:45:03] enabled state of it. uh destroy entity

[03:45:07] lookup is equal to system

[03:45:10] API.get component lookup destroy entity

[03:45:14] flag. Okay, so now when we enter play

[03:45:17] mode, we should fire three shots until

[03:45:20] the entity finally gets destroyed. So

[03:45:23] that is uh exactly what we want because

[03:45:25] the plasma blast is just getting

[03:45:27] destroyed right when it collides with

[03:45:29] the entity. Um, so we're going to move

[03:45:32] into spawning enemies next. But I do

[03:45:35] just want to provide a little bit of a

[03:45:37] challenge um to you all. And that would

[03:45:40] be to also see if you can make those

[03:45:42] plasma blasts get destroyed after a

[03:45:45] certain number of seconds. Now, I will

[03:45:47] kind of include this in sort of the

[03:45:49] final code if you want to look up the

[03:45:50] solution to that, but I think you should

[03:45:52] have all the information that you need

[03:45:55] in order to create a plasma blast that

[03:45:58] will destroy itself after some certain

[03:46:00] duration. Hey, just wanted to cut in

[03:46:02] here and say if you thought that attack

[03:46:03] is cool, then you might be interested in

[03:46:05] checking out the full project files over

[03:46:07] on the Unity Asset Store where I have a

[03:46:09] total of 12 different unique attacks,

[03:46:12] each with their own level up progression

[03:46:14] system associated with them. And all of

[03:46:16] this is fully documented in the dots

[03:46:19] survivors project files that you can

[03:46:21] purchase on that Unity asset store. Now,

[03:46:23] personally, I think this is one of the

[03:46:24] most interesting aspects of the game's

[03:46:26] architecture, but maybe you are

[03:46:27] interested in some other things yourself

[03:46:29] like character stat modifications or

[03:46:32] dealing knockback or how to do more

[03:46:34] complex enemy spawn waves. And luckily,

[03:46:37] I go into all that stuff and so much

[03:46:38] more in my project over on the Unity

[03:46:40] Asset Store called Dot Survivors. So,

[03:46:42] hope you check it out and learn a lot

[03:46:44] from it. Okay, so next up, we're going

[03:46:46] to basically turn this enemy into a

[03:46:48] prefab. And then we're going to create a

[03:46:50] spawner entity that can spawn a bunch of

[03:46:53] these enemies offcreen. And then they're

[03:46:56] all going to, you know, swarm the

[03:46:57] player. And it's going to be a grand old

[03:46:59] time. So, let's go ahead and start off

[03:47:01] by doing uh just that. We'll grab this

[03:47:03] alien guy, bring him into the prefabs

[03:47:05] folder, and delete him out of the main

[03:47:07] scene. And I'll go ahead and rightclick

[03:47:10] on this uh moon entity scene. We'll

[03:47:12] create an empty game object. And this is

[03:47:15] going to be our enemy spawner. And so

[03:47:18] we'll just leave this as empty for now

[03:47:20] cuz it's really just going to be a data

[03:47:21] only entity. And if we come back over to

[03:47:23] here, we can create a new class. And

[03:47:26] this is going to be the enemy spawner

[03:47:29] authoring. So we do of course need the

[03:47:33] Unity

[03:47:34] engine and

[03:47:36] Unity. entities namespaces. So for this

[03:47:41] we just need two data components. So one

[03:47:43] is going to be a public strruct for the

[03:47:46] enemy spawn data and this is going to be

[03:47:49] kind of the persistent data that's going

[03:47:51] to be relatively unchanging throughout

[03:47:53] the duration of the application. Um so

[03:47:56] first we'll need a public entity for the

[03:47:59] enemy prefab because we're just going to

[03:48:01] be spawning a bunch of these enemies.

[03:48:03] Um, let's also go ahead and define a

[03:48:05] public float for the spawn interval. Um,

[03:48:10] so we want to, you know, spawn one of

[03:48:12] these entities on a specific cadence.

[03:48:15] And let's also go ahead and create a

[03:48:17] public float for the spawn distance. Uh

[03:48:21] the idea here is that we're going to

[03:48:23] basically just kind of like determine a

[03:48:26] random angle from the player and then

[03:48:28] this spawn distance is just going to be

[03:48:30] enough to spawn that entity um offscreen

[03:48:34] at a certain distance away. The other

[03:48:36] data component that we're going to need

[03:48:37] is the public strruct here for the enemy

[03:48:41] spawn

[03:48:43] state. And this is an I component data.

[03:48:46] And again, here's kind of the separation

[03:48:48] that I have between fixed data and data

[03:48:52] that's going to be changing throughout

[03:48:53] the duration of the application. So here

[03:48:56] we'll define a public float for the

[03:48:59] spawn timer. So this spawn timer is

[03:49:03] going to be, you know, continually

[03:49:04] changing and so we know when we actually

[03:49:06] want to spawn on on a specific interval.

[03:49:09] And also we're going to um have a public

[03:49:12] random for we'll just call it random. Um

[03:49:16] we do have a conflict on random because

[03:49:18] we have two randoms. Um there is the

[03:49:22] Unity engine random. However, we

[03:49:24] actually want to say using random equals

[03:49:28] unity.

[03:49:31] Mathmatics.random. So this is a uh fully

[03:49:33] burst compatible random type. And um so

[03:49:36] yeah, let's just go ahead and assign

[03:49:38] this data on our authoring here. So

[03:49:40] we'll of course make this a mono

[03:49:43] behavior. And let's have a couple public

[03:49:45] fields for the game object for the enemy

[03:49:50] prefab. Public float for the spawn

[03:49:55] interval. Public float for the spawn

[03:49:59] distance. And let's also go ahead and

[03:50:02] define a public unsigned integer for the

[03:50:07] random seed. Now let's actually go ahead

[03:50:09] and bake this data here which we can do

[03:50:12] a private class baker baker enemy

[03:50:17] spawner authoring and implement that

[03:50:20] baker there. And of course we need to

[03:50:23] get a reference to the entity and we can

[03:50:25] do our good old get entity. And in here

[03:50:28] we can actually use the transform usage

[03:50:32] flags.none because this is a data only

[03:50:34] entity. This spawner doesn't need to

[03:50:36] live anywhere in the world. We're just

[03:50:38] kind of defining data. And then here we

[03:50:40] can add whatever components we need to

[03:50:42] it. So we'll say add component to the

[03:50:46] entity. It's going to be a new enemy

[03:50:50] spawn data. And in here we can go ahead

[03:50:53] and define uh the enemy prefab. Set this

[03:50:57] equal to get entity um authoring enemy

[03:51:03] prefab. Again, this is us converting

[03:51:05] that game object prefab into an entity.

[03:51:07] And then we need to define the transform

[03:51:09] usage flags for that enemy prefab. So,

[03:51:13] transform usage

[03:51:16] flags.damic. Next up, we'll define our

[03:51:19] spawn interval from the

[03:51:22] authoring.spawn interval. And our

[03:51:25] spawn distance we'll get from the

[03:51:31] authoring.spawn distance. We'll also

[03:51:33] need to add component to this entity of

[03:51:37] a new enemy spawn state. So this is kind

[03:51:41] of the uh more or less changing data. Um

[03:51:44] our spawn timer can just be set to zero

[03:51:47] by default. Um but random we do want to

[03:51:50] initialize uh by using the

[03:51:53] random.create from index. And here we

[03:51:56] can just pass in that seed that we are

[03:51:58] storing in the

[03:51:59] authoring random seed. And that is all

[03:52:03] the data that we need to bake. Um, so it

[03:52:05] should have everything that we need. And

[03:52:07] before we forget, we'll just come back

[03:52:08] to Unity and we'll just add the enemy

[03:52:11] spawner authoring. And so here we can

[03:52:13] assign the alien prefab as the enemy

[03:52:16] prefab. For spawn interval, let's just

[03:52:18] do

[03:52:19] 0 5 seconds for now. I'm going to do a

[03:52:22] spawn distance of 25, which should put

[03:52:24] it off of the screen. And a nice uh

[03:52:27] elite random seed there. So anyways,

[03:52:29] let's actually go into, you know,

[03:52:30] spawning these enemies. So outside of

[03:52:34] the authoring here, we'll create a

[03:52:37] public partial strct for the enemy spawn

[03:52:44] system, which is an I system. And so

[03:52:47] we'll do a public void on update ref

[03:52:51] system

[03:52:53] state. So go ahead and create a for each

[03:52:57] here. and do a little placeholder in

[03:53:00] system

[03:53:01] API.query. And let's go ahead and look

[03:53:04] for we do need uh to write to that uh

[03:53:07] enemy spawn state. So we'll say ref rw

[03:53:11] enemy spawn state and we'll also need

[03:53:15] that enemy spawn data. And once we have

[03:53:18] those things, we can just say spawn

[03:53:21] state and spawn data. So the first thing

[03:53:25] that we're going to do is check to see

[03:53:26] if it's time to spawn. So what we'll do

[03:53:29] is we'll say spawn state value

[03:53:32] rw spawn timer and we'll just subtract

[03:53:36] this by delta time each frame. So of

[03:53:39] course we'll need delta time out here.

[03:53:42] So we can say if spawn state do

[03:53:46] value spawn timer is greater than zero

[03:53:50] meaning there's still time on the

[03:53:52] timer go ahead and continue and then you

[03:53:56] know come back next frame and then once

[03:53:58] it's finally below zero let's just go

[03:54:00] ahead and reset this spawn state timer

[03:54:03] and we can get this from the spawn

[03:54:07] data spawn interval. So anyways, once

[03:54:11] we're past here, we know it's time to

[03:54:13] spawn. And how do we typically spawn?

[03:54:15] Well, we like to use good old entity

[03:54:17] command buffers. So first we'll do an

[03:54:20] ECB system. It's equal to system

[03:54:24] API.get singleton begin

[03:54:29] initial

[03:54:31] initialization entity command buffer

[03:54:37] system. Again, insane name, but again,

[03:54:40] we want it to spawn at the beginning of

[03:54:42] the frame. And we'll add that require

[03:54:44] for update. And let's get a reference to

[03:54:47] our ECB by doing an

[03:54:49] ECB

[03:54:52] system.create

[03:54:54] command buffer

[03:54:57] state.world

[03:54:58] unmanaged. So once we have our entity

[03:55:01] command buffer, let's go ahead and spawn

[03:55:03] a new enemy. So we can say

[03:55:05] ECB.instantiate

[03:55:07] instantiate passing in the spawn

[03:55:10] data enemy prefab. And so once we have

[03:55:14] that new enemy, we need to figure out a

[03:55:16] position for it. Um, again, the way that

[03:55:19] I like to do this is just kind of like

[03:55:21] figure out a random angle. And then once

[03:55:24] we have that random angle, then we'll

[03:55:25] just, you know, take a distance of it.

[03:55:27] So it's like kind of like a polar

[03:55:28] coordinate if you're familiar. So we'll

[03:55:30] say var spawn angle is equal to and of

[03:55:34] course we want a random angle each time.

[03:55:37] So again we're storing random in this

[03:55:39] spawn state. So we can say spawn state

[03:55:42] dov value rw. Uh by the way we do have

[03:55:45] to actually write um back to the random

[03:55:48] component otherwise the random seed

[03:55:51] doesn't get updated and we just generate

[03:55:53] the same random number each time which

[03:55:56] is not very random. Um so anyways with

[03:55:58] that random we can say next float and

[03:56:01] then here we can define the minimum and

[03:56:04] maximum values. So we'll say between

[03:56:07] zero and math towo um by the way to if

[03:56:12] you're not familiar is 2 pi um so

[03:56:14] basically we're finding an a radian

[03:56:16] angle between 0 and 2 pi which is 2 pi

[03:56:20] radians is equal to

[03:56:22] 360°. So we're just again finding a

[03:56:24] random angle on a circle and then we can

[03:56:27] create the spawn point and by doing a

[03:56:31] new float three and for x we're going to

[03:56:34] set this equal to math do sign of the

[03:56:39] spawn angle y is equal to math cosine of

[03:56:44] the spawn

[03:56:46] angle and then z is equal to zero. you

[03:56:50] guys are really getting uh your math

[03:56:52] lesson here today. So once we kind of

[03:56:54] have this spawn point, this is really

[03:56:55] just going to be a spawn point on a unit

[03:56:57] circle. Um so now we just need to

[03:56:59] multiply this by distance. So we'll say

[03:57:02] spawn point

[03:57:04] uh times equals spawn

[03:57:08] data spawn distance. So that's again

[03:57:12] kind of that uh distance offscreen. Now,

[03:57:15] this isn't going to be exactly correct

[03:57:17] because this spawn point is kind of

[03:57:19] origin at zero. So, we actually want to

[03:57:23] offset this by the player's position.

[03:57:25] So, we can say spawn point plus equals

[03:57:29] player position. And we don't have

[03:57:31] player position. So, let's go ahead and

[03:57:33] get that. Um, you may remember we have

[03:57:35] done this before. We first need to get

[03:57:37] the player entity with system

[03:57:40] API. get

[03:57:43] singleton entity player tag and then we

[03:57:47] can get the player position from system

[03:57:51] API.get

[03:57:53] component get the uh local transform off

[03:57:57] the player

[03:57:59] entity and do need to uh import the

[03:58:02] local transform and actually we're just

[03:58:04] going to get just the

[03:58:06] uh float 3 position on there. So then we

[03:58:09] can again add in the position to figure

[03:58:11] out the spawn point. And then now we

[03:58:13] just need to apply that to the new

[03:58:15] enemy. So here we can just say ecb set

[03:58:19] component on the new enemy local

[03:58:25] transform from position and then just go

[03:58:28] ahead and pass in that spawn point right

[03:58:30] there. And then uh just a little bit of

[03:58:32] cleanup, you know, we'll make sure that

[03:58:34] we add in the require for update of

[03:58:36] player so that um you know we only run

[03:58:38] the system when the player actually

[03:58:40] exists and go ahead and add the burst

[03:58:44] compile attribute to the on update

[03:58:47] there. So now this should spawn these

[03:58:49] guys um offscreen and all start moving

[03:58:51] towards the player. So we will go ahead

[03:58:53] and enter play mode here. And after just

[03:58:56] a moment you'll see that these enemies

[03:58:57] start coming from offscreen from all

[03:58:59] different angles. And again, our attack

[03:59:01] will always shoot towards the closest

[03:59:04] enemy to us at any given time. And um so

[03:59:07] yeah, that's uh exactly what we want to

[03:59:09] see. We have all these little aliens

[03:59:11] chasing us and it's a ton of fun. This

[03:59:13] is what a survivors game is all about.

[03:59:15] So actually, let's have when these

[03:59:17] enemies get destroyed, we'll have them

[03:59:19] drop a little experience gem for the

[03:59:21] player to pick up and then we can

[03:59:22] increment our little uh gem counter

[03:59:24] here. And so anyways, we'll come back to

[03:59:26] the enemy authoring and we'll create a

[03:59:28] public strruct for the gem

[03:59:32] prefab. And this of course will be an I

[03:59:35] component data. And on here, we're going

[03:59:37] to store a public um

[03:59:40] entity value here. And then in the enemy

[03:59:44] authoring, we'll do a public game object

[03:59:48] for the gem prefab. And then we'll just

[03:59:51] make sure to add that component here.

[03:59:54] Add component to the entity. New gem

[03:59:59] prefab. And for the value this is equal

[04:00:01] to get

[04:00:04] entity

[04:00:06] authoring

[04:00:08] gem prefab passing in a transform usage

[04:00:16] flags dynamic because that's what we

[04:00:18] want for these guys. So here we're just

[04:00:20] assigning the gem prefab and then we can

[04:00:23] come back to our nifty destroy entity

[04:00:25] system and then add a nice new little

[04:00:28] thing in here. So what we can do is

[04:00:30] we'll say if system API has component

[04:00:37] the gem

[04:00:39] prefab on that entity then we want to

[04:00:42] spawn a new one. Now, we can spawn them

[04:00:45] with the um end simulation entity

[04:00:48] command buffer system, but it's not

[04:00:50] really the best idea because again, we

[04:00:51] want to spawn these at the beginning of

[04:00:53] the frame because there's some weird

[04:00:54] kind of transform issues. Um, so I just

[04:00:57] go ahead and create a new begin ECB

[04:01:01] system. And this is system API.get

[04:01:06] Get

[04:01:08] singleton

[04:01:11] begin

[04:01:13] initialization entity command buffer

[04:01:17] system

[04:01:19] singleton and then the begin ECB is um

[04:01:24] we get the begin

[04:01:27] ECB system dot create command buffer

[04:01:34] stateworld unman

[04:01:36] managed and add the require for update.

[04:01:39] So it is uh up here. So anyways uh with

[04:01:42] this let's actually get a reference to

[04:01:44] the gem prefab itself. So we can do a

[04:01:47] system API.get get component of the gem

[04:01:53] prefab off that entity and use the begin

[04:01:59] ECB

[04:02:01] instant

[04:02:03] instantiate that uh gem prefab there and

[04:02:07] so actually I need to do a uh value on

[04:02:10] here so we get the actual underlying

[04:02:12] type. Um, so anyways, we can say var new

[04:02:16] gem equals that. Then we do need to

[04:02:18] figure out where we want to position

[04:02:20] this in the world. And so we can say var

[04:02:23] spawn position is equal to system

[04:02:29] API.get component. Uh we do want the

[04:02:32] local to world because we want the world

[04:02:35] position of that uh current entity that

[04:02:38] we're destroying. And then lastly, we

[04:02:40] can do on our begin

[04:02:44] ECB. Set component

[04:02:47] um on that new gem, set this equal to a

[04:02:51] local

[04:02:53] transform from position passing in that

[04:02:56] spawn position. Okay, so let's go ahead

[04:02:59] and create a new gem prefab. So I'll go

[04:03:02] ahead and uh do our good old create 3D

[04:03:06] quad here. And we can call this the gem.

[04:03:09] We'll go ahead and remove that collider.

[04:03:11] And on the mesh renderer, we can set the

[04:03:14] material to be the gem material right

[04:03:17] there. And uh let's go ahead and set a

[04:03:21] sphere collider on that right there. Um

[04:03:24] and we do eventually want this to be a

[04:03:27] trigger. So we can set that as a

[04:03:29] trigger. And then uh lastly, we just

[04:03:32] need to go to the layer and change that

[04:03:33] to be a gem layer. So, let's save this

[04:03:36] gem as a prefab here. Delete it out of

[04:03:39] the scene. Come back to the alien. And

[04:03:42] then on the enemy authoring, just drag

[04:03:45] that gem prefab right in there. So, now

[04:03:48] we enter play mode, the enemies should

[04:03:49] start uh coming towards us and we can

[04:03:52] shoot at them. And after a couple hits,

[04:03:54] they will get destroyed and we'll spawn

[04:03:57] a gem in their place. Now, the last

[04:03:59] thing that we kind of want to do here is

[04:04:01] pick up this gem and uh kind of add this

[04:04:04] to our little uh experience points right

[04:04:06] here. So, as you can imagine, we're

[04:04:07] going to be doing a little trigger setup

[04:04:10] on this gem here. So, we'll create one

[04:04:12] last little script here and uh we'll go

[04:04:15] ahead and do a class and this can be the

[04:04:19] gem authoring. And of course, we do need

[04:04:22] the Unity engine

[04:04:25] and

[04:04:27] Unity entities name spaces. And uh let's

[04:04:31] go ahead and create a public strruct for

[04:04:34] some gem data. And let's go ahead and

[04:04:37] create a public strruct for the gem tag,

[04:04:41] which is an I component data. Don't need

[04:04:43] any data on it. Um, but we do still just

[04:04:46] need to make this a mono behavior. And

[04:04:51] so we can actually create a private

[04:04:54] class

[04:04:55] baker, which is a baker

[04:04:59] gem authoring. And then we'll say get

[04:05:03] this entity with some transform usage

[04:05:07] flags.

[04:05:09] damic and add

[04:05:13] component gem tag to that entity. Boom.

[04:05:17] Now, also just kind of thinking ahead a

[04:05:19] little bit. Uh we do want to eventually

[04:05:21] destroy this entity after we pick it up.

[04:05:23] So we can also add good old destroy

[04:05:26] entity flag

[04:05:29] uh to that entity and make sure we do a

[04:05:32] set component enabled on that destroy

[04:05:36] entity

[04:05:38] flag for the entity to false. And so

[04:05:42] here let's create a public partial strct

[04:05:46] for the collect gem system I system. And

[04:05:51] in the public void on update ref system

[04:05:58] state we're actually going to need to uh

[04:06:01] create a job. So outside of this

[04:06:08] publicruct collect gem job which is an I

[04:06:13] trigger event job. We need to import the

[04:06:17] types and implement the missing members

[04:06:20] all that exciting stuff that we like to

[04:06:22] do. And let's get our component lookups.

[04:06:24] And so also our player is going to need

[04:06:26] a new data component because we want to

[04:06:28] keep track the number of gems collected.

[04:06:31] So we can say public strruct gems

[04:06:35] collected count. This of course is an I

[04:06:38] component data and then we can say

[04:06:40] public int value on here. And then we

[04:06:43] can just add this type to the player

[04:06:46] just like that. So we'll go ahead and

[04:06:48] define this new trigger events job. We

[04:06:50] have a couple component lookups here. A

[04:06:52] readonly component lookup for the gem

[04:06:54] tag so we can figure out which one is

[04:06:56] the experience gem. Uh the gems

[04:06:59] collected count is kind of a a two for

[04:07:01] one here. So this allows us to determine

[04:07:04] which entity is the player entity. And

[04:07:06] also in here, we're just going to

[04:07:07] directly increment this gems collected

[04:07:09] count. We're not going to do any crazy

[04:07:11] buffering or anything like that. And

[04:07:12] then finally, we do have a component

[04:07:14] lookup for the destroy entity flag. And

[04:07:16] uh that's because we want to destroy the

[04:07:18] experience gem when the player picks it

[04:07:21] up. And then we'll just do our usual

[04:07:22] determining which one is the gem entity,

[04:07:25] which one is the player entity. And then

[04:07:27] after that we can uh get the number of

[04:07:30] gems the player has collected by saying

[04:07:33] var gems collected is equal to our gems

[04:07:38] collected lookup at the player entity

[04:07:43] and then we can say gems collected plus

[04:07:47] equals 1. So just add one to it. Um

[04:07:50] actually we do need to do gemscollected

[04:07:52] value plus equals 1. And then this

[04:07:54] doesn't actually change the component

[04:07:56] data for the player. We need to set it

[04:07:58] back via the component lookup. So we say

[04:08:02] gems collected lookup at player entity

[04:08:06] is equal to gems collected. And then

[04:08:09] finally we want to destroy the gem

[04:08:11] entity. So we'll say destroy entity

[04:08:14] lookup at the gem entity. So we can say

[04:08:19] destroy entity lookup set component

[04:08:24] enabled on the gem entity equal to true.

[04:08:29] And that will of course just destroy the

[04:08:30] gem entity. All right. So once we have

[04:08:32] the collect gem job created, we just

[04:08:34] need to go ahead and schedule it. var

[04:08:37] new collect job is equal to a new

[04:08:42] collectge

[04:08:44] gem job. of course set all of our uh fun

[04:08:48] component lookups for the gem lookup. So

[04:08:51] just go ahead and define the jobruct

[04:08:53] with all the component lookups on it.

[04:08:55] And then finally just again get that

[04:08:57] simulation singleton from system API get

[04:09:00] singleton simulation singleton. And then

[04:09:03] we can actually schedule a job by

[04:09:04] passing in that simulation singleton and

[04:09:07] the state.dependency and then setting

[04:09:09] the result of that back to

[04:09:11] state.dependency. So, let's come back

[04:09:12] over to Unity and then on our gem

[04:09:15] prefab, we'll make sure to add the gem

[04:09:18] authoring component on there. We don't

[04:09:20] need to set any values or anything on

[04:09:22] there. And we'll go ahead and enter play

[04:09:24] mode. And then finally, we can find some

[04:09:27] of these aliens close by and they'll

[04:09:30] drop their gems and we can just go over

[04:09:33] and collect these. So, now we are

[04:09:35] collecting these gems. And if we just go

[04:09:37] ahead and pause the game right here,

[04:09:39] let's uh find the player and go over to

[04:09:42] our runtime mode. And if we scroll down,

[04:09:45] we should be able to see the gems

[04:09:47] collected count. So, we have collected

[04:09:49] four gems. Um, however, we're not just

[04:09:52] uh we're just not displaying that in the

[04:09:54] UI right now. We're still showing zero.

[04:09:56] So, the final piece of this puzzle is

[04:09:58] just going to be to update this UI right

[04:10:00] here. Now, what we actually want to do

[04:10:02] is we want to be able to burst compile

[04:10:04] this collection job. So, um I'm going to

[04:10:06] go ahead and just add the burst compile

[04:10:11] attribute onto that there. And uh we'd

[04:10:14] also just, you know, go ahead and uh

[04:10:17] burst compile the scheduling of this as

[04:10:19] well. And the reason that I bring this

[04:10:21] up specifically is because in the game

[04:10:24] UI controller, we actually have this uh

[04:10:27] public method for the update gems

[04:10:29] collected text. And all we need to do is

[04:10:32] just pass in an integer for the gems

[04:10:34] collected. And you know because we want

[04:10:36] this collection job to be burst compiled

[04:10:39] because this is you know potentially

[04:10:40] running against a large number of

[04:10:42] trigger events. Um we ideally would want

[04:10:44] this to be burst compiled. Um this is

[04:10:47] going to prevent us from directly just

[04:10:49] calling game UIC

[04:10:52] controller.instance.update gems

[04:10:53] collected text um with the gems

[04:10:55] collected value even though we have the

[04:10:56] data right there. So what we're actually

[04:10:58] going to end up having to do is use

[04:11:00] enable components to kind of trigger

[04:11:03] this behavior. So um on our player under

[04:11:06] the uh gems collected count we'll just

[04:11:08] have a public strruct for update gem

[04:11:13] UI flag which is an I component data. So

[04:11:17] an I

[04:11:19] enable component. So we just add this uh

[04:11:22] update gem UI flag to the player and uh

[04:11:25] it can start as enabled. That's totally

[04:11:27] fine um because we're actually just

[04:11:29] going to create a new system down here

[04:11:31] at the bottom. So this is a public

[04:11:34] partial strruct for update gem

[04:11:39] ui system and this is an i system and

[04:11:43] then we'll do our public void on update

[04:11:48] ref system

[04:11:51] state and here we can say for each

[04:11:55] var in system

[04:11:59] apiquery and so we're going to look for

[04:12:01] everything with the gems collected count

[04:12:05] and also we're going to do that enabled

[04:12:09] refw against that update gem UI flag and

[04:12:14] so that way this gives us access to the

[04:12:18] current gem count as well as um a flag

[04:12:22] to should update the UI. So from here,

[04:12:26] we're not going to burst compile this

[04:12:27] system. And so we'll say game UIC

[04:12:31] controller.

[04:12:33] Instance update gems collected text. And

[04:12:38] then in here we can pass in

[04:12:43] gemcount. And then we don't want to uh

[04:12:45] update the UI every frame. So we can

[04:12:47] just say should update

[04:12:50] UI.RW equals false. And again, that just

[04:12:54] disables that component. Um, so now we

[04:12:56] need to kind of enable this update gem

[04:12:58] UI flag component. And of course, we can

[04:13:01] do that from a component lookup here. So

[04:13:04] I've added a new component lookup for

[04:13:06] update gem UI flag called the update gem

[04:13:09] UI lookup. And then we're going to do um

[04:13:13] update gem UI lookup set component

[04:13:19] enabled on the player entity to true.

[04:13:23] And then just go to where we're

[04:13:25] scheduling it. And then we'll add the

[04:13:28] update gem UI lookup is in system

[04:13:34] API.get get

[04:13:36] component

[04:13:38] lookupdate gem UI flag and then we'll

[04:13:42] just add that component lookup to where

[04:13:44] we're scheduling the job. So now we'll

[04:13:46] destroy a couple aliens and when we pick

[04:13:48] up a gem you'll see that the gem count

[04:13:50] is now updating. So every time we

[04:13:52] collect one of these it's now updating

[04:13:55] that little UI um to display how many

[04:13:58] gems we've collected. And um there are

[04:14:00] two just other things that I do want to

[04:14:03] uh mention. So, one of them I think is

[04:14:05] already implemented. Let me see. If I

[04:14:06] hit escape, it should pause the game.

[04:14:09] So, I can show you real quickly how this

[04:14:10] works. Um, and so this gives us the

[04:14:12] ability to resume or quit to the main

[04:14:14] menu. Can also just hit escape to uh

[04:14:16] re-resoom. So, just escape to pause,

[04:14:19] escape to resume. Um, and you will

[04:14:20] notice that the uh character animations

[04:14:23] are all paused as well. So, the

[04:14:25] animations are not playing in the

[04:14:26] background. So, real quickly, I'll show

[04:14:28] you how to do that. And then after that,

[04:14:30] I'm very briefly going to go over how to

[04:14:33] do the um enemy health bar UI. So, how

[04:14:37] the game pausing works is I just already

[04:14:39] have this in the game UI controller that

[04:14:41] I shipped with the um kind of initial

[04:14:43] project files. And then um so you just

[04:14:45] see that right now in on update, I'm

[04:14:47] just looking for when you press the

[04:14:49] escape key that we toggle the game

[04:14:51] pausing um and just have this like local

[04:14:54] is pause variable that I'm just flipping

[04:14:56] back and forth between true and false.

[04:14:58] Um and just setting this pause UI panel

[04:15:01] to be active whether it's paused or not.

[04:15:03] Now the important part that I want to

[04:15:05] discuss is the set ECS enabled should

[04:15:08] enable. What I'm doing here is getting a

[04:15:10] reference to the ECS world all you know

[04:15:13] from the MonoBehavior side and I'm doing

[04:15:15] that by doing

[04:15:17] world.default game object injection

[04:15:19] world and that gives me the default

[04:15:21] world. And so first I just want to check

[04:15:23] to make sure that it's not null. If it's

[04:15:25] null, we just return out of there

[04:15:26] because things are going to throw

[04:15:28] errors. Now, what I'm doing is I'm

[04:15:30] actually grabbing the initialization

[04:15:32] system group and the simulation system

[04:15:34] group. You may remember way back towards

[04:15:37] the beginning of this video, I was kind

[04:15:38] of talking about the player loop and how

[04:15:41] there's kind of different phases of the

[04:15:42] player loop. And there's the

[04:15:44] initialization system group, which runs

[04:15:46] first, and the simulation system group,

[04:15:48] which runs kind of like in the middle,

[04:15:50] which is where most of the systems

[04:15:52] happen. And then finally, there's the

[04:15:54] presentation system group. So again, all

[04:15:56] of our game logic is either going to be

[04:15:58] in the initialization system group or

[04:16:00] the simulation system group. And that

[04:16:02] includes all of Unity's game logic for,

[04:16:05] you know, the transformation system, the

[04:16:07] physics system, all that stuff. Those

[04:16:08] are happening just within those two

[04:16:10] groups. So in order to implement

[04:16:12] gameplay pausing, what I'm doing is I'm

[04:16:14] just getting a reference to those top

[04:16:16] level groups and then just disabling

[04:16:18] them. And then when you disable them,

[04:16:20] basically what happens is it just

[04:16:21] doesn't call the update method for all

[04:16:24] the methods within that system group. So

[04:16:26] it effectively just stops the game

[04:16:29] because the the systems are no longer

[04:16:31] being updated. And so that includes

[04:16:33] systems like this global time update

[04:16:35] system because again this just runs in

[04:16:37] the simulation system group. And this is

[04:16:39] that global time variable that we use to

[04:16:42] increment the shader property to

[04:16:45] actually advance the animations. Now, if

[04:16:47] this system if the system group that

[04:16:50] this system is running in is disabled,

[04:16:52] you know, again, this system is not

[04:16:54] running. So, that global time variable

[04:16:56] is not continually being updated. So, it

[04:16:59] effectively just pauses the animations.

[04:17:01] So, it's kind of a cool little trick

[04:17:02] that I've been using to implement

[04:17:04] gameplay pausing and seems to work quite

[04:17:05] well for me. Okay. And then the final

[04:17:08] last thing, I promise, is going to be um

[04:17:12] adding in the player health bar. So we

[04:17:14] can kind of see the player's health,

[04:17:16] have a visual representation of it. Um,

[04:17:18] so anyways, the way this is going to

[04:17:19] work is I actually do have this player

[04:17:21] world UI prefab. And you can see that

[04:17:24] it's just a regular uh health bar slider

[04:17:26] that just goes kind of from 0 to one

[04:17:28] here. And what's going to happen is

[04:17:30] we're going to spawn this health bar

[04:17:32] into the world at the start of the game

[04:17:34] uh just under the player here. And then

[04:17:36] it's going to follow the player around

[04:17:38] and continually update the health bar to

[04:17:41] be accurate with the current health

[04:17:42] value for the player. And then finally,

[04:17:44] when the player gets destroyed, we also

[04:17:46] want to destroy the UI element so it's

[04:17:50] not just like living in the game world.

[04:17:52] And so, as you can imagine, we need a

[04:17:53] couple of components for this. So, we'll

[04:17:55] do a public strruct, and we can call

[04:17:58] this the player world UI. And this is

[04:18:02] going to be a special component type

[04:18:04] called an I cleanup component data. Now

[04:18:08] with cleanup components, this allows us

[04:18:10] to do some additional cleanup logic when

[04:18:13] the main entity is destroyed. So this

[04:18:16] component gets associated with our

[04:18:17] player entity. And then when our player

[04:18:19] entity gets destroyed when the aliens,

[04:18:21] you know, attack him, then the player

[04:18:23] entity no longer exists except it exists

[04:18:26] in this little bit of a weird state

[04:18:28] where it just kind of removes all of its

[04:18:30] regular components. But all the cleanup

[04:18:32] components remain on that entity. And

[04:18:34] the idea here is we can kind of query

[04:18:36] for that state where we can find um you

[04:18:39] know something where the cleanup

[04:18:41] component exists but some other known

[04:18:43] components do not exist. And then we can

[04:18:45] do some additional cleanup logic. And so

[04:18:48] this is helpful for when we have kind of

[04:18:50] these linked game objects where we have

[04:18:52] a game object health bar UI that is

[04:18:56] going to kind of follow our player

[04:18:58] around and then when our player

[04:19:00] eventually is destroyed, we want to

[04:19:02] clean up that component so it's not just

[04:19:04] some like weird empty game object living

[04:19:06] in the world. And then so on this

[04:19:08] cleanup component, this is going to be

[04:19:09] where we have our uh Unity object ref

[04:19:12] data to reference the things on the game

[04:19:15] object side. So, we'll do a public Unity

[04:19:19] object ref. And the first thing that

[04:19:21] we're going to need is a transform um

[04:19:25] just of the canvas. So, we'll say canvas

[04:19:28] transform. And then we'll also need a

[04:19:30] public Unity object ref of the slider.

[04:19:36] And we can just call this health bar

[04:19:39] slider. We do need to uh import the

[04:19:41] Unity engine.UI slider. Now these

[04:19:44] cleanup components are a little bit

[04:19:45] weird because we can't like add them to

[04:19:48] an entity through baking. So actually we

[04:19:51] have to do a uh public strruct for

[04:19:55] player world UI prefab. This can just be

[04:19:59] a regular I component data. And on here

[04:20:03] we need a

[04:20:05] public Unity object ref of type game

[04:20:09] object. So, we're going to be storing a

[04:20:11] game object prefab for the um player

[04:20:14] world UI. And we can just call this

[04:20:16] value because there's a single value

[04:20:17] there. So, anyways, come down on the

[04:20:20] player authoring and we'll create a

[04:20:22] public

[04:20:24] game object for the world UI prefab. And

[04:20:29] down at the bottom of the baker, go

[04:20:32] ahead and do an add component to the

[04:20:35] entity. is a new player world UI

[04:20:40] prefab. And then the value here is equal

[04:20:44] to

[04:20:45] authoring world UI prefab. And uh so

[04:20:49] that's just going to store this Unity

[04:20:51] object ref inside this player world UI

[04:20:54] prefab. Um now we're going to create a

[04:20:56] system and the system has three steps to

[04:20:59] it. So let's uh go down towards the

[04:21:01] bottom here and create a new public

[04:21:06] partial strct called the player world UI

[04:21:11] system and this is an I system and in

[04:21:15] the public void on update um we're going

[04:21:19] to do three separate for each queries.

[04:21:22] So we'll say for each little placeholder

[04:21:24] there in system

[04:21:27] API query. And so we're going to look

[04:21:30] for that player world UI prefab. And we

[04:21:34] also want to make sure that we do not

[04:21:36] already have the player world UI

[04:21:40] component proper. Um, now because we are

[04:21:43] going to need to add a component to this

[04:21:46] entity, we also do need with entity

[04:21:50] access like that. So let's go ahead and

[04:21:53] name these variables. So we'll say

[04:21:57] UI prefab and entity. And then to add a

[04:22:01] component to this entity, uh, we do need

[04:22:03] to use an entity command buffer. So

[04:22:06] let's just do a local entity command

[04:22:07] buffer. So new entity

[04:22:11] command

[04:22:12] [Music]

[04:22:14] buffer and use that uh state.world

[04:22:18] update

[04:22:20] allocator. And then um at the bottom

[04:22:23] later on we're going to need to do an

[04:22:26] ECB.playback state entity

[04:22:30] manager. Sweet. So let's go ahead and

[04:22:33] actually instantiate this game object

[04:22:36] into the world. So we'll say var new

[04:22:39] world UI is equal

[04:22:43] to the uh Unity object

[04:22:48] instantiate passing in UI prefab value

[04:22:52] do value and uh we kind of have to do

[04:22:55] the double value because we're saying

[04:22:57] you know dov valueue for that unity

[04:22:58] object ref and then dovalue for the

[04:23:01] actual you know prefab that it's

[04:23:03] pointing to. So we can just spawn it in

[04:23:05] the world because it's a full canvas

[04:23:07] itself. It doesn't need to be parented

[04:23:08] under anything. And then here we can do

[04:23:11] an

[04:23:12] ECB.add component. And we'll add a

[04:23:15] component to the entity. And this is a

[04:23:17] new player world UI. And in here we can

[04:23:21] set the canvas transform. Go to new

[04:23:26] world

[04:23:27] UI.transform. And then for the health

[04:23:30] bar slider can do a new world

[04:23:35] UI.get component in children uh passing

[04:23:40] in the slider just like that. And so now

[04:23:45] uh this kind of part of the for each

[04:23:47] loop is only going to run once basically

[04:23:48] at the beginning because uh we do have

[04:23:51] this player world UI prefab from the

[04:23:53] baker, but we don't yet have this player

[04:23:55] world UI um that actually keeps a

[04:23:58] reference to the transform and the

[04:24:00] slider. So at this point, we're just

[04:24:01] kind of adding this to the entity and

[04:24:04] setting the transform and slider values

[04:24:06] on that component. And now we'll do

[04:24:09] another of these for each. And this is

[04:24:12] going to be kind of the for each that

[04:24:14] updates constantly throughout the

[04:24:16] application. So uh we'll do a var

[04:24:19] placeholder in system

[04:24:22] API.query and we are going to need to

[04:24:24] know the world position of the player.

[04:24:26] So we can say local to world. We do need

[04:24:30] this player world UI which we just

[04:24:34] added. And then we also need to know the

[04:24:37] character current hit points and the

[04:24:42] character

[04:24:43] max hit points. And so from that then we

[04:24:48] can assign some names to these guys. So

[04:24:50] local to world we can call this

[04:24:52] transform player world UI we'll say

[04:24:55] world UI current hit points and max hit

[04:25:02] points. So now what we're going to do is

[04:25:04] we're first going to update the position

[04:25:07] of this world UI element. So we'll say

[04:25:09] world

[04:25:11] UI

[04:25:13] canvas

[04:25:16] transform dot

[04:25:19] value.osition is equal to

[04:25:24] transform.position. That's capital P

[04:25:26] position. So this is setting the

[04:25:28] position of that canvas UI to the

[04:25:31] position of the player. And uh so the

[04:25:34] canvas is kind of centered on the player

[04:25:36] and the health bar I just kind of

[04:25:38] manually offset that below center so

[04:25:40] it'll sit below the player. Um so now we

[04:25:42] need to actually update the health bar

[04:25:44] slider. So let's get the health value.

[04:25:47] And we can just get this by uh taking

[04:25:50] the current hit points dot value and

[04:25:55] dividing that by the max hit points dov

[04:25:58] valueue. So once we have that current

[04:26:00] health value, we can say world

[04:26:05] uihealthbar slider

[04:26:07] value is equal to health value. So

[04:26:12] little verbose, I know, but this world

[04:26:13] UI is kind of the player world UI

[04:26:16] component. Uh the health bar slider is

[04:26:18] the Unity object ref. When we do a

[04:26:21] value, we get access to the actual

[04:26:24] slider element itself. And then the

[04:26:26] lowercase value is us setting the value

[04:26:29] of that slider. And again, the slider is

[04:26:31] just from 0 to one. Um so it will

[04:26:33] always, you know, show the correct

[04:26:35] health here. So at this point, we're

[04:26:37] instantiating it in the world. We're

[04:26:39] updating its position. we're updating uh

[04:26:42] the slider and all that. And the final

[04:26:44] step is the cleanup step. So we can say

[04:26:46] for each bar in system API query and

[04:26:52] here we look for anything that has that

[04:26:54] player world UI but has none of the

[04:26:59] local to world. So basically if the

[04:27:01] local to world does not exist then we

[04:27:04] know the entity has been destroyed but

[04:27:06] that cleanup component still needs to be

[04:27:08] cleaned up. And uh we do need entity

[04:27:11] access here as well. So let's set these

[04:27:14] names for world UI and entity. And so

[04:27:19] first I always check to see if the world

[04:27:22] UI still exists. So we can say world

[04:27:26] UI.anvas

[04:27:28] transform value is not equal to null.

[04:27:31] Then in that case we'll say

[04:27:33] object.destroy

[04:27:35] destroy passing in the world UI

[04:27:39] dot canvas transform value.game game

[04:27:44] object. Like a little bit of a

[04:27:46] roundabout way of getting to it, but you

[04:27:48] know, again, we got to go through this

[04:27:50] Unity object refal, which gets us the

[04:27:53] transform, and then from the transform,

[04:27:56] we get reference to the game object, and

[04:27:58] we're actually destroying the game

[04:27:59] object. And then, so anyways, we can do

[04:28:01] an

[04:28:02] ECB. Component, and we want to remove

[04:28:06] that player world UI from the entity.

[04:28:10] And again, this is a cleanup component.

[04:28:12] So we need to specifically manually

[04:28:14] remove that component in order for that

[04:28:17] entity to get fully destroyed. So

[04:28:19] anyways, that is the player world UI

[04:28:21] system. We cannot burst compile it

[04:28:22] because we are using manage types here.

[04:28:24] So let's select the player and go to

[04:28:28] authoring mode. And on the player

[04:28:30] authoring, we just need to set the

[04:28:31] player world UI prefab like that. And

[04:28:34] now when we enter play mode, you'll see

[04:28:36] that the slider is now following the

[04:28:38] player. And then if we do get damaged by

[04:28:41] any of these entities, you'll see that

[04:28:43] the health bar goes down and uh it's

[04:28:45] going to stay at that level. And let's

[04:28:47] see if we can actually uh get the

[04:28:49] enemies to destroy us here. And then

[04:28:51] boom, once it reaches zero, we go into

[04:28:52] this game over state. And uh yeah, so

[04:28:55] that is um kind of a little survivors

[04:28:58] game we made with Unity ECS. Really hope

[04:29:01] you enjoy this tutorial. And so

[04:29:02] actually, I'm not going to leave you on

[04:29:03] that. I'm going to leave you with a

[04:29:04] challenge. The challenge that I have for

[04:29:06] you is that uh you'll see over in the

[04:29:09] materials we do have a reaper alien

[04:29:12] material with this awesome reaper.

[04:29:14] Again, created by Sill over at Penzilla

[04:29:16] Design. And what I would like you to do

[04:29:19] is to create a Reaper prefab that will

[04:29:22] actually spawn into the world after some

[04:29:25] set duration of time. And this Reaper

[04:29:28] should deal enough damage to the player

[04:29:29] to destroy it immediately. And this

[04:29:32] reaper should also have enough health

[04:29:34] where the player can do nothing about

[04:29:36] it. So you should have everything you

[04:29:38] need to know to implement this Reaper

[04:29:39] alien. Again, the final project files

[04:29:42] will include kind of one implementation

[04:29:44] of how to implement that. And another

[04:29:46] cool thing that I would try and

[04:29:47] challenge you to have to do is um when

[04:29:50] the Reaper actually gets spawned that

[04:29:52] you should destroy all the other

[04:29:54] existing aliens and make sure no

[04:29:56] subsequent aliens spawn after that. So

[04:29:59] anyways, I'll leave you with that and do

[04:30:01] a little outro here for you in just a

[04:30:02] sec. So anyways, that's how you can

[04:30:04] create a simple little survivors type

[04:30:06] game using Unity's data oriented

[04:30:08] technology stack and their entity

[04:30:09] component system. Really do hope that

[04:30:11] you learned a lot in this video and I

[04:30:13] really do again encourage you to try out

[04:30:15] some of those challenges that I pitched

[04:30:17] out to you throughout uh this course of

[04:30:19] this video and also feel free to just,

[04:30:21] you know, go in and start playing around

[04:30:22] with things. You know, crank up the

[04:30:24] numbers really high and and see where

[04:30:25] things go. You might be surprised at

[04:30:27] some of the numbers that you can uh

[04:30:29] achieve on your machine. And then just

[04:30:30] wanted to remind you one last time that

[04:30:32] this tutorial is again just kind of the

[04:30:34] introduction into dots and ECS. And if

[04:30:36] you do want to take it a step further,

[04:30:38] go check out the product that I have

[04:30:40] over on the Unity asset store where I

[04:30:42] have all the project files for my

[04:30:44] shipped Steam game survivors as well as

[04:30:46] a whole bunch of documentation both

[04:30:48] written and video detailing all of the

[04:30:51] most important concepts of this game and

[04:30:54] how everything all fits together. There

[04:30:55] is a ton of great information in that

[04:30:57] product and I'm extremely proud of it

[04:30:59] and I really hope that you do learn a

[04:31:00] ton from it. So anyways, thanks so much

[04:31:02] for watching this tutorial. I really

[04:31:04] can't wait to see all the amazing things

[04:31:05] that you create with Dots and ECS.
