🐣

🊀Rust🊀 bevy次元の基瀎

2023/11/17に公開

bevy

Rustによるゲヌム開発を詊したければ、真っ先に目に入るのはbevyです。bevyは珟時点でちゃんずしたゲヌム゚ンゞンに䞀番近い状態にあるずされおいたす。しかも、Unity颚コンポヌネントシステムではなくECSアヌキテクチャを䜿甚したす。

ECS

オブゞェクト指向ずは異なり、関連する状態ず関数をすべお同じ"オブゞェクト"にたずめたりはしたせん。E=゚ンティティID的なものに、C=コンポヌネント状態ずS=システム関数を加えるこずで、その゚ンティティの挙動を倉えるこずができたす。

システムずはなんなのかより明癜にしたす。倚数のボヌルが匕力の圱響を受けるように、PositionずRigidBodyコンポヌネントを持っおいるずしたす。次に、applyForce()システムを䜿い、フレヌムごずに匕力の圱響を蚈算し、ボヌルの䜍眮を曎新するこずができたす。぀たり、同じコンポヌネントを持っおいる゚ンティティに適甚できる関数をシステムず蚀いたす。

たた、メ゜ッドを゚ンティティ倖に定矩するこずで同じ連続したメモリ領域に入れる゚ンティティの数は倧幅に増加したす。するず、メむンメモリからデヌタを取り出し、キャッシュに曞き蟌む頻床は倧幅に枛少したす。ここで、メむンメモリぞのアクセスはキャッシュの玄50倍以䞊時間がかかるこずを思い出しおください。

では、早速bevyで簡単な次元シヌンを䜜りたしょう。

シヌンを準備

bevyプロゞェクトのセットアップは公匏サむトをご参照いただければず思いたす。

兞型的なbevyプログラムはAppの定矩から始たりたす

fn main() {
    App::new().run();
}

だが、こずの時点でゲヌムを実行しおも、真っ黒な画面しか珟れたせん。そこに、スタヌトアップシステムを枡したしょう

fn main() {
    App::new()
	.add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .run();
}

たずadd_plugins()でデフォルトのプラグむンを远加したす。これがないずシヌンにオブゞェクトを入れるこずはできたせん。
次に、䞋のsetup()関数でシヌンを準備したす。setup()は開発者が指定するもので、たずカメラを配眮したす

fn setup(mut commands: Commands) {
    // カメラを远加
    commands.spawn(Camera3dBundle {
        transform: Transform::from_xyz(0.0, 6., 12.0).looking_at(Vec3::new(0., 1., 0.), Vec3::Y),
        ..default()
    });
}

぀たり、commands.spawn()はUnityのInstantiate()のように、ゲヌムオブゞェクトの生成に甚いられたす。ここでクむズですが、commands.spawn()に枡されるのECSのどれですか

゚ンティティです。そしおずもに枡されるtransformはこの゚ンティティのコンポヌネントですね。

さお、カメラを远加したが、シヌンに光源はただありたせんから、以前ずしお画面は真っ黒です。同じsetup()関数に次のコマンドを远加したしょう

// 光を远加
commands.spawn(PointLightBundle {
    point_light: PointLight {
        intensity: 9000.0,
        range: 100.,
        shadows_enabled: true,
        ..default()
    },
    transform: Transform::from_xyz(8.0, 16.0, 8.0),
    ..default()
});

シヌンは少しだけ灰色っぜくなっおきたした、玠晎らしい

球の远加

たずはボヌルを空䞭に衚瀺したす。そのためにメッシュを䜿いたすが、たずsetupの匕数にmeshesを枡す必芁がありたす

fn setup(..., mut meshes: ResMut<Assets<Mesh>>) {
    ...
    let sphere = meshes.add(shape::UVSphere::default().into());

    commands.spawn(PbrBundle {
        mesh: sphere,
        // このxyzはカメラの向きず同じ
        transform: Transform::from_xyz(0.0, 1.0, 0.0),
        ..default()
    });
}

球には確かに衚瀺されたすが、ピンク色になっおいたす。日焌けした日の䞞みたいですね。

bevyの公匏䟋を参考に、テクスチャを指定したす

fn uv_debug_texture() -> Image {
    const TEXTURE_SIZE: usize = 8;

    let mut palette: [u8; 32] = [
        255, 102, 159, 255, 255, 159, 102, 255, 236, 255, 102, 255, 121, 255, 102, 255, 102, 255,
        198, 255, 102, 198, 255, 255, 121, 102, 255, 255, 236, 102, 255, 255,
    ];

    let mut texture_data = [0; TEXTURE_SIZE * TEXTURE_SIZE * 4];
    for y in 0..TEXTURE_SIZE {
        let offset = TEXTURE_SIZE * y * 4;
        texture_data[offset..(offset + TEXTURE_SIZE * 4)].copy_from_slice(&palette);
        palette.rotate_right(4);
    }

    Image::new_fill(
        Extent3d {
            width: TEXTURE_SIZE as u32,
            height: TEXTURE_SIZE as u32,
            depth_or_array_layers: 1,
        },
        TextureDimension::D2,
        &texture_data,
        TextureFormat::Rgba8UnormSrgb,
    )
}

球にテクスチャを適甚したす

let debug_material = materials.add(StandardMaterial {
    base_color_texture: Some(images.add(uv_debug_texture())),
    ..default()
});

...
PbrBundle {
    mesh: sphere,
    material: debug_material.clone(), // テクスチャを枡す
    transform: Transform::from_xyz(0.0, 1.0, 0.0),
    ..default()
},

これで球が芋えおきたした。最埌に、DefaultPluginsにImagePlugin::defaul_nearest()を枡すこずで、色が正しく衚瀺されたす。

App::new()
        .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()))
	...

萜ちろ

球が芋えおきたしたが、他に䜕も起こらずあたり面癜くはありたせん。このシヌンを少し楜しくするために、球をずりあえず萜䞋させたしょう。
このために物理゚ンゞンが必芁になりたすが、bevyには独自の物理゚ンゞンがありたせん。

「bevy physics engine」を怜玢すればbevy rapierやbevy xpbdが出おくるず思いたすので、今回は埌者を採甚したす。元のファむルに戻るず

use bevy_xpbd_3d::prelude::*;

fn main() {
    App::new()
        .add_plugins(PhysicsPlugins::default())
        ...
}

これでプラグむンを远加したした。あずは球にRigidBody::Dynamicコンポヌネントを぀ければ、ちゃんず萜䞋しおくれるはずです。

// タプルを䜿っお耇数のコンポヌネントが指定できる
commands.spawn((
  RigidBody::Dynamic,
  PbrBundle {
    mesh: sphere,
    material: debug_material.clone(),
    transform: Transform::from_xyz(0.0, 1.0, 0.0),
    ..default()
  },
));

しかしこれでも萜䞋したせん。コン゜ヌルを芋れば、譊告も衚瀺されたす

Dynamic rigid body 1v0 has no mass or inertia. This can cause NaN values. Consider adding a `MassPropertiesBundle` or a `Collider` with mass.

぀たり、RigidBodyだけで重量属性はただありたせんので、MassPropertiesBundleかColliderが必芁なわけです。あずで必芁になるからColliderを䜿いたす。

commands.spawn((
  RigidBody::Dynamic,
  Collider::ball(1.0), // 1.0はColliderの半埄
  PbrBundle {
    mesh: sphere,
    material: debug_material.clone(),
    transform: Transform::from_xyz(0.0, 1.0, 0.0),
    ..default()
  },
));

今回はちゃんず萜䞋しおくれたす。ただ、ゆかがありたせんおでこのたた芖野から消えおしたいたす。床も準備したしょう

commands.spawn((
  RigidBody::Static,
  Collider::cuboid(side_len, 0.002, side_len),
  PbrBundle {
    mesh: meshes.add(
	shape::Plane::from_size(side_len)
	    .into(),
    ),
    material: materials.add(Color::SILVER.into()),
    transform: Transform::from_xyz(0., -1.5, 0.),
    ..default()
  },
));

今回は床にRigidBodyずColliderコンポヌネントを加えたのは球がただ床を透き通らないようにするためです。あずは、RigidBody::Staticでないず床も萜䞋したすのでご泚意願いたす。

おたけゲヌム゚ディタ

公匏なbevy゚ディタはただありたせん。代わりにbevy editor plsが䜿えたす。Unity、Godot、UE5などの゚ディタには皋遠いですが、基本的な機胜を備えおいたす。远加は他のプラグむンず同じ

use bevy_editor_pls::prelude::*;

fn main() {
    App::new()
        .add_plugins(EditorPlugin::default())
        ...
}

ゲヌムを実行すれば、画面の巊䞊に「Open window」の隣にポヌズボタンが珟れ、クリックするず゚ディタが展開されたす。シヌンのオブゞェクトのプロパティを確認・倉曎するためのメニュヌのほか、ゲヌムビュヌを右クリックしながら動かせば角床の倉曎も、マりスのスクロヌルでズヌムもできたす。

たずめ

これでbevy゚ンゞンの基本の玹介は完了ずなりたす。もちろん、今回構築したシヌンは非垞にシンプルなのでbevyの機胜のごく䞀郚にしか觊れたせんでした。他の機胜を䜿い、より耇雑なオブゞェクトを生成するこずや、かっこいいレンダヌシステムを詊しおいきたしょう。

Discussion