ECS 与插件系统
应用生命周期、插件系统、调度、系统与 Bundle
anvilkit-app 提供应用生命周期管理、插件系统、系统调度和组件 Bundle,全部基于 bevy_app 和 bevy_ecs 构建。
注意: 推荐使用
AnvilKitApp::run()配合GameCallbacks来启动游戏。下面展示的App::new()方式适用于高级用例或测试场景。
AnvilKitApp 模式
大多数游戏推荐使用 AnvilKitApp::run() 配合 GameCallbacks 实现作为入口点。它会自动处理引擎初始化、主循环和关闭流程:
use anvilkit::prelude::*;
struct MyGame;
impl GameCallbacks for MyGame {
fn init(&mut self, ctx: &mut GameContext) {
ctx.app.add_systems(AnvilKitSchedule::Update, my_system);
}
}
fn main() {
AnvilKitApp::run(
GameConfig::new("My Game"),
App::new().add_plugins(DefaultPlugins),
MyGame,
);
}应用生命周期
App 是应用程序的入口点。典型的初始化流程为:
App::new() → add_plugins() → insert_resource() → add_systems() → run() / update()大多数项目推荐使用 DefaultPlugins 一行初始化:
use anvilkit::prelude::*;
App::new()
.add_plugins(DefaultPlugins::new())
.add_systems(AnvilKitSchedule::Startup, setup)
.run();DefaultPlugins 包含:AnvilKitEcsPlugin + RenderPlugin + AudioPlugin + InputPlugin(InputState + ActionMap + action_map_update_system)+ CameraPlugin + AutoDeltaTimePlugin + PersistencePlugin(cfg)+ SettingsApplyPlugin(cfg)。
此外,anvilkit-gameplay crate 提供了更高层的 Gameplay 插件。详见 Gameplay 系统 页面。
use anvilkit::prelude::*;
let mut app = App::new();
// 1. 注册插件(顺序重要——依赖项优先)
app.add_plugins(AnvilKitEcsPlugin);
app.add_plugins(TransformPlugin);
app.add_plugins(PhysicsPlugin);
// 2. 插入全局资源
app.insert_resource(MyConfig { difficulty: 3 });
app.init_resource::<Score>(); // 使用 Default
// 3. 将系统注册到调度中
app.add_systems(AnvilKitSchedule::Startup, setup_scene);
app.add_systems(AnvilKitSchedule::Update, (player_input, enemy_ai));
app.add_systems(AnvilKitSchedule::PostUpdate, cleanup);
// 4a. 阻塞主循环(在循环中调用 update())
app.run();
// 4b. 或手动驱动更新
for _ in 0..60 {
app.update(); // 一帧
}App API
| 方法 | 签名 | 说明 |
|---|---|---|
new | () -> Self | 创建一个预注册了所有引擎调度的 App |
add_plugins | <P: Plugin>(P) -> &mut Self | 注册插件(唯一插件会自动去重) |
add_systems | (impl ScheduleLabel, impl IntoSystemConfigs) -> &mut Self | 将系统添加到调度中 |
insert_resource | <R: Resource>(R) -> &mut Self | 插入类型化全局资源 |
init_resource | <R: Resource + FromWorld>() -> &mut Self | 使用 Default/FromWorld 实现插入资源 |
add_event | <E: Event>() -> &mut Self | 注册事件类型;启用 EventWriter<E> 和 EventReader<E>,自动双缓冲清理 |
set_fixed_timestep | (f32) -> &mut Self | 设置 FixedUpdate 间隔(秒),默认 1/60,最小 0.0001 |
fixed_timestep | () -> f32 | 获取当前 FixedUpdate 间隔 |
register_serializable | <T: 'static>(&str) -> &mut Self | 注册组件类型用于场景序列化 |
run | (&mut self) | 阻塞主循环,直到调用 exit() |
update | (&mut self) | 执行一帧:Startup(仅首次) → 事件刷新 → Main → PreUpdate → FixedUpdate(累加器驱动,0-N tick) → Update → PostUpdate → Cleanup + AppExit 检查 |
exit | (&mut self) | 通知应用在当前帧结束后停止 |
插件
插件封装了可复用的引擎功能。实现 Plugin trait:
pub struct PhysicsPlugin;
impl Plugin for PhysicsPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<DeltaTime>()
.add_event::<CollisionEvent>()
.add_systems(AnvilKitSchedule::FixedUpdate, (
velocity_integration_system,
collision_detection_system.after(velocity_integration_system),
));
}
}唯一插件(默认)最多注册一次;重复的 add_plugins 调用会被静默跳过。此唯一性检查同样适用于 PluginGroup 内的插件。
调度阶段
App::update() 每帧按以下顺序运行调度:
| 调度 | 变体 | 执行 | 典型用途 |
|---|---|---|---|
| Startup | AnvilKitSchedule::Startup | 首次 update() 时执行一次 | 场景设置、资源加载 |
| Main | AnvilKitSchedule::Main | 每帧,最先执行 | 全局游戏逻辑 |
| PreUpdate | AnvilKitSchedule::PreUpdate | 每帧 | DeltaTime 同步、父子层级同步 |
| FixedUpdate | AnvilKitSchedule::FixedUpdate | 每帧 0-N 次 | 确定性物理、固定速率模拟(默认 1/60s) |
| Update | AnvilKitSchedule::Update | 每帧 | 游戏逻辑、AI、物理 |
| PostUpdate | AnvilKitSchedule::PostUpdate | 每帧 | 变换传播、渲染准备 |
| Cleanup | AnvilKitSchedule::Cleanup | 每帧 | 临时数据清理、诊断 |
事件系统
AnvilKit 使用 bevy_ecs 事件实现系统间解耦通信:
use bevy_ecs::event::Event;
#[derive(Event)]
struct DamageEvent {
target: Entity,
amount: f32,
}
// 在插件中注册
app.add_event::<DamageEvent>();
// 发送事件
fn attack_system(mut events: EventWriter<DamageEvent>) {
events.send(DamageEvent { target: enemy, amount: 25.0 });
}
// 接收事件(支持多个读者)
fn health_system(mut events: EventReader<DamageEvent>) {
for event in events.read() {
println!("实体 {:?} 受到 {} 点伤害", event.target, event.amount);
}
}事件采用双缓冲机制,2 帧后自动清除。
游戏状态机
GameState<S> 提供游戏流程的状态管理(菜单、游戏、暂停):
use anvilkit_app::state::{GameState, NextGameState, in_state, StateValue};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum AppState { Menu, Playing, Paused }
impl StateValue for AppState {}
// 初始化状态
app.insert_resource(GameState::new(AppState::Menu));
app.init_resource::<NextGameState<AppState>>();
app.add_systems(AnvilKitSchedule::PreUpdate, state_transition_system::<AppState>);
// 仅在特定状态下运行系统
app.add_systems(AnvilKitSchedule::Update,
player_movement.run_if(in_state(AppState::Playing))
);
// 状态转换
fn pause_system(input: Res<InputState>, mut next: ResMut<NextGameState<AppState>>) {
if input.is_key_just_pressed(KeyCode::Escape) {
next.set(AppState::Paused);
}
}SystemSet
在调度内,系统可以通过 AnvilKitSystemSet 进行分组排序:
| SystemSet | 用途 |
|---|---|
Input | 键盘、鼠标、手柄处理 |
Time | 时间/计时器更新、帧率追踪 |
Physics | 速度积分、碰撞检测、物理步进 |
GameLogic | AI、状态机、游戏规则 |
Transform | 层级传播、坐标同步 |
Render | 可见性剔除、材质更新、绘制准备 |
Audio | 声音播放、音量控制 |
UI | 布局、事件处理、组件更新 |
Network | 复制、序列化、连接管理 |
Debug | Gizmos、性能叠加层、日志 |
app.add_systems(AnvilKitSchedule::Update, (
apply_gravity.in_set(AnvilKitSystemSet::Physics),
check_collisions.in_set(AnvilKitSystemSet::Physics)
.after(apply_gravity),
handle_input.in_set(AnvilKitSystemSet::Input),
));系统
系统是普通函数,其参数声明数据访问方式:
fn move_system(
time: Res<Time>,
mut query: Query<(&Velocity, &mut Transform)>,
) {
let dt = time.delta_seconds();
for (vel, mut transform) in &mut query {
transform.translation += vel.linear * dt;
}
}
fn collision_handler(mut events: EventReader<CollisionEvent>) {
for event in events.read() {
println!("Collision: {:?} <-> {:?}", event.a, event.b);
}
}Bundle
Bundle 将组件打包以便批量生成:
#[derive(Bundle)]
struct PlayerBundle {
transform: Transform,
global_transform: GlobalTransform,
velocity: Velocity,
collider: AabbCollider,
player: Player,
}