随着项目的发展,为了整个项目更加模块化和可扩展性、健壮性。我们可以把我们得游戏逻辑其拆分为Plugin。
就像我们写代码使用的VS Code ,IDEA 等开发工具一样,我们可以通过插件扩展工具的功能,而插件之间又不会互相影响,可以随着我们的喜好增加删除插件。Bevy正是按照该思路拓展的。
声明插件
rust
// 声明一个插件结构体
struct PlanetPlugin;
// 实现一个插件的trait
impl Plugin for PlanetPlugin {
fn build(&self, app: &mut App) {
// logic code...
}
}
我们需要声明一个结构体PlanetPlugin
用来表示插件,然后实现Plugin
trait
来编写插件相关的逻辑。仔细观察Plugin
trait
,它需要我们实现一个build
接口,该接口我们能够获取到一个App
对象,正如我们之前在main
函数中做的那样,当我们有了App
之后,我们就可以插入系统、添加新的插件等一些操作。
实现逻辑
在之前的Bevy Sprite文章中,我们学会了如何简单操作一个2D精灵,我们按照这个思路,实现一个简单的Plugin。
rust
// 定义移动速度
const SPEED: f32 = 800.0;
// 定义星球的结构体
#[derive(Component)]
struct Planet {
// 星球的名字,和资源名称绑定
name: String,
// 尺寸,因为是正方形,所以一个数值即可
size: u32,
// 初始化位置
init_pos: (f32, f32),
// 定义移动的键码,左上右下的顺序
moving: (KeyCode, KeyCode, KeyCode, KeyCode),
}
// 星球插件结构体
struct PlanetPlugin;
// 结构体实现
impl Plugin for PlanetPlugin {
fn build(&self, app: &mut App) {
// 添加一个观察者
app.add_observer(ob_planet)
// 添加一个移动星球的系统
.add_systems(Update, moving_planet);
}
}
// 移动星球
fn moving_planet(
time: Res<Time>,
keyboard_input: Res<ButtonInput<KeyCode>>,
mut sprite_query: Query<(&mut Transform, &Sprite, &Planet)>,
) {
let delta_time = time.delta_secs();
sprite_query.iter_mut().for_each(|(mut t, s, p)| {
let mut x = 0f32;
let mut y = 0f32;
if keyboard_input.pressed(p.moving.1) {
y = 1f32;
} else if keyboard_input.pressed(p.moving.3) {
y = -1f32;
}
if keyboard_input.pressed(p.moving.0) {
x = -1f32;
} else if keyboard_input.pressed(p.moving.2) {
x = 1f32;
}
Vec3::new(x, y, 0.0).try_normalize().map(|v| {
t.translation += v * delta_time * SPEED;
});
})
}
// 观察者用于观察Planet的添加,当用户添加Planet的时候,我们就添加新的 Sprite 以及 Transform。
fn ob_planet(
trigger: Trigger<OnAdd, Planet>,
query: Query<&Planet>,
mut commands: Commands,
asset_server: Res<AssetServer>,
) {
let entity = trigger.entity();
let _ = query.get(entity).map(|planet| {
commands.entity(entity).insert((
Sprite {
image: asset_server.load(format!("{}.png", planet.name)),
custom_size: Some(Vec2::new(planet.size as f32, planet.size as f32)),
..default()
},
Transform::from_xyz(planet.init_pos.0, planet.init_pos.1, 0f32),
));
});
}
插件的基本逻辑已经实现完成了,我们现在可以添加到App
中了。
rust
fn add_planet(mut commands: Commands) {
commands.spawn(Planet {
name: "alpine".to_string(),
size: 48u32,
init_pos: (-100.0, 0.0),
moving: (KeyCode::KeyA, KeyCode::KeyW, KeyCode::KeyD, KeyCode::KeyS),
});
commands.spawn(Planet {
name: "ocean".to_string(),
size: 32u32,
init_pos: (100.0, 0.0),
moving: (
KeyCode::ArrowLeft,
KeyCode::ArrowUp,
KeyCode::ArrowRight,
KeyCode::ArrowDown,
),
});
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_plugins(PlanetPlugin)
.add_systems(
Update,
add_planet.run_if(input_just_released(KeyCode::Digit1)),
)
.run();
}
// 添加相机
fn setup(mut commands: Commands) {
commands.spawn(Camera2d::default());
}
其实我们早就开始用插件了,不是吗?DefaultPlugins
!。
我们将PlanetPlugin
插件添加到系统,然后编写一个add_planet
系统用来添加Planet
。
用到的两个星球资源如下:
现在,运行我们的代码:
当我们按下按键1
的时候,窗口中会出现两个星球,这两个星球可以通过键盘的上下左右和WASD
来分别控制。
如果我们只看main
函数的代码,会发现他相当简洁。Bevy 中的Plugin 可以更好的组织代码、梳理我们的游戏逻辑,对于关联性特别强的逻辑,我们推荐使用Plugin 进行组织,如果对外部有依赖,便通过Event来互相通讯。
什么是观察者?
读者可能会注意到,上述代码中add_observer
并没有讲过。那么,什么是Observer
?
Observer
是一个监听Trigger
的系统。每个触发器都适用于特定的事件类型。
Bevy有一些内置的触发器,我们可以使用它们:
类型 | 描述 |
---|---|
OnAdd | 添加实体时触发 |
OnInsert | 插入实体时触发 |
OnRemove | 在实体消失时触发 |
注意,Observer
其实就是一个系统,不过这个系统的用法和其他系统稍微不一样,我们用我们的代码举例:
rust
// 定义Observer
fn ob_planet(
// Observer 需要一个 Trigger
trigger: Trigger<OnAdd, Planet>,
query: Query<&Planet>,
mut commands: Commands,
asset_server: Res<AssetServer>,
) {}
// 添加 Observer 的方法需要使用 add_observer
app.add_observer(ob_planet)
- 使用
Trigger
作为一个系统的参数。 - 通过
add_observer
添加这个Observer
。
这样,当我们添加一个新的Planet
的时候,便会进入到我们ob_planet
Observer
,此处,我们添加了Sprite
用于二维精灵的显示。