Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

弾丸の実装

playing/bullet.rs を作成し、Bullet コンポーネントと関連する定数を定義します。

#![allow(unused)]
fn main() {
use bevy::prelude::*;

use super::utils::*;
use crate::state;

#[derive(Component)]
pub struct Bullet {
    owner: Character,
    velocity: Vec3,
    damage: f32,
}

const BULLET_SPEED: f32 = 30.0;
const PLAYER_BULLET_DAMAGE: f32 = 30.0;
const ENEMY_BULLET_DAMAGE: f32 = 3.0;
const BULLET_COLLISION_RADIUS: f32 = 1.0;
const BULLET_LIFE_TIME: f32 = 0.7;
}

弾丸を移動させるためのシステムを作成します。

#![allow(unused)]
fn main() {
fn move_bullet(query: Query<(&mut Transform, &Bullet)>, time: Res<Time>) {
    for (mut transform, bullet) in query {
        transform.translation += bullet.velocity * time.delta_secs();
    }
}
}

弾丸は発射から一定時間後に消滅させる必要があるため、時間を計測するためのコンポーネントを playing/utils.rs に追加します。

#![allow(unused)]
fn main() {
#[derive(Component)]
pub struct Interval {
    pub time: f32,
    pub interval: f32,
}

impl Interval {
    pub fn tick(&mut self, delta_time: f32) {
        self.time += delta_time;
    }
    pub fn reset(&mut self) {
        self.time = 0.0;
    }
    pub fn is_ready(&self) -> bool {
        self.time >= self.interval
    }
}

fn tick_interval(time: Res<Time>, query: Query<&mut Interval>) {
    for mut interval in query {
        interval.tick(time.delta_secs());
    }
}
}

作成した tick_interval システムを、UtilPluginbuild メソッド内で App に追加しておきます。

#![allow(unused)]
fn main() {
pub struct UtilPlugin;

impl Plugin for UtilPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(
            Update,
            (tick_interval, update_velocity).run_if(
                in_state(crate::state::GameState::Playing)
                    .and(in_state(super::InGameState::Running)),
            ),
        );
    }
}
}

続いて、playing/bullet.rs に弾丸を生成(スポーン)するための関数を定義します。

#![allow(unused)]
fn main() {
pub fn spawn_bullet(
    commands: &mut Commands,
    owner: Character,
    translation: Vec3,
    forward: Dir3,
    meshes: &mut Assets<Mesh>,
    materials: &mut Assets<StandardMaterial>,
) {
    let (color, damage) = match owner {
        Character::Player => (Color::srgb(0.0, 1.0, 1.0), PLAYER_BULLET_DAMAGE),
        Character::Enemy => (Color::srgb(1.0, 0.0, 1.0), ENEMY_BULLET_DAMAGE),
    };
    commands.spawn((
        Bullet {
            owner,
            velocity: forward.normalize() * BULLET_SPEED,
            damage,
        },
        super::utils::Interval {
            time: 0.0,
            interval: BULLET_LIFE_TIME,
        },
        DespawnOnExit(state::GameState::Playing),
        Transform::from_translation(translation).looking_to(forward, Vec3::Y),
        Mesh3d(meshes.add(Cuboid::new(0.2, 0.2, 1.0))),
        MeshMaterial3d(materials.add(StandardMaterial {
            base_color: Color::BLACK,
            emissive: color.to_linear(),
            ..default()
        })),
    ));
}
}

弾丸とキャラクターの衝突判定を行うシステムを作成します。

#![allow(unused)]
fn main() {
fn bullet_collision(
    mut commands: Commands,
    bullet_query: Query<(Entity, &Transform, &Bullet)>,
    mut character_query: Query<(&Transform, &Character, &mut HP)>,
) {
    for (bullet_entity, bullet_transform, bullet) in bullet_query {
        for (character_transform, character, mut hp) in character_query.iter_mut() {
            if *character == bullet.owner {
                continue;
            }
            let distance_sq = bullet_transform
                .translation
                .distance_squared(character_transform.translation);
            if distance_sq <= BULLET_COLLISION_RADIUS * BULLET_COLLISION_RADIUS {
                hp.0 -= bullet.damage;
                commands.entity(bullet_entity).despawn();
                break ;
            }
        }
    }
}
}

生存期間(Interval)が終了した弾丸を自動的に削除するシステムを作成します。

#![allow(unused)]
fn main() {
fn remove_time_out_bullet(
    mut commands: Commands,
    bullet_query: Query<(Entity, &super::utils::Interval), With<Bullet>>,
) {
    for (entity, interval) in bullet_query {
        if interval.is_ready() {
            commands.entity(entity).despawn();
        }
    }
}
}

BulletPlugin を作成し、これまで定義したシステムを登録します。

#![allow(unused)]
fn main() {
pub struct BulletPlugin;

impl Plugin for BulletPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(
            Update,
            (bullet_collision, move_bullet, remove_time_out_bullet).run_if(
                in_state(crate::state::GameState::Playing)
                    .and(in_state(super::InGameState::Running)),
            ),
        );
    }
}
}

作成した BulletPluginplaying/mod.rs に追加するのを忘れないようにしてください。

最後に、プレイヤーがスペースキーを押した時に弾丸を発射できるよう、playing/player.rs に発射システムを追加します。

#![allow(unused)]
fn main() {
fn shoot(
    mut commands: Commands,
    query: Query<(&Transform, &Character), With<Player>>,
    keyboard: Res<ButtonInput<KeyCode>>,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    for (transform, &owner) in query {
        if keyboard.just_pressed(KeyCode::Space) {
            bullet::spawn_bullet(
                &mut commands,
                owner,
                transform.translation,
                transform.forward(),
                &mut meshes,
                &mut materials,
            );
        }
    }
}
}

作成した shoot システムを PlayerPlugin にも登録しましょう。

#![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, shoot).run_if(
                    in_state(crate::state::GameState::Playing)
                        .and(in_state(super::InGameState::Running)),
                ),
            );
    }
}
}

これで弾丸の発射までできるようになりました。