己の🦀を磨け
注
別にシンセやらドラムやらは一切出てきません 強いて言うならメロディです
いきあたりばったりな実装なので参考サイトの方から見てやっていくのがいいと思います。
とりあえず記録として残しておきたかったのでだいぶ適当に書いています。 そのうち書き直しと勉強してもう少しわかりやすい内容に更新する予定です。
作ったもの
下手なのは許してください
経緯
あるLTにてゲームを作った人がいたのでbevy
いじりがてら作ってみるかと至った次第です。
めっちゃいい内容なので皆さん見てください
bevy、ECSについて
Bevyとは、Rust製のゲームエンジンで、Rustでゲーム開発をするのであればこれでなんとかなる場合が多いと思います。
別にWebAssemblyでゲーム開発をすることもできますが、Assetも多いですし、様々な作成例が公開されたコードとともに転がっているのでBevyがおすすめです。
Bevyでのゲーム開発は、ECSと呼ばれる設計パターンに基づいて実装していきます。
一昔一世を風靡したオブジェクト指向のような継承ではなく、コンポジションを用いることにより、複雑な依存性を管理することなく開発を行うことができるので、データ指向の設計と相性がいいです
ほぼ受け売りなので各自調べていただけると幸いです
ざっくり作り方
何かしらのオブジェクトを作りたい場合、Bundleを使用して.spawn(Bundle {})
です
画面(初期状態)を作る
pub fn game_setup(mut commands: Commands) {
println!("Game Setup");
commands.spawn((
Camera3dBundle {
transform: Transform::from_xyz(20.0, 0.0, 50.0)
.looking_at(Vec3::new(10.0, 5.0, 0.0), Vec3::Y),
..Default::default()
},
ThirdPersonCamera,
));
commands.spawn(PointLightBundle {
point_light: PointLight {
intensity: 5000.0,
shadows_enabled: true,
..default()
},
transform: Transform::from_xyz(-3.0, 0.0, 10.0),
..default()
});
}
今回は3Dゲームなので、オブジェクトの表示のためにCamera3dBundle
、PointLightBundle
を用います。Unityの最初の画面みたいなもんだと思います。(エアプ)
数値に関しては視点やら光源の位置角度を指定するものですが、これはなんども調整しながら作ったので苦労しました。
オブジェクトを作る
ピアノの鍵盤をつくります。直方体ですが、それぞれ黒鍵・白鍵か、何番目なのか、などの情報を構造体で保持します。構造体はECSに基づいて#[derive(Component)]
しておいてデータのやり取りをしましょう。
commands.spawn((
PianoKey(index, PianoKeyType::White),
PianoKeyId(index),
PianoKeyType::White,
PbrBundle {
mesh: meshes.add(Mesh::from(shape::Box::new(
WHITE_KEY_WIDTH,
WHITE_KEY_HEIGHT,
WHITE_KEY_DEPTH,
))),
material: materials.add(Color::WHITE.into()),
transform: Transform::from_xyz(position_x, 0.0, 0.0),
..default()
},
));
PbrBundle
は、Phycical Base Rendering
のPBRで、bevy
ではmesh
とmaterial
で構成されるオブジェクトのことを指します。ここでは直方体を生成したいので、Mesh::from(shape::Box::new())
を用いて生成しています。
押された鍵盤をハイライトする
イベントを起こす
音を鳴らすときに、ピアノのオブジェクトからイベントを通知するためにPianoKeyInputEvent
を定義
#[derive(Event)]
でイベント構造体に出来、発火したい場所でsend()
しましょう
for key in key_events.iter() {
for (entity, key_id_component, key_type) in &key_entityies {
let PianoKeyId(key_id) = key_id_component;
if key.key_code == Some(KEBOARD_MAPPING[*key_id]) {
if let Ok(handle) = key_materials.get_mut(entity) {
if let Some(material) = materials.get_mut(&handle) {
let color: Color;
match key.state {
ButtonState::Pressed => {
color = Color::BLUE;
note_events.send(PianoKeyInputEvent {
key_id: *key_id,
key_state: ButtonState::Pressed,
});
}
ButtonState::Released => {
color = if key_type == &PianoKeyType::Black {
Color::BLACK
} else {
Color::WHITE
};
note_events.send(PianoKeyInputEvent {
key_id: *key_id,
key_state: ButtonState::Released,
});
}
};
material.base_color = color;
}
}
}
}
}
音を出す
// play the sound
for event in note_events.iter() {
match event.key_state {
ButtonState::Pressed => {
let truck = trucks.trucks.get(&event.key_id).unwrap();
let truck_handle = truck.clone_weak();
let truck_handle = truck_handle;
audio.play(truck_handle);
}
ButtonState::Released => {}
}
}
打鍵に反応して音がなるようにしています。
今後の展望
- 今のところ音源をアセットとして提供しているので、自分で音を録って来ている → 鍵盤数から周波数を割り当てて自動でアセットが生成できるようにしたい
- MIDIの動画でよくある鳴らした鍵盤から物体を出したい
余談: 数式を用いてピアノっぽい音をつくる
波といえば(数多くの音声処理ライブラリにおいて)正弦波が最も扱いやすいのですが、楽器として出る音が正弦波では味気ないので、数式でピアノの音を再現します。
let mut y = 0.0;
let a = 0.6 * (freq * t * 2.0 * PI as f32).sin() * (-0.0015 * freq * t).exp();
let b = a + 0.4 * (2.0 * freq * t * 2.0 * PI as f32).sin() * (-0.0015 * freq * t).exp();
let c = b.powi(3);
y += c * (1.0 + 16.0 * t * (-6.0 * t).exp());
(y * 0.5 + 0.5).try_into().unwrap()
おまけ: Unwelcome School冒頭の歌詞
n , ; / h
n , . , ; ; /
w 2 q ; . , m j m , . ; / c
n , ; / h
n , . , ; ; /
w 2 q ; / q 2 w 3 3 e e r
楽譜も何もかも分からずにメモしただけなので適当です。
GitHub
参考サイト
bevyは頻繁にアップデートにより推奨・非推奨のものが移り変わるので、必ず最新のバージョンを参照してください。
- 3Dオブジェクトの生成関連はこのリポジトリにお世話になりました バージョンが最新ではないのですこし非推奨の関数が混じっているので参考する際には注意してください。
- ここではMIDI機器がある前提なので音の再生やらキーイベントの取得は自分で考えることになったのでいい勉強になりました。
- 効果音扱いなので再生周りのイベント発火はExampleをもとにしました。
- Unwelcome Schoolの音はここみて頑張りました。
- ピアノの数式も自分でできたら良かったのですが、早く音が鳴らしたかったので先人の知恵をお借りしました
Bevy系
Rustは全体的に役に立つソースが少ないですが、それでも公式ドキュメントの充実度たるやという感じです。
Bevyに関しては非公式のチートブックもあるので、それも参照しながらやるといい感じに開発できるのでは無いでしょうか