プレイヤー
プレイヤーが操作するキャラクターを作成します。
はじめに、playing/utils.rsでCharacter、HP、およびControlというコンポーネントを定義します。Controlは速度の計算と保持を行い、それに基づいてTransformを更新するために使用します。本来は既存の物理エンジンライブラリを活用したいところですが、本記事の執筆時点ではBevyのメジャーアップデート直後で、代表的な物理エンジンであるRapierが最新バージョンに対応していませんでした。そのため、今回は簡易的な物理挙動を自作することにします。
#![allow(unused)]
fn main() {
use bevy::{math::NormedVectorSpace, prelude::*};
pub struct UtilPlugin;
impl Plugin for UtilPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
Update,
(update_velocity).run_if(
in_state(crate::state::GameState::Playing)
.and(in_state(super::InGameState::Running)),
),
);
}
}
#[derive(Component, PartialEq, Eq, Clone, Copy)]
pub enum Character {
Player,
Enemy,
}
#[derive(Component)]
pub struct HP(pub f32);
#[derive(Component, Default)]
pub struct Control {
pub mass: f32,
pub force: Vec3,
pub acceleration: Vec3,
pub velocity: Vec3,
pub speed_limit: f32,
}
impl Control {
pub fn add_force(&mut self, force: Vec3) {
self.force = force;
}
pub fn speed(&self) -> f32 {
self.velocity.norm()
}
pub fn calculate_velocity(&mut self, delta_time: f32) {
self.acceleration = self.force / self.mass;
self.velocity += self.acceleration * delta_time;
if self.speed() >= self.speed_limit {
self.velocity = self.velocity.normalize() * self.speed_limit;
}
self.force = Vec3::ZERO;
}
}
fn update_velocity(query: Query<(&mut Control, &mut Transform)>, time: Res<Time>) {
for (mut control, mut transform) in query {
control.calculate_velocity(time.delta_secs());
transform.translation += control.velocity * time.delta_secs();
transform.translation.x = transform.translation.x.clamp(-20.0, 20.0); // x limit
}
}
}
作成したUtilPluginをplaying/mod.rsのAppに追加します。
次に、playing/player.rsを編集します。まずは必要な定数を定義しましょう。
#![allow(unused)]
fn main() {
const PLAYER_FORCE: f32 = 3.0;
const PLAYER_SPEED_LIMIT: f32 = 10.0;
const PLAYER_MAX_HP: f32 = 100.0;
const PLAYER_START_X: f32 = 0.0;
const PLAYER_START_Y: f32 = 0.0;
const PLAYER_START_Z: f32 = -8.0;
}
次にPlayerコンポーネントとプレイヤーをスポーンするシステムを作ります。
#![allow(unused)]
fn main() {
use bevy::prelude::*;
// superは親モジュール(ここでは`playing/mod.rs`)を指します。
use super::bullet;
use super::utils::*;
#[derive(Component)]
pub struct Player;
fn spawn_player(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
commands.spawn((
DespawnOnExit(crate::state::GameState::Playing),
Character::Player,
Player,
HP(PLAYER_MAX_HP),
Transform::from_xyz(PLAYER_START_X, PLAYER_START_Y, PLAYER_START_Z).looking_at(Vec3::ZERO, Vec3::Y),
Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::BLACK,
emissive: Color::srgb(0.1, 0.1, 1.0).to_linear(),
..default()
})),
Control {
speed_limit: PLAYER_SPEED_LIMIT,
mass: 1.0,
..default()
},
));
}
}
続いて、プレイヤーの入力を受け取ってキャラクターを移動させるシステムを作成します。
#![allow(unused)]
fn main() {
fn move_player(mut query: Query<&mut Control, With<Player>>, keyboard: Res<ButtonInput<KeyCode>>) {
let mut control = match query.single_mut() {
Ok(control) => control,
Err(_) => {
warn!("Expected exactly one Player entity, but found none or multiple.");
return;
}
};
if keyboard.pressed(KeyCode::ArrowLeft) || keyboard.pressed(KeyCode::KeyA) {
control.add_force(Vec3 {
x: PLAYER_FORCE,
y: 0.0,
z: 0.0,
});
}
if keyboard.pressed(KeyCode::ArrowRight) || keyboard.pressed(KeyCode::KeyD) {
control.add_force(Vec3 {
x: -PLAYER_FORCE,
y: 0.0,
z: 0.0,
});
}
}
}
定義したシステムをまとめたプラグインを作成します。
#![allow(unused)]
fn main() {
pub struct PlayerPlugin;
impl Plugin for PlayerPlugin {
fn build(&self, app: &mut App) {
app.add_systems(OnEnter(crate::state::GameState::Playing), spawn_player)
.add_systems(
Update,
(move_player).run_if(
in_state(crate::state::GameState::Playing)
.and(in_state(super::InGameState::Running)),
),
);
}
}
}
playing/mod.rsでこのPlayerPluginを追加するのを忘れないようにしてください。
これでプレイヤーを動かせるようになりました。