🦀
chip9. フルスクリーン⇔ウィンドウ切替
はじめに
2023/08/15時点の内容です。
- rustc 1.71.1
- bevy 0.11.1
bevyは開発初期段階のOSSで、まだまだ破壊的なアップデートが入ります。
でも、面白いですよ。
0.11のバグフィックス版(0.11.1)がリリースれてました。
前回
閑話:ソースコード分割
前回のソースコードを改造して、キー・マウス・ゲームパッドによる極座標更新処理を別ファイルへ分離しました。
- main.rs:これまで通りメインの処理
- const_defs.rs:これまで通りマジックナンバーを書いておく
- spawn_objs.rs:これまで通りオブジェクトをspawnする関数を分離
-
catch_input.rs:キー・マウス・ゲームパッドで極座標を更新する関数を分離
srcに横並びに配置
ウィンドウモード
WindowMode::Windowed
がウインドウで、残りの3つはフルスクリーンです。
手元の環境ではWindowMode::SizedFullscreen
にすると縦横比が維持されて上下に黒帯が表示される状態でした。他の2つは画像がモニター全体に広がって縦横比が変化するようでした。
enum WindowMode | 先生たちによる翻訳 (DeepL先生 & Google先生) |
---|---|
Windowed | ウィンドウは、ウィンドウ解像度のサイズを使用して、画面の一部を占めるはずです。 |
BorderlessFullscreen | ウィンドウは、ボーダーレスで画面のフルサイズを使用することにより、フルスクリーンで表示されるはずです。 これを設定すると、ウィンドウの物理サイズが現在のモニター解像度のサイズに一致するように変更され、論理サイズはスケール係数に基づいて変更されます。「WindowResolution」を参照してください。 |
SizedFullscreen | ウィンドウは "true"/"legacy "フルスクリーンモードで表示されるはずです。 これを設定する場合、オペレーティング システムは、ウィンドウの物理サイズにできる限り一致させるために、現在のモニターで利用可能な最も近い解像度を使用するように要求されます。その後、ウィンドウの物理サイズがモニターの解像度に一致するように変更され、論理サイズはスケール係数に基づいて変更されます。「WindowResolution」を参照してください。 |
Fullscreen | ウィンドウは "true"/"legacy "フルスクリーンモードで表示されるはずです。 これを設定すると、オペレーティング システムは現在のモニターで利用可能な最大の解像度を使用するように要求されます。その後、ウィンドウの物理サイズがモニターの解像度に一致するように変更され、論理サイズはスケール係数に基づいて変更されます。「WindowResolution」を参照してください。 |
フルスクリーン⇔ウィンドウ切替
[Alt]+[Enter]キーでフルスクリーンとウィンドウを切り替えます。ゲームパッドの場合は[Select]ボタンで切り替えます(PS4 Controllerなら[SHARE]ボタンが該当)。
フルスクリーンの動画です
(切替の瞬間はスクショ撮れず)
main.rs
//external crates
use bevy::
{ prelude::*,
render::*, render::settings::*,
core_pipeline::clear_color::*,
input::mouse::*,
window::WindowMode::*,
};
//standard library
use std::f32::consts::*;
//internal submodules
mod spawn_objs;
mod const_defs;
use const_defs::*;
mod catch_input;
//------------------------------------------------------------------------------
fn main()
{ //Note:手元の環境だとVulkanのままでは影が描画されなかったので、DX12へ切り替えた。
let backends = Some ( Backends::DX12 );
let wgpu_settings = WgpuSettings { backends, ..default() };
let backend_dx12 = RenderPlugin { wgpu_settings };
App::new()
//DefaultPluginsに各種の面倒を見てもらう
.add_plugins
( DefaultPlugins
//Note:この行をコメントアウトするとデフォルトのbackend
.set( backend_dx12 )
)
//各種オブジェクトを作成する
.add_systems
( Startup,
( spawn_objs::camera3d_and_light, //3Dカメラとライト
spawn_objs::locked_chest, //3Dオブジェクト(宝箱)
spawn_objs::camera2d, //2Dカメラ(情報表示用)
spawn_objs::display_board, //UIテキスト(情報表示用)
)
)
//メインルーチンを登録する
.add_systems
( Update,
( ( ( catch_input::from_keyboard, //極座標を更新(キー入力)
catch_input::from_mouse, //極座標を更新(マウス)
catch_input::from_gamepad, //極座標を更新(ゲームパッド)
),
move_orbit_camera, //極座標カメラを移動
)
.chain(), //実行順を固定
bevy::window::close_on_esc, //[ESC]キーで終了
toggle_window_mode, //ウィンドウとフルスクリーンの切換
show_parameter, //情報を表示
)
)
//アプリを実行する
.run();
}
//------------------------------------------------------------------------------
//極座標の型
#[derive( Clone, Copy )]
struct Orbit
{ r : f32, //極座標のr(注目点からカメラまでの距離)
theta: f32, //極座標のΘ(注目点から見たカメラの垂直角度)
phi : f32, //極座標のφ(注目点から見たカメラの水平角度)
}
//極座標から直交座標へ変換するメソッド
impl Orbit
{ fn into_vec3( self ) -> Vec3
{ let x = self.r * self.theta.sin() * self.phi.sin();
let y = self.r * self.theta.cos() * -1.0;
let z = self.r * self.theta.sin() * self.phi.cos();
Vec3::new( x, y, z )
}
}
//------------------------------------------------------------------------------
//極座標カメラに付けるComponent
#[derive( Component )]
pub struct OrbitCamera { orbit: Orbit }
//極座標カメラの初期位置
impl Default for OrbitCamera
{ fn default() -> Self
{ Self
{ orbit: Orbit
{ r : ORBIT_CAMERA_INIT_R,
theta: ORBIT_CAMERA_INIT_THETA,
phi : ORBIT_CAMERA_INIT_PHI,
}
}
}
}
//UIテキストに付けるComponent
#[derive( Component )]
struct DisplayBoard;
//------------------------------------------------------------------------------
//ウィンドウとフルスクリーンの切換(トグル動作)
pub fn toggle_window_mode
( mut q_window: Query<&mut Window>,
inkey: Res<Input<KeyCode>>,
inbtn: Res<Input<GamepadButton>>,
gamepads: Res<Gamepads>,
)
{ let Ok( mut window ) = q_window.get_single_mut() else { return };
//[Alt]+[Enter]キーの状態
let is_key_pressed =
( inkey.pressed( KeyCode::AltRight ) || inkey.pressed( KeyCode::AltLeft ) )
&& inkey.just_pressed( KeyCode::Return );
//ゲームパッドは抜き挿しでIDが変わるので.iter()で回す
let button_type = GamepadButtonType::Select; //ps4[SHARE]
let mut is_gp_button_pressed = false;
for gamepad in gamepads.iter()
{ let button = GamepadButton { gamepad, button_type };
is_gp_button_pressed = inbtn.just_pressed( button );
if is_gp_button_pressed { break }
}
//入力がないなら
if ! is_key_pressed && ! is_gp_button_pressed { return }
//ウィンドウとフルスクリーンを切り替える
window.mode = match window.mode
{ Windowed => SizedFullscreen, //or BorderlessFullscreen, Fullscreen
_ => Windowed,
};
}
//------------------------------------------------------------------------------
//極座標カメラを動かす
fn move_orbit_camera
( mut q_camera: Query<( &OrbitCamera, &mut Transform )>,
)
{ let Ok ( ( camera, mut transform ) ) = q_camera.get_single_mut() else { return };
//カメラの位置と向きを更新する
let translation = camera.orbit.into_vec3();
*transform = Transform::from_translation( translation )
.looking_at( Vec3::ZERO, Vec3::Y );
}
//------------------------------------------------------------------------------
//極座標の情報を表示する
fn show_parameter
( mut q_text: Query<&mut Text, With<DisplayBoard>>,
q_camera: Query<&OrbitCamera>,
gamepads: Res<Gamepads>,
)
{ let Ok ( mut text ) = q_text.get_single_mut() else { return };
let Ok ( camera ) = q_camera.get_single() else { return };
let orbit = &camera.orbit;
//極座標の情報
let r = orbit.r;
let theta = orbit.theta.to_degrees(); //ラジアンから度へ変換
let phi = orbit.phi.to_degrees(); //ラジアンから度へ変換
let info = format!( " r:{r:3.02}\n theta:{theta:06.02}\n phi:{phi:06.02}" );
//ゲームパッドの接続状態
let mut pads = "\n Gamepads:".to_string();
for gamepad in gamepads.iter()
{ let Some ( name ) = gamepads.name( gamepad ) else { continue };
pads = format!( "{pads}\n - ID:{} {}", gamepad.id, name );
}
//表示の更新
text.sections[ 0 ].value = format!( "{info}{pads}" );
}
const_defs.rs
const_defs.rs
use super::*;
//2Dカメラの画像を3Dカメラの画像の上にのせる(レンダリングの順位)
pub const CAMERA2D_ORDER: isize = 1;
pub const CAMERA3D_ORDER: isize = 0;
//2Dカメラの画像の背景を透過させる
pub const CAMERA2D_BGCOLOR: ClearColorConfig = ClearColorConfig::None;
//光源
pub const LIGHT_BRIGHTNESS: f32 = 15000.0; //明るさ
pub const LIGHT_POSITION: Vec3 = Vec3::new( 30.0, 100.0, 40.0 ); //位置
//UIテキスト
pub const UI_TEXT_FONT_SIZE: f32 = 50.0;
//極座標カメラの設定値
pub const ORBIT_CAMERA_INIT_R : f32 = 3.0; //初期値
pub const ORBIT_CAMERA_INIT_THETA: f32 = PI * 0.7; //初期値(ラジアン)
pub const ORBIT_CAMERA_INIT_PHI : f32 = 0.0; //初期値(ラジアン)
pub const ORBIT_CAMERA_MAX_R : f32 = 5.0; //最大値
pub const ORBIT_CAMERA_MIN_R : f32 = 1.0; //最小値
pub const ORBIT_CAMERA_MAX_THETA: f32 = PI * 0.99; //最大値(ラジアン)
pub const ORBIT_CAMERA_MIN_THETA: f32 = PI * 0.51; //最小値(ラジアン)
//マウスからの入力値の感度調整用係数
pub const MOUSE_WHEEL_Y_COEF : f32 = 0.1;
pub const MOUSE_MOTION_Y_COEF: f32 = 0.01;
pub const MOUSE_MOTION_X_COEF: f32 = 0.01;
spawn_objs.rs
spawn_objs.rs
use super::*;
//3Dカメラと光源を作る
pub fn camera3d_and_light( mut cmds: Commands )
{ //3Dカメラ
let orbit_camera = OrbitCamera::default();
let vec3 = orbit_camera.orbit.into_vec3();
cmds.spawn( ( Camera3dBundle::default(), orbit_camera ) )
.insert( Camera { order: CAMERA3D_ORDER, ..default() } )
.insert
( Transform::from_translation( vec3 ) //カメラの位置
.looking_at( Vec3::ZERO, Vec3::Y ) //カメラレンズの向き
);
//光源
let light = DirectionalLight
{ illuminance: LIGHT_BRIGHTNESS,
shadows_enabled: true, //影の描画を有効化
..default()
};
cmds.spawn( DirectionalLightBundle::default() )
.insert( light )
.insert
( Transform::from_translation( LIGHT_POSITION ) //光源の位置
.looking_at( Vec3::ZERO, Vec3::Z ) //光源の向き
);
}
//------------------------------------------------------------------------------
//2Dカメラを作る
pub fn camera2d( mut cmds: Commands )
{ cmds.spawn( Camera2dBundle::default() )
.insert( Camera { order: CAMERA2D_ORDER, ..default() } )
.insert( Camera2d { clear_color: CAMERA2D_BGCOLOR } );
}
//UIテキストを作る
pub fn display_board( mut cmds: Commands )
{ let textstyle = TextStyle { font_size: UI_TEXT_FONT_SIZE, ..default() };
let text = Text::from_section( "", textstyle ); //placeholderのみ
cmds.spawn( ( TextBundle { text, ..default() }, DisplayBoard ) );
}
//------------------------------------------------------------------------------
//3Dオブジェクトを作る(宝箱)
pub fn locked_chest
( mut cmds: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
)
{ //地面
cmds.spawn( PbrBundle::default() )
.insert( meshes.add( shape::Plane::from_size( 2.0 ).into() ) )
.insert( Transform::from_translation( Vec3::ZERO ) )
.insert( materials.add( Color::rgb( 0.5, 0.7, 0.3 ).into() ) );
//宝箱
cmds.spawn( PbrBundle::default() )
.insert( materials.add( Color::NONE.into() ) ) //透明
.insert( Transform::from_translation( Vec3::new( 0.0, 0.5, 0.0 ) ) )
.with_children
( | cmds |
{ //本体
let shape_box = shape::Box::new( 0.7, 0.3, 0.4 );
cmds.spawn( PbrBundle::default() )
.insert( meshes.add( shape_box.into() ) )
.insert( Transform::from_translation( Vec3::Y * -0.35 ) )
.insert( materials.add( Color::MAROON.into() ) );
//上蓋
let shape_cylinder = shape::Cylinder { height: 0.695, radius: 0.195, ..default() };
cmds.spawn( PbrBundle::default() )
.insert( meshes.add( shape_cylinder.into() ) )
.insert
( Transform::from_translation( Vec3::Y * -0.2 )
.with_rotation( Quat::from_rotation_z( PI * 0.5 ) )
)
.insert( materials.add( Color::MAROON.into() ) );
//錠前
let shape_cube = shape::Cube::new( 0.1 );
cmds.spawn( PbrBundle::default() )
.insert( meshes.add( shape_cube.into() ) )
.insert( Transform::from_translation( Vec3::Y * -0.2 + Vec3::Z * 0.17 ) )
.insert( materials.add( Color::GRAY.into() ) )
.with_children
( | cmds |
{ //鍵穴
let cylinder = shape::Cylinder { height: 0.11, radius: 0.01, ..default() };
cmds.spawn( PbrBundle::default() )
.insert( meshes.add( cylinder.into() ) )
.insert
( Transform::from_translation( Vec3::Y * 0.02 )
.with_rotation( Quat::from_rotation_x( PI * 0.5 ) )
)
.insert( materials.add( Color::BLACK.into() ) );
let shape_box = shape::Box::new( 0.01, 0.04, 0.11 );
cmds.spawn( PbrBundle::default() )
.insert( meshes.add( shape_box.into() ) )
.insert( Transform::from_translation( Vec3::Y * 0.0 ) )
.insert( materials.add( Color::BLACK.into() ) );
}
);
}
);
}
catch_input.rs
catch_input.rs
use super::*;
//ゲームパッドによって極座標カメラの位置を更新する
pub fn from_gamepad
( mut q_camera: Query<&mut OrbitCamera>,
time: Res<Time>,
axis_button: Res<Axis<GamepadButton>>,
axis_stick : Res<Axis<GamepadAxis>>,
gamepads: Res<Gamepads>,
)
{ let Ok ( mut camera ) = q_camera.get_single_mut() else { return };
let orbit = &mut camera.orbit;
let time_delta = time.delta().as_secs_f32(); //前回の実行からの経過時間
//ゲームパッドは抜き挿しでIDが変わるので.iter()で回す
for gamepad in gamepads.iter()
{ //左トリガーでズームイン
let button_type = GamepadButtonType::LeftTrigger2;
let button = GamepadButton { gamepad, button_type };
if let Some ( value ) = axis_button.get( button )
{ orbit.r -= value * time_delta;
orbit.r = orbit.r.max( ORBIT_CAMERA_MIN_R );
}
//右トリガーでズームアウト
let button_type = GamepadButtonType::RightTrigger2;
let button = GamepadButton { gamepad, button_type };
if let Some ( value ) = axis_button.get( button )
{ orbit.r += value * time_delta;
orbit.r = orbit.r.min( ORBIT_CAMERA_MAX_R );
}
//左スティックのY軸で上下首振り
let axis_type = GamepadAxisType::LeftStickY;
let stick_y = GamepadAxis { gamepad, axis_type };
if let Some ( value ) = axis_stick.get( stick_y )
{ orbit.theta += value * time_delta;
orbit.theta = orbit.theta
.min( ORBIT_CAMERA_MAX_THETA )
.max( ORBIT_CAMERA_MIN_THETA );
}
//左スティックのX軸で左右回転
let axis_type = GamepadAxisType::LeftStickX;
let stick_x = GamepadAxis { gamepad, axis_type };
if let Some ( value ) = axis_stick.get( stick_x )
{ orbit.phi -= value * time_delta;
orbit.phi -= if orbit.phi >= TAU { TAU } else { 0.0 };
orbit.phi += if orbit.phi < 0.0 { TAU } else { 0.0 };
}
}
}
//------------------------------------------------------------------------------
//マウス入力によって極座標カメラの位置を更新する
pub fn from_mouse
( mut q_camera: Query<&mut OrbitCamera>,
mouse_nutton: Res<Input<MouseButton>>,
mut e_mouse_motion: EventReader<MouseMotion>,
mut e_mouse_wheel: EventReader<MouseWheel>,
)
{ let Ok ( mut camera ) = q_camera.get_single_mut() else { return };
let orbit = &mut camera.orbit;
//ホイール
for mouse_wheel in e_mouse_wheel.iter()
{ orbit.r += mouse_wheel.y * MOUSE_WHEEL_Y_COEF; //感度良すぎるので
orbit.r = orbit.r
.min( ORBIT_CAMERA_MAX_R )
.max( ORBIT_CAMERA_MIN_R );
}
//右ボタンが押されていないなら
if ! mouse_nutton.pressed( MouseButton::Left ) { return }
//マウスの上下左右
for mouse_motion in e_mouse_motion.iter()
{ //上下首振り
orbit.theta += mouse_motion.delta.y * MOUSE_MOTION_Y_COEF; //感度良すぎるので
orbit.theta = orbit.theta
.min( ORBIT_CAMERA_MAX_THETA )
.max( ORBIT_CAMERA_MIN_THETA );
//左右回転
orbit.phi -= mouse_motion.delta.x * MOUSE_MOTION_X_COEF; //感度良すぎるので
orbit.phi -= if orbit.phi >= TAU { TAU } else { 0.0 };
orbit.phi += if orbit.phi < 0.0 { TAU } else { 0.0 };
}
}
//------------------------------------------------------------------------------
//キー入力によって極座標カメラの位置を更新する
pub fn from_keyboard
( mut q_camera: Query<&mut OrbitCamera>,
time: Res<Time>,
inkey: Res<Input<KeyCode>>,
)
{ let Ok ( mut camera ) = q_camera.get_single_mut() else { return };
let orbit = &mut camera.orbit;
let time_delta = time.delta().as_secs_f32(); //前回の実行からの経過時間
for keycode in inkey.get_pressed()
{ match keycode
{ KeyCode::Z =>
orbit.r = ( orbit.r + time_delta ).min( ORBIT_CAMERA_MAX_R ),
KeyCode::X =>
orbit.r = ( orbit.r - time_delta ).max( ORBIT_CAMERA_MIN_R ),
KeyCode::Up =>
orbit.theta = ( orbit.theta + time_delta ).min( ORBIT_CAMERA_MAX_THETA ),
KeyCode::Down =>
orbit.theta = ( orbit.theta - time_delta ).max( ORBIT_CAMERA_MIN_THETA ),
KeyCode::Right =>
{ orbit.phi -= time_delta;
orbit.phi += if orbit.phi < 0.0 { TAU } else { 0.0 };
}
KeyCode::Left =>
{ orbit.phi += time_delta;
orbit.phi -= if orbit.phi >= TAU { TAU } else { 0.0 };
}
_ => (),
}
}
}
Discussion