玩家控制
本章我们来编写板子的控制逻辑。
Player组件
板子作为玩家操作的对象,我们可以创建一个组件Player,来标记这个对象,并且添加一个成员变量speed来设定控制的速度:
#[derive(Component, Edit, Default)]
struct Player {
move_speed: f32,
}
Player作为一个组件,首先需要实现Component。为了让我们的编辑器可以添加/删除/修改Player组件,需要实现Edit。实现了Edit组件也必须实现Default,这是为了编辑器添加这个组件时可以为其设置一个默认值。
Player组件写好了后,只需要在SteelApp中注册一下,就可以在编辑器里面使用了:
#[no_mangle]
pub fn create() -> Box<dyn App> {
SteelApp::new()
.add_plugin(Physics2DPlugin)
.register_component::<Player>()
.boxed()
}
完成代码修改后,点击顶部菜单的“Project -> Compile”按钮编译。编译完成后,就可以给我们的Board实体添加一个Player组件了,并设置move_speed为10,然后点击顶部菜单的“Scene -> Save”保存一下场景即可。
玩家控制系统player_control_system
接下来我们想要通过键盘改变Board实体的移动速度来操作它,那么它就不应该受到重力而下落,也不应该受到任何其他的力而改变速度,此时我们可以把它的RigidBody2D组件的body_type改成KinematicVelocityBased,这样可以确保它的速度由我们的代码控制。
实现系统player_control_system:
fn player_control_system(
player: View<Player>,
mut transform: ViewMut<Transform>,
rb2d: View<RigidBody2D>,
mut physics2d_manager: UniqueViewMut<Physics2DManager>,
input: UniqueView<Input>,
) {
for (player, transform, rb2d) in (&player, &mut transform, &rb2d).iter() {
if let Some(rb2d) = physics2d_manager.rigid_body_set.get_mut(rb2d.handle()) {
let mut linvel = Vec2::ZERO;
if input.key_held(VirtualKeyCode::Left) {
linvel = Vec2::new(-player.move_speed, 0.0);
} else if input.key_held(VirtualKeyCode::Right) {
linvel = Vec2::new(player.move_speed, 0.0);
}
rb2d.set_linvel(linvel.into(), true);
}
}
}
我们遍历所有存在Player组件、Transform组件和RigidBody2D组件的实体,场景中这样的实体一定是我们的Board,因此for循环应该只会执行一次。通过physics2d_manager.rigid_body_set.get_mut方法,传入RigidBody2D组件的handle,可以得到物理世界中的RigidBody对象。Input单例提供了读取当前的按键情况,其key_held函数可以查询当前某个键是否被按下了。如果当前键盘按下了左键或右键,我们通过RigidBody的set_linvel方法设置一个向左或向右的速度。这样就实现了对Board的操控。
我们将player_control_system放在Schedule::Update阶段运行即可,因为这个阶段会在编辑器中未运行游戏的时候跳过,我们不希望在游戏未运行的时候就能操控板子:
#[no_mangle]
pub fn create() -> Box<dyn App> {
SteelApp::new()
.add_plugin(Physics2DPlugin)
.register_component::<Player>()
.add_system(Schedule::Update, player_control_system)
.boxed()
}
此时尝试运行游戏,按键盘左右按钮,可以控制Board左右移动了。但是很快你会发现一个问题,Board可以移动到屏幕外。为了解决这个问题我们给Board的位置增加一个范围限制就好了:
fn player_control_system(
player: View<Player>,
mut transform: ViewMut<Transform>,
rb2d: View<RigidBody2D>,
mut physics2d_manager: UniqueViewMut<Physics2DManager>,
input: UniqueView<Input>,
) {
for (player, transform, rb2d) in (&player, &mut transform, &rb2d).iter() {
if let Some(rb2d) = physics2d_manager.rigid_body_set.get_mut(rb2d.handle()) {
...
if transform.position.x > 9.0 {
transform.position.x = 9.0
}
if transform.position.x < -9.0 {
transform.position.x = -9.0
}
}
}
}
适配安卓系统
player_control_system还有一个问题,如果我们的游戏运行在安卓手机上,一般来说安卓系统是没有连接键盘的,安卓系统习惯使用触屏来操作,我们还需要为安卓定制一套操作方式,这里我们做一个简单的实现,如果触摸了屏幕左半区域,则向左移动Board,如果触摸了屏幕右半区域,则向右移动Board:
fn player_control_system(
player: View<Player>,
mut transform: ViewMut<Transform>,
rb2d: View<RigidBody2D>,
mut physics2d_manager: UniqueViewMut<Physics2DManager>,
input: UniqueView<Input>,
egui_ctx: UniqueView<EguiContext>,
) {
for (player, transform, rb2d) in (&player, &mut transform, &rb2d).iter() {
if let Some(rb2d) = physics2d_manager.rigid_body_set.get_mut(rb2d.handle()) {
let mut linvel = Vec2::ZERO;
...
if steel::platform::BUILD_TARGET == BuildTarget::Android {
egui_ctx.input(|input| {
if let Some(press_origin) = input.pointer.press_origin() {
if press_origin.x < input.screen_rect.center().x {
linvel = Vec2::new(-player.move_speed, 0.0);
} else {
linvel = Vec2::new(player.move_speed, 0.0);
}
}
});
}
rb2d.set_linvel(linvel.into(), true);
...
}
}
}
我们使用常量steel::platform::BUILD_TARGET来判断当前是否在为安卓系统编译,如果是安卓系统则添加安卓系统的操作逻辑。我们这次使用了另一个单例EguiContext,来实现左右半屏的触屏判断,因为Input目前暂时还不支持触屏事件的处理。
在将游戏编译成安卓apk之前,需要安装安卓SDK,你可以通过安装Android Studio来安装安卓SDK。然后还需要执行以下命令安装cargo-ndk:
rustup target add aarch64-linux-android
cargo install cargo-ndk
插入可以adb调试的安卓手机,点击编辑器顶部菜单的“Project -> Export -> Android”即可编译到安卓手机上查看效果。