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/enemy.rs を作成し、Enemy コンポーネントと関連する定数を定義します。

#![allow(unused)]
fn main() {
#[derive(Component)]
pub struct Enemy;

const SHOOT_RATE: i32 = 3;
const SHOOT_CHANCE_MAX: i32 = 10;
const SHOOT_INTERVAL: f32 = 0.3;
const ENEMY_MAX_HP: f32 = 100.0;

const ENEMY_SPAWN_X_RANGE: std::ops::RangeInclusive<i32> = -9..=9;
const ENEMY_SPAWN_Y: f32 = 0.0;
const ENEMY_SPAWN_Z: f32 = 10.0;

const ENEMY_SPEED_LIMIT: f32 = 2.0;
}

敵を生成するための spawn_enemy 関数を定義します。

#![allow(unused)]
fn main() {
fn spawn_enemy(
    commands: &mut Commands,
    meshes: &mut Assets<Mesh>,
    materials: &mut Assets<StandardMaterial>,
    translation: Vec3,
) {
    let mut rng = rand::rng();
    commands.spawn((
        DespawnOnExit(crate::state::GameState::Playing),
        Character::Enemy,
        Enemy,
        super::utils::Interval {
            time: 0.0,
            interval: SHOOT_INTERVAL,
        },
        HP(ENEMY_MAX_HP),
        Transform::from_translation(translation).looking_to(-Vec3::Z, Vec3::Y),
        Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
        MeshMaterial3d(materials.add(StandardMaterial {
            base_color: Color::BLACK,
            emissive: Color::srgb(1.0, 0.1, 0.1).to_linear(),
            ..default()
        })),
        Control {
            speed_limit: ENEMY_SPEED_LIMIT,
            mass: 1.0,
            velocity: Vec3 {
                x: rng.random_range(-1.0..1.0),
                y: 0.0,
                z: rng.random_range(-1.0..0.0),
            },
            ..default()
        },
    ));
}
}

また、ランダムな位置へのスポーン関数と、ゲーム開始時の初期配置を行うシステムを定義します。 ランダムスポーンは、敵が撃破された際や画面外へ消えた際のリスポーンに使用します。

乱数生成を行うために、rand クレートを追加しましょう。Cargo.toml[dependencies] に以下を追記します。

rand = "0.10.1"
#![allow(unused)]
fn main() {
use rand::prelude::*;
pub fn spawn_random_enemy(
    commands: &mut Commands,
    meshes: &mut Assets<Mesh>,
    materials: &mut Assets<StandardMaterial>,
) {
    let mut rng = rand::rng();
    let x = rng.random_range(ENEMY_SPAWN_X_RANGE);
    spawn_enemy(
        commands,
        meshes,
        materials,
        Vec3 {
            x: x as f32,
            y: ENEMY_SPAWN_Y,
            z: ENEMY_SPAWN_Z,
        },
    );
}

fn setup_enemies(
    mut commands: Commands,
    mut materials: ResMut<Assets<StandardMaterial>>,
    mut meshes: ResMut<Assets<Mesh>>,
) {
    for i in -3..=3 {
        spawn_enemy(
            &mut commands,
            &mut meshes,
            &mut materials,
            Vec3 {
                x: (i * 3) as f32,
                y: ENEMY_SPAWN_Y,
                z: ENEMY_SPAWN_Z,
            },
        );
    }
}
}

画面外(移動範囲外)に出た敵を削除し、代わりに新しい敵をランダムな位置にスポーンさせます。

#![allow(unused)]
fn main() {
const ENEMY_X_LIMIT: f32 = 19.0;
const ENEMY_NEG_X_LIMIT: f32 = -19.0;
const ENEMY_Z_LIMIT: f32 = 15.0;
const ENEMY_NEG_Z_LIMIT: f32 = -6.0;

fn delete_out_of_range_enemy(
    query: Query<(&Transform, Entity), With<Enemy>>,
    mut commands: Commands,
    mut materials: ResMut<Assets<StandardMaterial>>,
    mut meshes: ResMut<Assets<Mesh>>,
) {
    for (translation, entity) in query
        .iter()
        .map(|(transform, entity)| (transform.translation, entity))
    {
        let x = translation.x;
        let z = translation.z;
        if !(ENEMY_NEG_X_LIMIT..=ENEMY_X_LIMIT).contains(&x)
            || !(ENEMY_NEG_Z_LIMIT..=ENEMY_Z_LIMIT).contains(&z)
        {
            commands.entity(entity).despawn();
            spawn_random_enemy(&mut commands, &mut meshes, &mut materials);
        }
    }
}
}

敵が一定間隔で一定確率で弾丸を発射するシステムを作成します。

#![allow(unused)]
fn main() {
fn enemy_shoot(
    mut commands: Commands,
    query: Query<(&Transform, &Character, &mut super::utils::Interval), With<Enemy>>,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    for (transform, &owner, mut interval) in query {
        let mut rng = rand::rng();
        let random_val = rng.random_range(0..SHOOT_CHANCE_MAX);
        let is_ready = interval.is_ready();
        if is_ready {
            interval.reset();
        }
        if is_ready && random_val <= SHOOT_RATE {
            super::bullet::spawn_bullet(
                &mut commands,
                owner,
                transform.translation,
                transform.forward(),
                &mut meshes,
                &mut materials,
            );
        }
    }
}
}

作成したシステムを EnemyPlugin にまとめ、アプリケーションに登録します。

#![allow(unused)]
fn main() {
use super::utils::*;
use bevy::prelude::*;
use rand::prelude::*;
pub struct EnemyPlugin;

impl Plugin for EnemyPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(OnEnter(crate::state::GameState::Playing), setup_enemies)
            .add_systems(
                Update,
                (enemy_shoot, delete_out_of_range_enemy).run_if(
                    in_state(crate::state::GameState::Playing)
                        .and(in_state(super::InGameState::Running)),
                ),
            );
    }
}
}