はじめに
先日Bevyのバージョンが0.7になり、仕様変更に戸惑っていたRemint20です。今回はマウスカーソルの位置(ワールド座標系)を受け取り、スプライトをマウス方向に向けるプログラムを紹介します。
完成品
セットアップ
マウスを取得するための簡単なセットアップをおこないましょう。ここではマウス位置を受け取る構造体と後程利用する関数を定義しておきます。
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.insert_resource(MousePos(Vec2::ZERO))
.add_startup_system(setup)
.add_system(cursor_system)
.run();
}
struct MousePos(Vec2);
fn setup(mut commands: Commands) {}
fn cursor_system(){}
カメラを設定
カーソルの位置を取得するにはカメラを設定しないといけません。今回は後でスプライトを表示するのでOrthographicCameraBundle::new_2d()
を指定します。カメラはプログラムが始まったときに設定してほしいのでsetup()
内で設定します。MainCamera
コンポーネントはcursor_system()
でカメラを取得するために使います。
#[derive(Component)]
struct MainCamera;
fn setup(mut commands: Commands, assert_server: Res<AssetServer>) {
commands
.spawn()
.insert_bundle(OrthographicCameraBundle::new_2d())
.insert(MainCamera);
}
マウスカーソル位置を取得
それではマウスカーソルを取得しましょう。最初に用意したcursor_system()
を以下のコードに変換します。
fn cursor_system(
wnds: Res<Windows>,
mut mouse_pos: ResMut<MousePos>,
camera_query: Query<(&Camera, &GlobalTransform), With<MainCamera>>,
) {
if let Ok((camera, camera_transform)) = camera_query.get_single() {
if let Some(wnd) = wnds.get_primary() {
// カメラがウィンドウ内にあるか確認して、その位置を取得する
if let Some(screen_pos) = wnd.cursor_position() {
// ウィンドウサイズの取得
let window_size = Vec2::new(wnd.width() as f32, wnd.height() as f32);
// 画面位置を[0..window_size] -> [-1..1](gpu座標)に変換します
let ndc = (screen_pos / window_size) * 2.0 - Vec2::ONE;
// 投影とカメラ変換を元に戻すための行列
let ndc_to_world =
camera_transform.compute_matrix() * camera.projection_matrix.inverse();
// ワールド座標に変換
let world_pos = ndc_to_world.project_point3(ndc.extend(-1.0));
// Vec3 -> Vec2に変換
let world_pos: Vec2 = world_pos.truncate();
eprintln!("World Position: x:{} y:{}", world_pos.x, world_pos.y);
// MousePos内に格納
mouse_pos.0 = world_pos;
}
}
}
}
カメラとウィンドウの取得
camera_query
ではCamera
とGlobalTransform
を受け取っています。また、With<MainCamera>
でMainCamera
コンポーネントを持つオブジェクトだけを指定しています。With<>はフィルターの意味もありますが、今回の場合はGlobalTransform
を受け取るためにも必要となります。
次にウィンドウの取得です。ウィンドウは基本的に一つしか使用しないと思われるのでget_primary()
で取得しています。もしほかのウィンドウを取得したい場合はget()
を使用するといいでしょう。その代わりにWindowId
を取得する必要が出てきますが...。
最後にscreen_pos
を受け取っています。取得したウィンドウの座標になります。
if let Ok((camera, camera_transform)) = camera_query.get_single() {
if let Some(wnd) = wnds.get_primary() {
// カメラがウィンドウ内にあるか確認して、その位置を取得する
if let Some(screen_pos) = wnd.cursor_position() {
...
ウィンドウサイズを所得
これは特に説明はいりませんね。ウィンドウの横幅と縦幅をVec2
に変換しているだけです。
let window_size = Vec2::new(wnd.width() as f32, wnd.height() as f32);
ウィンドウ座標 -> ワールド座標に変換
// [0..window_size](画面位置) -> [-1..1](gpu座標)に変換します
let ndc = (screen_pos / window_size) * 2.0 - Vec2::ONE;
// 投影とカメラ変換を元に戻すための行列
let ndc_to_world = camera_transform.compute_matrix() * camera.projection_matrix.inverse();
// ワールド座標に変換
let world_pos = ndc_to_world.project_point3(ndc.extend(-1.0));
はじめにウィンドウ座標をGPU座標に変換します。それぞれの座標は以下の図になります。
他のゲームエンジンやGPUライブラリでは左上が原点になっていることが多いですがBevyでは左下が原点になります。
最後にワールド座標に変換するための行列を用意して、それにGPU座標を入れて行列計算してもらいます。
これでマウスカーソルの位置をワールド座標系として取得することができます。
スプライトをカーソル方向に向ける
では取得したカーソル位置を利用してオブジェクトを回転させてみましょう。初めにスプライトを用意します。用意するスプライトはなんでも構いません。私はDOTOWNから取得しました。
setup
を以下のコードに変換して、Player
コンポーネントを追加しましょう。
#[derive(Component)]
struct Player;
fn setup(mut commands: Commands, assert_server: Res<AssetServer>) {
...
commands
.spawn_bundle(SpriteBundle {
transform: Transform {
translation: Vec3::new(0.0, 0.0, 0.0),
scale: Vec3::new(0.0625, 0.0625, 1.0),
..Default::default()
},
texture: assert_server.load("textures/うんちハニワ.png"),
..Default::default()
})
.insert(Player);
}
次にlook_at
関数を作成。ここではPlayerコンポーネントを持つオブジェクトをマウスカーソル方向に回転させる機能を持たせます。
これにはTransform
のlook_at
を使用しましょう。look_at(&mut self, target: Vec3, up: Vec3)
で宣言されているのでfrom
に向けたい方向、up
に回転したい軸を入れましょう。(実際はupはXでもYでも問題ない、from
とup
を逆にしても動きます。2次元の場合だけだと思うけど...)
fn look_at(mut query: Query<&mut Transform, With<Player>>, mouse_pos: Res<MousePos>) {
if let Ok(mut tf) = query.get_single_mut() {
tf.look_at(Vec3::Z, Vec3::from((mouse_pos.0, 0.)));
}
}
最後にapp
にadd_system()
として読み込みます。
App::new()
.add_plugins(DefaultPlugins)
.insert_resource(MousePos(Vec2::ZERO))
.add_startup_system(setup)
.add_system(cursor_system)
+ .add_system(look_at)
.run();
実行して確かめてみましょう。
今回使用したコードはGithubでも公開しています。
Bevyはまだまだ安定版が出ていません。なので大きな仕様変更があることに注意してください。