Video
https://www.youtube.com/watch?v=nE76GGICofU
I’ll be making this basic FPS game where you can click charge up a bar and then it shoots a ball and the ball has like basic physics where it bounces and the balls look a different color each time. That kind of thing. The way this video is going to be structured, it’s going to be a like sort of stepbystep approach to building the game if you were to not necessarily know how the finished product was going to look. In an attempt to show off as many features of Bevy as possible, I’m going to actually be doing things in sort of a sub-optimal way and then going back and refactoring them into a more appropriate way for using them in Bevy. An example of this is the first time we spawn the balls in, we’ll just be getting access to the assets and creating assets for each individual ball. Whereas later, we’ll uh refactor that out and instead use a resource that contains the mesh for the ball and all the materials that are already pre-generated. This is partly to show sort of the process that you can go through to solve problems in Bevy, but also because it means that there’s a lot less code before you can get something on screen. Whereas if you go through and write everything correct the first time, it can take quite a while to actually have something appear that you otherwise wouldn’t necessarily need to do if you were just prototyping a game. It also allows me to show off ideas such as locals for quickly prototyping something before I extract them out into a resource to use in other systems in the game. If at any point you struggle to keep up with what I’m doing, you can find a GitHub repo in the description. And each commit inside that repo will correspond to a chapter inside this video. And each chapter will broken up into each time we launch the game. So each chapter will encapsulate what we needed to write in order to add it to our game and then running the game and showing off what that change has actually done to our game world. Bevy basics remaster will assume a certain level understanding of Rust. If you want to take an extra step back and be like, how do I like create a game fely from scratch? Like I’ve never used Rust or Baby before. I have a series called Zero to Pong which is like two three episodes of like starting from a fresh Windows install, installing Rust, installing all the extensions, uh setting up VS Code, like everything needed to actually get to the point where this starts off and then continue on a little bit and actually building like a basic Pong game. Whereas this one’s going to pivot. Instead of being Pong, it’s going to be this first person shooter. Let’s jump to uh Visual Studio Code. Okay, so once you’ve set up your blank Rust project, the first thing we want to do is go inside of our cargo.l in here. We need to add bevy as a dependency. In this particular case, we’re going to use bevy version 0.16. I’m also going to include rand so that I can have access to RNG later in this example, but this isn’t strictly required. Something else that’s not required, but is highly recommended is to add profile.dev.package.w wildcard opt level equals 3. What this does is cause the Rust compiler to compile all your dependency code at a higher level of optimization that it otherwise would. The reason we need to do this is because Rust tries to maintain a high level of safety. In order to do this during debug builds, Rust has all sorts of checks in place. This can lead to an issue though in games like Bevy where we do lots of things with meshes which have tens of thousands of vertexes. This can really slow the game down as Rust will do a whole bunch of thounds checks and underflow overflow checks that may not actually be necessary for the type of math that you’re doing. By including this line in our cargo, we’re telling the Rust compiler that it should compile all of our dependencies to a higher level of optimization. This only has a drawback on the first time compiling since Bevy will have to compile all the dependencies at a higher optimization level which can take significantly longer than compiling them at the regular level. But once this is done, Rust can just use the previously compiled version of Bevy every time that you rebuild your game. Therefore, this is only a trade-off on the first time you try to compile something. And it’s well worth it for the 10x speed up it’ll provide in high performance games. Now that we have our cargo toml set up, we can head over to our main.r RS. In here, we will find our main function that is actually run when we run our application. This is where we’ll construct our Bevy application. The first thing we want to do is use the Bevy preload. What the preload does is bring in all of the types that Bevy has deemed necessary for a basic game. This is things like vect 3es and basic plugins as well as things like transforms and visibility components. Then inside of our main function, we’ll construct an app. This is done by calling app new. Personally, I like to assign this to a mutable variable so that in future it is easy to modify when things are executed as well as add conditional compiling elements. Though it is possible to purely chain everything together without ever assigning a variable in the first place. Once we have our app variable, the next thing we’re going to do is add the default plugins. This is a sequence of plugins that Bevy has deemed necessary to make a very basic application. This will do things like set up the window, handle input management, as well as a bunch of other things that Bevy deems necessities for running a basic application. A lot of the default plugins are hidden behind feature walls, which means that if you disable that feature in Bevy, the corresponding plugin will not be added with default plugins. This is very useful since it allows you to dynamically change what you want your game to do and you won’t pay any performance overhead for features that you aren’t using. The final thing to do to get the absolute bum app running is to call app.r run. This will cause our application to run with the default plugins included. This will cause a basic window to open and will cause our application to have a cap frame rate of 60 fps. These are all things that can be changed and customized later down the road if you would like, but for now this is our basic application. So, as you can imagine, a basic application that doesn’t actually do anything but render a gray screen isn’t particularly useful. So, next we’ll be adding something to our application so we actually have something to look at. To achieve this, the first thing we’re going to do is want to spawn a camera.
To do this, we create a basic function that takes in the command strruct as a parameter inside of the body of this system. We simply call commands.spawn and provide the default construction of a 3D camera. This will cause Bevy to spawn an entity that contains a 3D camera. This is all that’s required to get a basic camera into our world. Though if we were to run our application now, we wouldn’t see any difference since the camera may have spawned, but there is still nothing for it to render.
The next step is to spawn some objects into the world for the camera to actually render. We do this by
again creating a new rust function, this time called spawn_map.
This will have the following parameters:
fn spawn_map( mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>, mut materials: ResMut<Assets<StandardMaterial>>,) { commands.spawn(DirectionalLight::default()); let ball_mesh = meshes.add(Sphere::new(1.)); for h in 0..16 { let color = Color::hsl((h as f32 / 16.) * 360., 1.0, 0.5); let ball_material = materials.add(StandardMaterial { base_color: color, ..Default::default() }); commands.spawn(( Transform::from_translation(Vec3::new((-8. + h as f32) * 2., 0., -50.0)), Mesh3d(ball_mesh.clone()), MeshMaterial3d(ball_material), )); }}Inside the body of this function, the first thing we’re going to do is spawn a directional camera.
This will act as our sun since it shines light in a singular direction regardless of its distance from the object that it is shining onto.
We’re then going to create the mesh of our objects that we want to spawn. In this case, we’ll be using a basic sphere to spawn some balls. To do this, we call mesh assets and add a sphere with a radius of one. This will generate a mesh and then add it to the mesh’s assets, returning us a handle so that we can get access to it later. We’ll then iterate through a loop from 0 to 16. Inside this loop, the first thing we’re going to do is generate a color which is going to result in 1/16th of the lap run the color wheel. Then, very similar to our mesh, we’ll generate a standard material where we’ll assign this color as its base color and add it to the materials resource. This again returns us a handle to the asset so we can reference it later. The final thing to do through each of our loops is to spawn the ball corresponding to that color. To do this, we call commands.spawn, sport providing it a transform with the translation of this particular ball, a mesh 3D which contains a copy of our handle to the mesh and a mesh material 3D which contains a copy of the handle to the standard material. When we add the mesh handle, we need to call clone. This is because we are sharing one mesh handle between all of 16 of the balls that we are spawning. The only reason we don’t have to do this with the material handle is because we are generating each material handle independently for each ball. So, we do not need to make multiple copies.
Finally, for us to actually see these changes in our world, we need to make sure that these systems actually run. To do this, back in our main function, we call app.add_systems, providing the Startup.This is run once when the game starts and then never again. Meaning that these systems will execute exactly once when our application launches.
fn main() { App::new() .add_plugins((DefaultPlugins,)) .add_systems(Startup, (spawn_camera, spawn_map)) .run();}We then provide as a tuple the spawn map and spawn camera functions. This will add these functions as systems that run in our bevy application. We can then hit run to see what it looks like.
Now, as you can see, once we run the game, there are 16 balls of different colors rendered in front of us. If you don’t currently see the balls, it is important to go back and check that when you gave them a transform, you gave them a negative Z component. This means that the balls will be rendered in front of the player. Whereas, if you gave them a positive Z component, they would actually be behind you and therefore not rendered.
An alternative way of fixing the fact that the balls aren’t visible, is to allow the player to turn around. Though, this isn’t necessarily the best direct fix since you still want the player to have something in front of them when they first rendering to know that the game is working properly.
Mouse
But let’s give the player the ability to look around using the mouse since this is a critical component of most games. The first thing we’re going to need is a component to indicate which entity is actually the player and therefore which entity should be rotating around when we move the mouse because we don’t want every entity to spin around as we turn the mouse. To do this, we’re going to create what’s called a marker component.
This is a component that doesn’t contain any data and instead is used to mark an entity as being affected by a specific set of logic or in other cases ignored by a specific set of logic. In order to create a marker, we simply make a normal struct in Rust that has no data otherwise known as ZST.
We then derive the component trait. This will insert and implement all the required logic for us to use this struct as a component.
#[derive(Component)]struct Player;We then go back to the system where we spawn the camera and attach the marker struct to the camera so that we can know that this is the camera that we want to move when we rotate the mouse. This on its own won’t actually do anything since all we’re doing is marking the camera as being a special entity affected by a certain behavior.
We need to actually add some logic to make this actually take effect. To do this, we’re going to make a new function called player_look.
This function will take in three system parameters.
fn player_look( mut player: Single<&mut Transform, With<Player>>, mouse_motion: Res<AccumulatedMouseMotion>, time: Res<Time>,) {
}a single that has access to mutable transforms and filters for with player. This is the equivalent of requesting mutable access to the transform component of an entity with the player component present without actually requesting any data that may be present on the player component.
The single system parameter has an additional restriction that there can only be one matching copier. This means that this system won’t run if there are no entities that contain both a transform and a player and also won’t run if there is more than one entity that contains a transform and a player.
Next, we need a res accumulated mouse motion. The accumulated mouse motion is a resource provided by Bevy that automatically collects all mouse motion events from the previous frame and accumulates them into a single place. This is simply a more convenient way of accessing this data. In theory, we could replace this with an event reader for mouse motion and accumulate ourself inside the system. But this is a waste of time since Bevy already provides us a convenient access to the accumulated mouse motion.
We then need access to res time. The time resource is a resource that Bevy provides us to allow us to access things like the delta time and the time elapse since the game started. These are useful values for when we are trying to correct for things like potentially varying frame rates.
fn player_look( mut player: Single<&mut Transform, With<Player>>, mouse_motion: Res<AccumulatedMouseMotion>, time: Res<Time>,) { let dt = time.delta_secs(); let sensitivity = 0.1; use EulerRot::XYZ; let (mut yaw, mut pitch, _) = player.rotation.to_euler(XYZ); pitch -= mouse_motion.delta.y * dt * sensitivity; yaw -= mouse_motion.delta.x * dt * sensitivity; pitch = pitch.clamp(-1.57, 1.57); // (-π / 2. , π / 2.) player.rotation = Quat::from_euler(XYZ, yaw, pitch, 0.);}Inside the body of this function, the first thing we’re going to do is create a sensitivity variable. This at the moment will be hardcoded to 0.1. But in future, we’ll take in the resolution of the game and set the variable accordingly. Since Bevy accumulates mouse motion based on pixels moved and not fraction of screen covered. This means that on higher resolution screens, you will end up with a higher sensitivity than on lower resolution screens.
Next, we need to extract the y and pitch out of the current player’s rotation. To do this, we access the transform stored inside our single parameter. We access it rotation and call to oiler providing the oiler rotation yxz. This will convert the quturnian that bevy uses to represent rotation into three oiler angles in the order of rotation yx z. This translates to getting the y followed by the pitch and then the roll. For the sake of this application, we don’t actually want to use the roll. So, we can just discard this. Next, we need to calculate the new pitch. To do this, we subtract the motion of the mouses in the y- axis times by delta times by sensitivity. After we’ve updated the pitch, we update the yaw. This is done by subtracting the x delta of the mouse times by delta times by sensitivity from our yaw that we extracted earlier. Finally, we want to clamp our pitch between half pi and positive half pi. We use half pi because bevy does all it math in radians, which means that this is the equivalent of representing 90° and positive 90°. If we don’t clamp the pitch between negative and positive 90°, the sign of the pitch will invert when the player looks past halfway. This can cause a horrible flicker as the player tries to look directly up. Finally, we need to recalculate the kturnian equivalent of these angles and set the player’s rotation to this. To do this, we call from oiler providing the same oiler rotation order that we provided before being yx z. Then passing in the your pitch and zero for the roll. If you don’t provide them in this specific order, you’ll get unintentional roll since the player will pitch up and then roll around their new up vector which is no longer pointed directly up in the world. Whereas, if we yaw around the Y direction and then pitch up, we will get the correct result we’re looking for.
Finally, to actually have this take effect inside our game, we need to run it in our update loop. To do this, we go back to our main function and we call app.add systems and we attach it to the update schedule.
The update schedule is run once per frame. And in this case, that’s once per 60th of a second. We then provide the system player look. Now, when we run a game, as we move the mouse around, you can see that the player will look around according to how we move the mouse. But you can still see the mouse on the screen, and it is even possible for the mouse to leave the confines of the screen. We’ll be addressing that in the next section. Okay, so the first step to making sure that the player only gets to move the camera when they’re actually focused on the window is to add a check to our player look to make sure that the window is actually focused before applying any motion. To do this, we need to include an additional system parameter of single window with primary window. This will give us access to the primary window entity and be able to check to see if it’s focused. Then at the beginning of our function, we simply add a guard clause. If not window focused, return. This will cause the player look function to return early if the window is not currently focused. Since we now have access to additional information about the window, we can update our sensitivity from being just a fixed value of 0.1 to instead take in the resolution and calculate what the sensitivity should be. The way I’ve chosen to do this is to take the smaller of the width and the height of the screen in pixels and divide 100 by this number. This will give us a number close to 0.15 on a standard 720p resolution. But this actually in fact gives us a percentage representation of of how far a single pixel is across the smaller dimension of the window. This means that if someone is running this game in full screen on our 1080p monitor, they will have the same sensitivity as if someone running it on a 720p monitor. Whereas if we didn’t do this check, we’d be forced to make sure that the player is always playing at a fixed resolution because the sensitivity of the mouse would be relative to the number of pixels they moved, not the percentage of the screen that they covered. We choose the smaller of the two simply to make sure that there is uniform scaling between the horizontal and vertical if they are playing in some kind of unusual aspect ratio. This on its own is enough to make sure that the mouse doesn’t move when the player clicks off of the game since the window will lose focus, but doesn’t address the original problem being that the mouse can leave the screen when the player has control and that the mouse is visible even when the player is moving the camera around. To address this, we’re going to use events and observers. This is not the only way of doing this, but it is my preferred method of approaching grabbing the cursor. The first step is to create a grab event. This will be an event that we emit whenever we want to change the current cursor’s grab state. This will be a strct containing a singular bull. A true value means grab the cursor and a false value means ungrab the cursor. Doing it this way allows us to have a forced grab and forced ungrab rather than only being able to toggle back and forth between what the current user has. This will make the state of our game less likely to fall out of sync with what the player is currently doing. Once we’ve created the grab event strct, we need to derive event on it so that we can use it as an event in Bevy. And for convenience, we’re also going to derive dref. This will allow us to simply access the boolean without needing to call zero all the time. Next, we’re going to make an observer system that actually reacts to the grab event. The main difference between an observer system and a normal system in bevy is that the function must take a trigger as its first argument. A trigger takes in a generic type of the event that we are going to be reacting to. The trigger, as well as containing the event data itself, will also give us additional metadata such as the entity that’s being targeted and the entity that the observer belongs to. These can be used in various different ways, but for this case, we’re not going to be using them. We’ll also need a single, giving us mutable access to the window that’s marked with primary window. Inside the body of the function, we’ll simply check to see if the grab event was true. If it was true, we’ll set the mouse invisible and lock it to within the screen. If the grab event was false, however, we’ll set the mouse to being invisible again and set the cursor grab to none. This will allow the cursor to move freely once again. To be able to make our observer react to this event in the world, we need to add it to our application. To do this, we call app.addobserver and provide our observer system. This will spawn an entity with the observer on it into our world. At the moment, this won’t actually do anything since we have no way of triggering a grab event. To do this, we’re going to add two additional systems. One that checks for window focus events to work out whether we should be grabbing or unrabbing the mouse. And the other will be a toggle, which allows for a key to toggle whether the grab is set or not. First, let’s cover the focus events. The first thing we need to do is use Bevy’s window focus event. Then, we’re going to create a focus events function. This function will have an event reader of window focused. This allows us to react to any window focused events that bevy emits. We’ll also need access to commands. This will allow us to trigger observers from inside this function. The body of this function is fairly simple. All we are doing is checking to see if there were any window focused events fired since the last time this function ran. If there were, we take the last one and then use commands to trigger a grab event with whatever the focus was set to. This will cause our observer to trigger and set the correct mouse state. The toggle grab function is equally straightforward. It takes a single system parameter that gives us mutable access to the window with the primary window flag and also the command strruct inside the body of this function. The first thing we do is set the windows focus equal to not the windows focus and then call a command.t trigger grab event with the new window focus. The reason we’re doing it this way is so that it’s one unified place where we’re updating the cursor if we wanted to. Since we have midle access to the window, we could have updated the cursor here. But it is better to make sure that all of our attempts to grab and unra the cursor are done in the same way. This makes the code less likely to break later. We then go add these two systems to our update schedule. The focused events can just go straight into the update schedule, but the toggle grab needs to have an additional condition applied. The reason for this is because the toggle grab will simply invert the grab state every frame that it runs. This will result in an alternating pattern of grabbing and ungrabbing the mouse if we ran it on every update. One way of resolving this would have been to add an additional system parameter that gets access to the inputs resource and check to see if a specific key was pressed. But instead, to show off more of the functionality of Bevy, we’ll be using run conditions. Run conditions are additional restrictions you can apply to a system when you add it to your application that control when it can run. In this case, we’ll use the input just released run condition providing it the key code escape. This run condition will mean that our system can only run on a frame where the player has just released the escape key. There are other variants of this run condition such as the just pressed variant. This will cause the same functionality but when the key is pressed as opposed to when the key is released. There is also a pressed variant, but this will cause the same toggling effect since it simply is checking to see if the key is currently pressed and will therefore cause the function to run every frame that the key is being pressed, not just the frames that the state changed. With these systems added to our application, when we run it, you’ll notice that the mouse immediately disappears. This is because Bevy emits a focused event as soon as the game loads and the window is focused. We can then press escape to unfocus and get our mouse back. And you’ll notice that the window does not follow the mouse anymore. If we press escape again, the camera will start following the mouse since we have regained focus on the window. An interesting thing to note is every time you hide and unhide the mouse is recentered on the screen. All right, so it’s all well and good to have your player be able to look around using the mouse. But another important thing for an FPS game is the ability to move around relative to where the camera is pointing. So that’s what we’re going to be adding next. The first thing to start with is a constant speed. In theory, this doesn’t have to be a constant and instead could be either a resource if you wanted it to be controlled globally for all players, or we could add it as a data field on our player component that we made earlier in order to allow for the player speed to be individually modified on a per player basis. For this video, I’m going to leave speed as a constant here since it is something you’ll tweak until it feels right in your game, not necessarily something that needs to be changed at runtime. For the sake of this video, we’re going to use a constant speed of 50. So, like everything else, we need to make our player move system. So this function will take in a single which gives us mutable access to the transform with player very much like the player look function. This is used so that we can move the player around. We’ll then need res button input key codes. This resource holds the state of all the buttons on the keyboard and contains information such as if they were just pressed, just released or currently being held. We’ll then need res time again since we want movement to feel consistent even when the frame rate drops below 60 fps. The first thing we do inside the player movement system is define a delta. This will represent the intended movement of the player relative to the camera’s axis. This is fairly straightforward since all we need to do is check if the A key is pressed and subtract one from the X. If the D key is pressed, we will add one to the X. If the W key is pressed, we will add one to the Z and if the S key is pressed, we will subtract one from the zed. This will give us an axi aligned representation of how the player wants to move. Then from the player transform, we extract their forward vector. This is done by calling dotforward on the player’s transform. This will return a dur three that can be converted into a vector. This vector will have a magnitude of one pointing in the direction forward of the player relative to their current rotation. We then multiply this by delta zed. This represents the desired motion that the player wants to move in the forward direction. Next, we need to work out how much the player wants to move left and right. We do this by doing exactly the same process as before, but instead of grabbing the forward vector, we grab the right vector and multiply by delta x. This will give us how much the player wants to move in the left right direction. Next, we need to work out the total amount of movement that we were moving the player by. We do this by simply summing the forward and the right vector together. Then, to remove the player’s ability to fly, we zero the yaxis. The reason we need to zero the y- axis is it means that if the player is looking up, they don’t end up flying forward into the air. Instead, we want them to stay on the ground. Next, we need to normalize or zero this vector. This results in the vector being brought back down to a length of one. This means that if the player is pressing forward and left, they don’t get an increased speed boost because they’re moving in both axis at the same time. It also means that the player will move it at a constant speed even if they’re looking almost directly up. Since we remove the y component, we need to read add some x and zed in order to correct for this loss. We use normalize or zero since if the player is standing perfectly still and we call normalize, the vector will be invalid. We then have to multiply this vector by delta time and the player’s speed and add it onto the player’s current transform. This results in the player moving around at a consistent speed in the direction that the camera is pointing. When we add the system to our application, we could simply add it to the update loop, but in most cases, it’s desirable to have a slightly more deterministic way of moving. But since Bevy runs systems in unstable ordering, it is possible that the player will look and then move, or the player could move and then look. This will result in slightly different placement of the player based on the order that the systems ran in. In order to prevent this, we simply attach an ordering constraint to the player move system. This is done by calling dot after player look. This will cause the player move system to always run after the player look system. This will result in a much more consistent and predictable way of the player moving around. This is almost unnoticeable at 60 fps since the player look and the player move are such small increments. But on lower FPS’s, it can become quite noticeable that the player moved before they looked or they moved after they looked since the camera will jump quite dramatically and the player can move quite a distance. So it’s always best practice to make sure that you’re looking and moving are ordered relative to each other. But the specific order that you pick isn’t critical as long as the order is consistent between frame. Now, when we launch the application, we can move around. And you’ll notice that the movement speed is consistent regardless if I’m holding two keys or a single key and always in the direction that the character is facing. This is the desired effect for most FPS shooters. So, next we’ll move on to adding something more interesting for the player to do than simply run around looking at a sequence of colored balls. So, obviously, the next step is adding something so that the player can interact with the world and change it. Something that’s better than just running around in a static world. To do this, we’re going to add a ball spawn event. This will be an event that when the player triggers it will cause a ball to spawn at a specific position in the world. To do this, we create a new strct called ball spawn. This strct will have a position vector 3, which is where the ball will be spawned in the world. This strct then needs to drive event so we can use it as an event. Now, unlike when we were using an in observer, this strct actually needs to persist between frames. To do this, we call app.addevent ball spawn. This will cause bevy to add all the systems and resources required for persistent events to function correctly. Once we have our event added to our application, we can now create a ball spawning system. This system will take an event reader with a generic type ball spawn. An event reader will read all the cute events of its specific type. Evering system to allow systems to respond to events up to one frame old. This allows for systems to respond to events that were fired after they had run in the previous frame. If Bevy didn’t do a double buffering system like this, it would mean that you could only respond to events that were fired before you ran in that particular frame and not events that were fired after you ran. Next, we’ll need commands so that we can spawn the objects into the world. And like in our map generation, we will need access to the assets for meshes and standard materials. The body of this function is fairly straightforward. We simply iterate through all of the events that have been fired and then call commands.spawn specifying the transform to be equal to the events spawn position. The mesh 3D will simply be a new spherical mesh added to the mesh assets and the standard material will just be a default standard material added to the material assets. This will result in basically white ball spawning whenever the spawn event is triggered. Now, at the moment, even if we added this function to our application, no balls would ever spawn because we have no way of emitting a ball spawn event. So, we need to add a system that actually spawns balls in. For this case, we’re going to make a shoot ball system. This system will take in a res button input mouse button to be able to read when the player left clicks. It’ll also take in a single transform with player so that we can work out where the player is to spawn the ball at that location. As well as a event writer ball spawn. This is the counterpart to an event reader. It allows us to send ball spawn events, not consume ball spawn events. We’ll then also use a single window with primary window in order to be able to check to see if the player is currently in a state at which the ball should spawn. The first thing we do inside of our bowl system is check to see if the cursor is currently visible. If it is visible, we return early. The reason we have to do it this way is we have no way of preventing Bevy from setting the focus of the window before this system runs. This means that when you click back into the window, Bevy has likely already set the focus back to true before this function runs. So instead, we check to see if the cursor is visible since this allows us to order this function relative to our other system that checks for focus events and sets the cursor accordingly. This is a workound to get past the fact that Bevy sets the window focused immediately when it’s clicked. Theoretically, it may be possible to place this system somewhere in your application at which it is guaranteed to run before bevy regains focus. But that is needlessly complex when we can simply order it relative to our system that checks to see if Bevby has changed the focus. The next thing this function does is check to see if the left mouse button was just pressed. If it wasn’t, we return early. If it was, we simply call spawner.right and pass in a ball spawn event with the position set to the player’s current transform. This will then be picked up by the ball spawner and spawn the ball into the world. Now back in application, we need to make sure these two systems are added to our update loop. The spawn ball system can go anywhere since this system is where we will base our other systems off, but the shoot ball system has to run before the spawn ball system. This isn’t entirely necessary, but does prevent a potential one frame of lag where the shooting of the ball could happen after the spawning of the balls, meaning that it has to wait until the next frame before that ball can spawn. By ordering it to happen before the spawning ball means that all events will always be caught on the frame that they’re emitted. The more important thing is that we have to order it before the focus events system. If you remember, the focused event system is the system that responds to Bevy’s inputs and decides whether we hide or unhide the cursor. By ordering the system to run before focus events, we guarantee that the player left clicking back into the game doesn’t cause a ball to spawn. Now, when we load back into our game, you’ll notice that every time we left click, a new ball will spawn. Though, if we are not currently locked inside the screen, the left clicking will not cause balls to spawn. And even when we click off of the game and back onto it, we don’t spawn a ball when we click back in. If we didn’t have the is visible check, we will have spawned a ball when we click back into the game. But spawning a bunch of white balls is pretty boring. Let’s spice it up a little bit by adding some color to each of the balls we spawn. So, in order to be able to spawn balls with different colors, we’ll need different materials for each ball in order to specify their color. But rather than simply generating a new material every time we spawn a ball, let’s create a resource that when the game starts up, we’ll generate a predefined list of colors and then randomly select one when we spawn a ball. This resource will also be able to hold a master copy of the handle to our mesh, meaning that we won’t need to create a new mesh every time we spawn a ball either. This cuts down on the amount of redundant resources our game would use. Admittedly, this game isn’t using very many resources, but this is still a great example of how you can use resources to store and reuse asset handles instead of creating or loading new ones every time you need them. So, the first thing to do is create a strct called bold data. This will hold a single handle to our mesh, a vector of handles to our materials and a RNG value which I’m putting behind a mutex simply so that I can make this resource accessible with non-mutable access. We then draw a resource on the strct. This will indicate to bevy that we can use this strct as a resource. Next, we’re going to implement some helper functions onto our ball data. The first of these is simply a mesh function. This will just clone the mesh that we have stored and return it as a handle. This just makes it more neat and tidy whenever we want to access this mesh. A slightly more complicated one is the materials field. then uses the RNG to randomly select one of the handles from our vector of handles and then clones that and returns it. The more important thing we need to implement for ball data in order to be able to use it as a convenient resource is from world. The from world trait allows us to tell Bevy how to create this resource when it’s initialized. If you implement default on your resource, the from world trait is automatically implemented as well and just will call default. But sometimes not all the data you need can be created with default. For example, cuz we’re using handles, we need to actually load the assets in order to get the handles. When we implement our from world trait, we get a function called from world that gives us access to the mutable world that Bevy creates. This means we can extract resources and add assets as required and then return ourselves to be inserted into the world. So, the very first thing we’re going to do inside of our from world is access the assets mesh. This is the equivalent of using the res mute assets mesh system parameter that we were using earlier. We then add a spherical mesh with a radius of one to the assets mesh. This will then return us the handle that we can store inside of our resource. Next, we’ll create a materials vector. At the moment, this will just be an empty vector that we will populate in a second. We’ll then need access to our materials assets. We do exactly the same thing we did to get access to our mesh assets, but instead specifying a standard materials asset. We’ll then iterate through the value 0 to 36. This represents us spawning 36 balls that are 136th of a step through the color wheel. This is identical to what we were doing earlier when we were spawning our world, except this time we’re doing 36 steps instead of 16. So, we create a color that is 136th of a step through the color wheel. We then create a standard material using this as our base color and add it to the materials assets. We then insert that handle into our materials vector. The final thing I do is set a fixed seed for the RNG. This is not at all necessary, just for my own enjoyment. We then return our ball data resource with the mesh handle, the materials vector, and our RNG. To actually make sure the from world function is called, we go back to our application and we call init resource and pass in bold data. This will cause the from world implementation to be called on ball data. Therefore, setting up all our materials and assets accordingly. Now that we have a standard handle for the mesh and materials, let’s go to our from world and modify it to use this new resource. To do so, we can remove the two system parameters that we were using to access the assets for meshes and standard materials and instead replace them with a single system parameter res ball data. This will return access to the ball data. The directional light is unchanged, but instead of generating our 16 balls, what we will instead do is iterate through from zero to the length of the materials vector. This will result in us spawning one ball for each material. Then when we call spawn, instead of adding a new standard material into the assets, we can just clone the material data directly out of our resource. And same goes for our mesh data. The spawn in the sample balls of the world, I’ll actually be accessing the material explicitly, but this is only because I want the balls to stay in order and don’t want the order to be randomly selected when I spawn them in. The next change we need to make is to our spawn ball function. This gets the same changes as from world except instead of directly accessing a specific material, we’ll just call material to select one at random. As you can see, we now get a larger array of colors since we have increase the number of steps from 16 to 36. And each time we spawn a ball, they are picked from one of these 36 random colors. We are still stuck in a pretty static world where once we spawn a ball, that’s it. It’s spawned where it is. The next, let’s add some velocity to these balls so that they start moving in the direction that the player is facing when they are spawned in. So the first step to adding velocity to the balls is to make a velocity component. The velocity component will be a new type component wrapping a ve 3. The ve 3 will represent both the direction and magnitude of the velocity. Once we’ve created our strct and derived component on it, we then need to actually do something with this velocity. To do this, we’re going to create a function called apply velocity. This function will take in a query that gives us mutable access to the transform of an entity and immutable access to its velocity as well as the time resource. In order to apply the velocity, all we’ll do inside the body of this function is iterate through all of the transforms that also have a velocity attached. We’ll then update the transforms translation by adding the velocity time delta time. To be able to spawn our balls in with a specified velocity, we’re going to add a velocity to our ball spawn event. This is simply just the ve 3 representing the velocity. Then inside of our spawn ball system, we simply include the velocity component when spawning in the ball. We still need to calculate this velocity when we create the ball spawn event. So inside of our ball shoot system, we’ll take the player’s forward velocity. We’ll take the player’s forward vector and multiply it by 15 in order to calculate the velocity of the ball when we spawn it. Before we can run our game, we need to do two more steps before we can actually use the velocity. First is to create the velocity system. This is going to be done slightly different to all the other systems we’ve done so far since we’ll be adding it to a fixed update. This is because even though bevy is capped at 60 fps, the actual delta time is not necessarily consistent frame to frame. For a more consistent physics experience, we actually want a fixed delta step. In order to achieve this easily in Bevy, we can add things to our fixed update. The fixed update, as the name implies, runs with a fixed delta time. Depending on what we set our fixed update to, this could result in multiple normal updates running between each fixed update step or multiple fixed update steps running between each frame. The main thing to consider is that the fixed update will run with an exact delta time. This does mean that it’s possible to have an inconsistent number of fixed updates between each frame since Bevy may insert additional fixed updates in the case of something like lag or if the delta of your fixed update doesn’t divide nicely into the delta of your normal update loop. Practical application of using a fixed time step is more about smoothing out potential frame lags. This is important for something like physics where doing multiple fixed time steps is different than doing one singular large delta step. As a result, we’re placing our physics inside the fixed update. The final thing we need to do is actually tell Bevy how long our fixed update is. This is done by inserting a time resource with fixed as its generic type. In this case, we’ll be constructing one from hertz 30. This will cause our physics to update 30 times a second. Now, once we run our application, you’ll see that when we click, the balls will fire straight out of in front of us with relatively high velocity. You’ll notice that the balls will continue on forever into the distance. Let’s fix that. In order to show off what I consider to be one of Bevy’s greatest strengths, we’ll be adding gravity to the balls. The great strength of Bevy this will be showing off is generic systems. I feel like it needs to be stressed that when we talk about generic systems, we’re talking about systems that interact well with other systems without directly knowing that the other system exists. This can result in some really interesting emerging behavior without us needing to explicitly define the interactions that can happen. To show this off, when we add the gravity, we’re simply going to write the gravity to directly interface with the velocity component. This means that we don’t need to worry about how our balls get their neutral velocity. This means that anything with a velocity could possibly have gravity without us needing to explicitly code any additional information about these entities. In order to create this function, we’ll just need to take in a query with mutable access to all velocities. We’ll also take in the time resource inside of our gravity system. We calculate what the value of gravity will be for this particular update. We’re going to do this using delta time instead of just hard coding it as a constant. Since if in the future we wanted to increase or decrease our time step, we would be able to we could, for example, make our time step smaller to have a more accurate physics simulation. Or if we’re using a more complicated physics engine, we may not increase the time step in order to reduce lag. This would also allow us to have a time step that could be changed at runtime. say give the player a slider in the options menu that goes from high performance to high accuracy. We then simply iterate through all velocities applying our new gravity to those values. In this example, we’ll be using a gravity of 9.8. This is the equivalent of Earth gravity. Finally, when we add our gravity system to our application, we’ll place it in the fixed update just like velocity, but we’ll schedule it to run before we apply the velocity. This is about giving consistency to our physics since we always want to apply the gravity before we apply the velocity. We could apply the gravity after the velocity and it would make a very slight difference. But for this particular use case, we’re just going to stick with before. Now, when we spawn into our world, whenever we fire a ball, they’ll start to fall downward, infinitely acrewing negative velocity as they fall into the void. But where’s the fun of balls that forever fall into the void? So, let’s make them bounce off the ground. In order to prevent the balls from falling infinitely into the void, I’ll be adding a basic bounce system. This will show off some of the emerging behavior that I was talking about from generic systems. Since the bounce system has no idea how gravity is applied, we actually end up with a slight dampening effect where the balls will slowly stop bouncing over time. Okay, let’s create our bounce system. This function will take in a query that gives us immutable access to the transform and mutable access to the velocity. Then inside the body of this function, we’ll iterate through this query checking to see if the transforms y value is less than zero and the velocity is less than zero. If so, we’ll invert the y velocity. The reason we need to check to see if the y velocity is less than zero is because it is possible that a ball can end up below the ground and therefore will invert its velocity every other frame since it is always below y, not being able to reach above the surface before its velocity gets inverted again. Finally, to add the bounce to our game, we simply add it to the fixed update. This time we’ll be scheduling it after the velocity is applied. Now, when we put into our game and fire balls to the air, you’ll see that they’ll bounce off the ground. And if we fire them very close to the ground with a slight little bounce, they will very quickly lose that bounce and continue to descend to zero. and green ball is not about thing. Okay, the final thing I want to add to this example before we call the end of this video, which is getting awfully long, is to add the ability to click and hold to charge the balls up to shoot further. This section will actually cover two commits on the GitHub if you’re following along there. Originally, this was split into two parts in order to show off some more functionality of Bevy, but for the sake of time, I won’t be running the game between these two commits since nothing visually changes in the game. So, instead, we’ll just run through these two commits and then we’ll launch into the game for the final time. So, what we’re going to do is add the ability to charge up balls with a power. The first thing we need to do is go into our ball spawn event and add a power value. This will just be an F32 representing how long the mouse was held down for. Next, inside of our spawn ball function, we’re going to modify what we set the velocity to. Instead of just simply modifying the velocity 15, we’re going to modify it by power times by 10. Next, inside of our shoot ball system, we need to add two more system parameters. First, we’re going to add a local optional F32. This will represent the amount of power that we’ve charged up. And then we’ll also need access to the res time so that we can calculate how much time has passed since the player was holding the ball. The local system parameter holds a piece of data that’s private to this system but persists between executions of the system itself. This means inside the body of our function, we can check to see if our local is set to sum. If the value is set to sum, that means that the player must have been holding down left click in the previous frame. We then check to see if the player has just released the left click. If they have, we’ll treat this how we used to treat pressing the left click spawning the ball event and providing the additional parameter of the current power level. If the value of sum and the player is currently pressing left mouse button, we’ll increment the current power by delta time. else. If they’re not currently holding down the left mouse button, they must have just released it. So, we can set the power back to none. And finally, regardless of if power is set or not, if they’ve just pressed the left mouse button, we’re going to set power equal to some one. This will result in a basic starting power of one. This is all we need to do to add a very basic ability to charge up a shot when we left click. But next, we’re going to actually add a visual display back to the player that they’re charging up their shot. Now, unfortunately, to be able to display feedback to the player about how charged their shot is, we can no longer use a local unless we wanted to include all the display logic inside of that function as well. So, instead, we’ll be splitting the power out into its own resource. Unlike a local, a resource is shared globally between all systems, meaning that any system can check to see what the power level is and display it to the screen. In order to create the power resource, we simply construct a strct called power. This strct will consist of two fields, charging and current. Charging will represent whether the player is currently holding down the left mouse button and current will represent how much power when the ball is fired. In order to use a strct as a resource in bib, we need to derive the resource trait. All this does is flag a strruct as being able to be used as a resource. It doesn’t actually implement any behavior. Now back in our main function, we need to insert the power resource so that our systems have access to it when they run. We’re going to instantiate it with some pretty basic values of false and zero. Since the initial values of our power resource are not actually that critical. We could have implemented default on power and when a resource implements default bevy will automatically implement from world for that particular strct. This would have allowed us to call init resource instead of having to insert the resource with specified values. Next in our shoot ball function we’re going to replace the local sum f32 with a res mute power. This will give us mutable access to the power resource. Then instead of checking to see if the local variable is sum, we’ll simply check to see if power is charging and use power.curren current where appropriate. Another thing I’m going to change while I’m here is I’m going to clamp the power level between 1 and six. This means that if you click rapidly, you’ll get a power level of one, and if you hold for up to 5 seconds, the power will increase, but after that, it won’t. This will make the UI much easier to implement. Since if we left this value uncapped, there’d be no way to represent the value as a percentage. Next, I’m going to make a power bar component. This will represent the UI element that actually represents the power being charged. This will take in a minimum and a maximum values. This could allow us to have multiple power bars on screen that all fill at different rates. Next, we’ll need some constants for when we create and use the power bar. First is a not charging color. This will represent the color of the power bar when the player is not currently holding down left click. Next is min fill. This represents the width of the bar when the player is not currently charging a shot. And finally, empty space. This represents how much of the bar we have to fill from our minimum value to our maximum value. Now, inside of our map setup function, we’re going to spawn the UI. The first thing I’ll do is spawn the adder bar. This will represent what a full bar will look like. To do this, first we need to specify an absolute position. This allows us to position that bar anywhere in the screen. We then specify a width of vmax 30. Vmax is a percentage representation of the larger of the two dimensions of the viewport. So in this case, since the window will be wider than it is tall, we are going to be 30% of the width. Next, we set the height to vmax 5. So 5% of the width of the screen. We then position this box 20 pixels from the bottom of the screen and 20 pixels from the left. We set its background color to light gray and give it border radiuses equal to vmax 5. This will give us nice rounded corners. Next, we add a child entity to this. This will actually be the bar that we are modifying and filling up. Similar to the outside bar, we’ll set its position type to absolute. We’ll then set its minimum width to vmax min fill. This means no matter what width we attempt to set later, the minimum size it can be is our min fill. We’ll then set its height to 95%. This will mean that it won’t quite touch the top and bottom of our frame. Now, we’ve reached a point where we’re going to have to start fighting against Bevy’s limited UI. I’m going to set the margin equal to UI rect all. This will set all four margins equal to the same value. Annoyingly, I have to use a value of vmax 0.125. The reason I need to use Vmax here is because if I was to specify the margin as a percentage, it will always be a percentage of the width. This made it difficult to vertically center my box when I was trying to set it up. So instead, I just calculate what 2 and 12% of the 5% of the height would be. Therefore, I get the value 0.125. We then set it background color to charging and give it border radiuses equal to vmax 5. We also need to provide our power bar component with a minimum value of one and a maximum value of six. Now, simply spawning an entity with that component wouldn’t actually do anything. We need to make a system that goes through and updates the power bar according to what the power resource is set to. To do this, we’re going to make our update power function. This will take in a query that gives us mutable access to an entity’s node, immutable access to its power bar, and mutable access to its background color. We’ll also need to take in our power resource so that we can read it value and calculate what the power bar should be set to. In the body of this function, we’ll iterate through the query. If the power bar is not charging, we’ll set the background color equal to not charging and the bar’s width equal to vmax min fill. If the bar is charging, we first work out what percentage the bar has been filled. This is done by subtracting the minimum power from the current power and dividing that by the difference between the maximum and the minimum power. We’ll then set the background color to an RGB value where the red is set as one minus this percentage and then the green is set to this percentage. This results in the bar starting off red when it’s fully discharged and slowly charging to green, giving a nice color lur. And finally, we set the bar’s width equal to min width plus the percentage times empty space. This will cause the bar to slowly get wider until it fills up all available space. And finally, we add update power bar to our update and can run the application to see what our new power bar looks like in game. As you can see, we have a light gray box in the corner with a darker gray square in it. If we left click, the bar will start charging up from red through yellow all the way to green. And then when we let go, the ball will fire out with high speed. If we only charge part of the way, the ball will fire out with a slower speed. That brings us to the end of episode zero of Baby Basics Remastered. I hope you’ve enjoyed this in-depth tutorial explaining how to make a game in Bevy. I would like to thank my Kofi supporter for his dedicated support. If you’d like to help support the channel, you can find my Kofi linked in the description or you can join the YouTube memberships. I also have my Discord if you want to come say hi. This video was a hell of a lot of work, so please do share and like the video. And if you want to see more episodes of Bevy Basic Remastered as they come out, don’t forget to subscribe so you don’t miss an