LoginSignup
5
1

More than 1 year has passed since last update.

Bevyでマウス位置を取得する

Posted at

はじめに

先日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ではCameraGlobalTransformを受け取っています。また、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座標.png

他のゲームエンジンや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コンポーネントを持つオブジェクトをマウスカーソル方向に回転させる機能を持たせます。
これにはTransformlook_atを使用しましょう。look_at(&mut self, target: Vec3, up: Vec3)で宣言されているのでfromに向けたい方向、upに回転したい軸を入れましょう。(実際はupはXでもYでも問題ない、fromupを逆にしても動きます。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.)));
    }
}

最後にappadd_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はまだまだ安定版が出ていません。なので大きな仕様変更があることに注意してください。

5
1
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
5
1