Creating a Player and Adding Simple Controls
1. Create the player component
The circle will become the player, so we need to create a Player
struct for it with parameters like position and color.
-
The
Player
struct is a component that can be attached to entities in Bevy’s Entity-Component-System. -
We also spawn a player entity in the
setup()
function just as we did with the camera.
Here is the new and improved code:
main.rs
use bevy::prelude::*; // Includes commonly used types, traits, and functions from the Bevy game engine.
use bevy::color::palettes::basic::*;
#[derive(Component)] // Marks the Player struct as a component.
struct Player {
position: Vec2,
color: Srgba,
}
fn main() {
App::new() // Creates a new Bevy application.
.add_plugins(DefaultPlugins)
// Adds the setup system to the Startup stage, which runs once at the beginning.
.add_systems(Startup, setup)
//Adds the draw_player system to the Update stage, which runs every frame.
.add_systems(Update, draw_player)
// Runs the application.
.run();
}
fn setup(mut commands: Commands) {
commands.spawn(Camera2dBundle::default()); //Spawns a 2D camera entity.
commands.spawn(Player { //Spawns a Player entity with these parameters.
position: Vec2::new(0.0, 0.0),
color: RED,
});
}
fn draw_player(
mut gizmos: Gizmos,
mut player_query: Query<&mut Player>,
) {
let size_radius = 20.0;
for player in &mut player_query {
// Draw a circle at the player's position.
gizmos.circle_2d(player.position, size_radius, player.color);
}
}
- Although there is a little more code, it still does the same thing as the previous code.
2. Up and Down Controls
Lets add simple controls to the player so that it is able to move up and down.
-
We will use the up and down arrow keys to move the player.
-
Lets adjust the
draw_player()
function and add a keyboard input. -
We add two simple
if
statements.
Now the draw_player()
function should look something like this:
fn draw_player(
mut gizmos: Gizmos,
mut player_query: Query<&mut Player>,
keyboard_input: Res<ButtonInput<KeyCode>>,
) {
let size_radius = 20.0;
for mut player in &mut player_query {
// Draws a circle at the player's position.
gizmos.circle_2d(player.position, size_radius, player.color);
if keyboard_input.pressed(KeyCode::ArrowUp) {
player.position.y += 1.0; // Moves the player up.
}
if keyboard_input.pressed(KeyCode::ArrowDown) {
player.position.y -= 1.0; // Moves the player down.
}
}
}
- Run your code and try pressing the up and down arrow buttons. Your player should move up and down.
3. Adding Rotation
Now lets make the controls a bit more advanced and let the player turn and rotate.
-
We will add another parameter to the Player struct called
direction_angle
. -
Its the angle (in radians) that the player is facing. This angle is used to determine the direction in which the player moves when the forward or backward keys are pressed.
-
To do that we need to edit the struct and the setup function.
#[derive(Component)] // Marks the Player struct as a component that can be attached to entities in Bevy's Entity-Component-System.
struct Player {
position: Vec2,
direction_angle: f32,
color: Srgba,
}
fn setup(mut commands: Commands) {
commands.spawn(Camera2dBundle::default()); //Spawns a 2D camera entity.
commands.spawn(Player { //Spawns a Player entity with these parameters.
position: Vec2::new(0.0, 0.0),
direction_angle: 0.0,
color: RED,
});
}
-
Next, we need to edit the
draw_player
function. Thedirection_angle
of the player will change when the left or right arrow key is pressed. -
Usually, angles are measured from left to right, so we will make it so that the right arrow increases the angle and the left arrow decreases it.
-
If you know anything about vectors, you should know that when two vectors sum together, it results in another vector. Because
player.position
is a vector, in order to change it, we need to add another vector to it. However,direction_angle
is not a vector, but we can make it into one by calculating the sine and cosine of the angle and then using them as the x and y components of themovement_vector
. -
Then the
movement_vector
will be added to the currentplayer.position
. Depending on the x and y components of themovement_vector
, theplayer.position
will change in the direction indicated by the arrow keys.
The updated draw_player
function looks like this:
fn draw_player(
mut gizmos: Gizmos,
mut player_query: Query<&mut Player>,
keyboard_input: Res<ButtonInput<KeyCode>>,
) {
let size_radius = 20.0;
for mut player in &mut player_query {
gizmos.circle_2d(player.position, size_radius, player.color); // Draws a circle at the player's position.
if keyboard_input.pressed(KeyCode::ArrowLeft) {
player.direction_angle -= 0.1; // Rotates the player to the left.
}
if keyboard_input.pressed(KeyCode::ArrowRight) {
player.direction_angle += 0.1; // Rotates the player to the right.
}
// Calculate the movement vector based on the player's direction and speed.
let x = f32::sin(player.direction_angle);
let y = f32::cos(player.direction_angle);
let movement_vector = Vec2::new(x, y);
if keyboard_input.pressed(KeyCode::ArrowUp) {
player.position += movement_vector; // Moves the player forward.
}
if keyboard_input.pressed(KeyCode::ArrowDown) {
player.position -= movement_vector; // Moves the player backward.
}
}
}
Now, run your code and try pressing the arrow keys. If everything is correct, your player should move forwards and backwards and turn when you press the left or right key.
4. Speed
-
If we want the player to go a little faster (or slower), you need to add another parameter named
speed
to thePlayer
struct and define it in thesetup()
function where the player entity is spawned. Then multiply theplayer.speed
by the x,y vector when defining themovement_vector
. -
Because of the multiplication operation,
movement_vector
will be larger (or smaller), hence the player will move further when the arrow key is pressed, creating the effect of it moving faster.
Here is the finished code:
main.rs
use bevy::prelude::*; // includes commonly used types, traits, and functions from the Bevy game engine.
use bevy::color::palettes::basic::*;
//use bevy::input::ButtonInput;
#[derive(Component)] // Marks the Player struct as a component that can be attached to entities in Bevy's Entity-Component-System.
struct Player {
position: Vec2,
direction_angle: f32,
speed: f32,
color: Srgba,
}
fn main() {
App::new() // Creates a new Bevy application.
.add_plugins(DefaultPlugins)
// Adds the setup system to the Startup stage, which runs once at the beginning.
.add_systems(Startup, setup)
//Adds the player_update system to the Update stage, which runs every frame.
.add_systems(Update, draw_player)
// Runs the application.
.run();
}
fn setup(mut commands: Commands) {
commands.spawn(Camera2dBundle::default()); //Spawns a 2D camera entity.
commands.spawn(Player { //Spawns a Player entity with these parameters.
position: Vec2::new(0.0, 0.0),
direction_angle: 0.0,
speed: 3.0,
color: RED,
});
}
fn draw_player(
mut gizmos: Gizmos,
mut player_query: Query<&mut Player>,
keyboard_input: Res<ButtonInput<KeyCode>>,
) {
let size_radius = 20.0;
for mut player in &mut player_query {
gizmos.circle_2d(player.position, size_radius, player.color); // Draws a circle at the player's position.
if keyboard_input.pressed(KeyCode::ArrowLeft) {
player.direction_angle -= 0.1; // Rotates the player to the left.
}
if keyboard_input.pressed(KeyCode::ArrowRight) {
player.direction_angle += 0.1; // Rotates the player to the right.
}
// Calculate the movement vector based on the player's direction and speed.
let x = f32::sin(player.direction_angle);
let y = f32::cos(player.direction_angle);
let movement_vector = Vec2::new(x, y) * player.speed;
if keyboard_input.pressed(KeyCode::ArrowUp) {
player.position += movement_vector; // Moves the player forward.
}
if keyboard_input.pressed(KeyCode::ArrowDown) {
player.position -= movement_vector; // Moves the player backward.
}
}
}
Congratulations! You have made a fully controllable player.