2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

🦀ひとりRustとBevyでゲーム開発🕊️Advent Calendar 2024

Day 6

【Rustのまほう2】#6 プレイヤーキャラクターのエンティティ生成

Last updated at Posted at 2024-12-05

前回はプログラミング要素が少なめだったので、今回はBevyのECSアーキテクチャをちょっとだけ深掘りしたいと思います。

システム

Bevyにおけるシステムは、指定した特定のタイミングで呼ばれる関数です。誤解を恐れずにオブジェクト指向プログラミングに例えると、イベントに登録されたイベントハンドラ、イベントリスナーみたいなものです。

たとえば、タイトル画面をクリックしてゲームの本編が始まったときには、プレイヤーキャラクターのスプライト(画像)を表示したり、背景の画像を並べたりする必要があります。まずはそのときに呼ばれるシステム(つまり関数)setup_levelを次のように定義します。

fn setup_level(
    mut commands: Commands,
    ...
) {
 ...
}

それから、ゲーム全体を表すapp.add_systemsを呼んで、setup_levelを登録します。ここでは、ゲーム本編GameState::InGameが始まったとき(OnEnter)に呼ばれる、ということを指定しています。

app.add_systems(OnEnter(GameState::InGame), setup_level);

OnEnter以外にも、さまざまなスケジュールが定義されています。Cheat Bookが参考になります。

コマンドとspawn

上の関数はcommandsという引数をとっていますが、これはゲーム内にエンティティを生成するときに使うもっとも基本的なシステムパラメータです。このCommandsにはBevyのワールドに変更を加えるためのメソッドがいくつも定義されていて、たとえばcommands.spawnを呼ぶと、ゲーム内にエンティティを生成できます。commands.spawnは引数にタプルをとるので、エンティティに含めたいコンポーネントを最大16個まで指定できます。

以下は私が作っているゲームでプレイヤーキャラクターのエンティティを生成している実際のコードです。

commands.spawn((
    Name::new("witch"),
    StateScoped(GameState::InGame),
    Actor {
        uuid,
        spell_delay: 0,
        mana: 1000,
        max_mana: 1000,
        life,
        max_life,
        pointer: Vec2::from_angle(angle),
        intensity,
        move_direction: Vec2::ZERO,
        move_force: PLAYER_MOVE_FORCE,
        fire_state: ActorFireState::Idle,
        group: WITCH_GROUP,
        filter: ENTITY_GROUP | WALL_GROUP | WITCH_GROUP | ENEMY_GROUP,
        current_wand: 0,
        effects: default(),
        wands,
    },
    ActorState::default(),
    Witch,
    controller,
    EntityDepth,
    Breakable {
        life: 0,
        amplitude: 0.0,
    },
    Transform::from_translation(position.extend(1.0)),
    GlobalTransform::default(),
    InheritedVisibility::default(),
    (
        RigidBody::Dynamic,
        Velocity::default(),
        Collider::ball(WITCH_COLLIDER_RADIUS),
        GravityScale(0.0),
        LockedAxes::ROTATION_LOCKED,
        Damping {
            linear_damping: 6.0,
            angular_damping: 1.0,
        },
        ExternalForce::default(),
        ExternalImpulse::default(),
        CollisionGroups::new(
            WITCH_GROUP,
            ENTITY_GROUP
                | WALL_GROUP
                | WITCH_GROUP
                | WITCH_BULLET_GROUP
                | ENEMY_GROUP
                | ENEMY_BULLET_GROUP
                | MAGIC_CIRCLE_GROUP,
        ),
    ),
));

エンティティの名前を指定するコンポーネントName::new("witch")や、座標を指定するコンポーネントTransform、物理エンジンでの物体を表すコンポーネントRigidBody::Dynamicなど、さまざまなコンポーネントが詰め込まれているのがわかると思います。

実はこれでもまだ一部で、このエンティティの子として次のような別のエンティティを生成しています。

  • プレイヤーキャラクターの画像。ダメージを受けた時に震える演出をするため、画像は子のコンポーネントにしています
  • プレイヤーキャラクターの持つ杖の画像。杖はマウスカーソルに向かって回転して表示されるので、これも子のコンポーネントにして回転できるようにしています
  • プレイヤーキャラクターの名前。オンライン時にキャラクターを見分けられるように、名前を表示できるようにしています
  • 体力バー。キャラクターの残り体力を表示するバーも子のエンティティにしています

ちなみにコンポーネントを16個以上指定したい場合は、タプルをネストすればいくらでも追加できたりしますし、いったんエンティティを生成してから、あとからinsertでさらにコンポーネントを動的に追加することもできます。上のコードでは16個では足りなくなったので、タプルをネストしているのがわかると思います。

それでアニメーションとか、マウスカーソルの位置に応じた回転とかいろいろ頑張って、次のような感じになりました(わかりやすいよう極端に拡大表示してます)。

magiaforge 0.1 2024-12-04 01-26-15.gif

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?