AnvilKitAnvilKit

ECS 与插件系统

应用生命周期、插件系统、调度、系统与 Bundle

anvilkit-app 提供应用生命周期管理、插件系统、系统调度和组件 Bundle,全部基于 bevy_appbevy_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 + InputPluginInputState + 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() 每帧按以下顺序运行调度:

调度变体执行典型用途
StartupAnvilKitSchedule::Startup首次 update() 时执行一次场景设置、资源加载
MainAnvilKitSchedule::Main每帧,最先执行全局游戏逻辑
PreUpdateAnvilKitSchedule::PreUpdate每帧DeltaTime 同步、父子层级同步
FixedUpdateAnvilKitSchedule::FixedUpdate每帧 0-N 次确定性物理、固定速率模拟(默认 1/60s)
UpdateAnvilKitSchedule::Update每帧游戏逻辑、AI、物理
PostUpdateAnvilKitSchedule::PostUpdate每帧变换传播、渲染准备
CleanupAnvilKitSchedule::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速度积分、碰撞检测、物理步进
GameLogicAI、状态机、游戏规则
Transform层级传播、坐标同步
Render可见性剔除、材质更新、绘制准备
Audio声音播放、音量控制
UI布局、事件处理、组件更新
Network复制、序列化、连接管理
DebugGizmos、性能叠加层、日志
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,
}

目录