系统(Systems) 概述 学习目标 :
理解系统的概念和作用
掌握如何定义和使用系统
了解系统参数类型
理解系统闭包、泛型系统、一次性系统等高级功能
前置知识要求 :
核心编程框架(ECS)
组件(Components)
实体(Entities)
Rust 基础语法
核心概念 什么是系统? 系统是在实体、组件和资源上运行逻辑的函数。系统是 ECS 中的逻辑单元,用于处理游戏逻辑。
为什么使用系统?
逻辑分离 :将游戏逻辑分离为独立的系统
并行执行 :系统可以并行执行,提高性能
易于测试 :系统可以独立测试
系统的设计思想 系统采用数据导向的设计思想,将数据(组件)和逻辑(系统)分离。这种设计使得:
系统可以独立开发和测试
系统可以并行执行,提高性能
系统可以灵活组合,实现复杂的游戏逻辑
基础用法 定义系统 系统是普通 Rust 函数,用于处理实体、组件和资源。
源代码文件 :bevy/examples/ecs/ecs_guide.rs
代码示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 fn print_message_system () { println! ("This game is fun!" ); } fn new_round_system (game_rules: Res<GameRules>, mut game_state: ResMut<GameState>) { game_state.current_round += 1 ; println! ( "Begin round {} of {}" , game_state.current_round, game_rules.max_rounds ); } fn score_system (mut query: Query<(&Player, &mut Score, &mut PlayerStreak)>) { for (player, mut score, mut streak) in &mut query { let scored_a_point = random::<bool >(); if scored_a_point { score.value += 1 ; *streak = match *streak { PlayerStreak::Hot (n) => PlayerStreak::Hot (n + 1 ), PlayerStreak::Cold (_) | PlayerStreak::None => PlayerStreak::Hot (1 ), }; println! ( "{} scored a point! Their score is: {} ({})" , player.name, score.value, *streak ); } } }
关键要点 :
系统是普通 Rust 函数
系统可以访问资源(Res<T>、ResMut<T>)
系统可以查询组件(Query<T>)
系统可以创建和修改实体(Commands)
系统在每次应用更新时运行
说明 : 系统是 ECS 中的逻辑单元。系统通过查询访问组件,通过 Res 或 ResMut 访问资源。系统可以并行执行,提高性能。
系统参数类型 系统可以使用多种参数类型来访问不同的数据。
源代码文件 :bevy/examples/ecs/ecs_guide.rs
代码示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 fn score_check_system ( game_rules: Res<GameRules>, mut game_state: ResMut<GameState>, query: Query<(&Player, &Score)>, ) { for (player, score) in &query { if score.value == game_rules.winning_score { game_state.winning_player = Some (player.name.clone ()); } } }
关键要点 :
Res<T>:只读访问资源
ResMut<T>:可变访问资源
Query<T>:查询组件
Commands:创建和修改实体
系统可以同时使用多种参数类型
说明 : 系统参数必须实现 SystemParam trait。Bevy 提供了多种系统参数类型,如 Res、ResMut、Query、Commands 等。
进阶用法 系统闭包 系统可以是闭包,允许捕获外部变量。
源代码文件 :bevy/examples/ecs/system_closure.rs
代码示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 fn main () { let simple_closure = || { info!("Hello from a simple closure!" ); }; let complex_closure = |mut value: String | { move || { info!("Hello from a complex closure! {}" , value); value = format! ("{value} - updated" ); } }; let outside_variable = "bar" .to_string (); App::new () .add_plugins (LogPlugin::default ()) .add_systems (Update, simple_closure) .add_systems (Update, complex_closure ("foo" .into ())) .add_systems (Update, || { info!("Hello from an inlined closure!" ); }) .add_systems (Update, move || { info!( "Hello from an inlined closure that captured the 'outside_variable'! {}" , outside_variable ); }) .run (); }
关键要点 :
系统可以是闭包
闭包可以捕获外部变量
使用 move 关键字移动变量到闭包中
闭包可以用于简单的系统逻辑
注意事项 :
闭包捕获的变量会在系统调用之间保持状态
使用 move 关键字会移动变量到闭包中
注意闭包的生命周期和所有权
最佳实践 :
对于简单的系统逻辑,使用闭包
对于复杂的系统逻辑,使用普通函数
注意闭包捕获的变量的生命周期
泛型系统 泛型系统允许在不同类型上重用逻辑。
源代码文件 :bevy/examples/ecs/generic_system.rs
代码示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 fn cleanup_system <T: Component>(mut commands: Commands, query: Query<Entity, With<T>>) { for e in &query { commands.entity (e).despawn (); } } fn main () { App::new () .add_plugins (DefaultPlugins) .init_state::<AppState>() .add_systems (Startup, setup_system) .add_systems ( Update, ( print_text_system, transition_to_in_game_system.run_if (in_state (AppState::MainMenu)), ), ) .add_systems (OnExit (AppState::MainMenu), cleanup_system::<MenuClose>) .add_systems (OnExit (AppState::InGame), cleanup_system::<LevelUnload>) .run (); }
关键要点 :
泛型系统允许在不同类型上重用逻辑
使用 ::<T> (turbofish) 语法指定类型
泛型类型必须满足 trait 约束
泛型系统可以用于处理相关组件或资源
注意事项 :
泛型系统需要为每个类型创建专门的副本
确保为每个类型都注册了系统
泛型系统可以结合用户定义的 trait 使用
最佳实践 :
对于处理相关组件或资源的系统,使用泛型系统
结合用户定义的 trait 使用泛型系统
确保为每个类型都注册了系统
一次性系统 一次性系统在触发时运行一次,而不是每次更新都运行。
源代码文件 :bevy/examples/ecs/one_shot_systems.rs
代码示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 use bevy::{ ecs::system::{RunSystemOnce, SystemId}, prelude::*, }; #[derive(Component)] struct Callback (SystemId);#[derive(Component)] struct Triggered ;fn setup_with_commands (mut commands: Commands) { let system_id = commands.register_system (system_a); commands.spawn ((Callback (system_id), A)); } fn setup_with_world (world: &mut World) { world.run_system_once (system_b).unwrap (); let system_id = world.register_system (system_b); world.spawn ((Callback (system_id), B)); } fn trigger_system ( mut commands: Commands, query_a: Single<Entity, With<A>>, query_b: Single<Entity, With<B>>, input: Res<ButtonInput<KeyCode>>, ) { if input.just_pressed (KeyCode::KeyA) { let entity = *query_a; commands.entity (entity).insert (Triggered); } if input.just_pressed (KeyCode::KeyB) { let entity = *query_b; commands.entity (entity).insert (Triggered); } } fn evaluate_callbacks (query: Query<(Entity, &Callback), With<Triggered>>, mut commands: Commands) { for (entity, callback) in query.iter () { commands.run_system (callback.0 ); commands.entity (entity).remove::<Triggered>(); } } fn system_a (entity_a: Single<Entity, With<Text>>, mut writer: TextUiWriter) { *writer.text (*entity_a, 3 ) = String ::from ("A" ); info!("A: One shot system registered with Commands was triggered" ); } fn system_b (entity_b: Single<Entity, With<Text>>, mut writer: TextUiWriter) { *writer.text (*entity_b, 3 ) = String ::from ("B" ); info!("B: One shot system registered with World was triggered" ); }
关键要点 :
一次性系统在触发时运行一次
使用 register_system() 注册系统
使用 run_system() 或 run_system_once() 运行系统
一次性系统可以用于推送式逻辑
注意事项 :
一次性系统可以减少很少运行的系统开销
一次性系统可以提高调度灵活性
一次性系统可以用于事件驱动的逻辑
最佳实践 :
对于很少运行的系统,使用一次性系统
对于事件驱动的逻辑,使用一次性系统
注意一次性系统的生命周期管理
系统管道 系统管道允许将多个系统连接在一起,将第一个系统的输出传递给下一个系统。
源代码文件 :bevy/examples/ecs/system_piping.rs
代码示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 fn parse_message_system (message: Res<Message>) -> Result <usize , ParseIntError> { message.parse::<usize >() } fn handler_system (In (result): In<Result <usize , ParseIntError>>) { match result { Ok (value) => println! ("parsed message: {value}" ), Err (err) => println! ("encountered an error: {err:?}" ), } } fn main () { App::new () .insert_resource (Message ("42" .to_string ())) .add_systems ( Update, ( parse_message_system.pipe (handler_system), data_pipe_system.map (|out| info!("{out}" )), parse_message_system.map (|out| debug!("{out:?}" )), ), ) .run (); }
关键要点 :
使用 .pipe() 方法将系统连接在一起
使用 .map() 方法转换系统输出
系统管道允许将多个系统连接在一起
系统管道可以用于错误处理和数据转换
注意事项 :
系统管道要求系统输出类型匹配下一个系统的输入类型
系统管道可以用于错误处理
系统管道可以用于数据转换
最佳实践 :
对于需要错误处理的系统,使用系统管道
对于需要数据转换的系统,使用系统管道
注意系统管道的类型匹配
独占系统 独占系统提供对 ECS World 的完全直接访问。它们不能与其他系统并行运行,因为它们可以访问任何东西并做任何事情。
源代码文件 :bevy/examples/ecs/ecs_guide.rs
代码示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 fn exclusive_player_system (world: &mut World) { let total_players = world.resource_mut::<GameState>().total_players; let should_add_player = { let game_rules = world.resource::<GameRules>(); let add_new_player = random::<bool >(); add_new_player && total_players < game_rules.max_players }; if should_add_player { println! ("Player {} has joined the game!" , total_players + 1 ); world.spawn (( Player { name: format! ("Player {}" , total_players + 1 ), }, Score { value: 0 }, PlayerStreak::None , )); let mut game_state = world.resource_mut::<GameState>(); game_state.total_players += 1 ; } }
关键要点 :
独占系统提供对 World 的完全访问
独占系统不能与其他系统并行运行
独占系统应该尽量避免,以最大化并行性
独占系统可以用于需要完全访问的场景
注意事项 :
独占系统会阻止所有其他系统的并行执行
独占系统应该尽量避免
独占系统可以用于需要完全访问的场景
最佳实践 :
尽量避免使用独占系统
只在必要时使用独占系统
考虑使用 Commands 代替独占系统
可失败系统参数 可失败系统参数在无法获取时会跳过系统,而不是导致错误。
源代码文件 :bevy/examples/ecs/fallible_params.rs
代码示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 fn track_targets ( mut player: Single<(&mut Transform, &Player)>, enemy: Option <Single<&Transform, (With<Enemy>, Without<Player>)>>, time: Res<Time>, ) { let (player_transform, player) = &mut *player; if let Some (enemy_transform) = enemy { } else { } } fn move_targets (mut enemies: Populated<(&mut Transform, &mut Enemy)>, time: Res<Time>) { for (mut transform, mut target) in &mut *enemies { } }
关键要点 :
Single<D, F>:必须有恰好一个匹配实体,否则系统会被静默跳过
Option<Single<D, F>>:必须有零个或一个匹配实体,如果有多个,系统会被静默跳过
Populated<D, F>:必须至少有一个匹配实体,否则系统会被静默跳过
可失败系统参数可以用于条件系统执行
注意事项 :
可失败系统参数在无法获取时会跳过系统
其他系统参数(如 Query)永远不会失败验证
可失败系统参数可以用于条件系统执行
最佳实践 :
对于需要恰好一个实体的系统,使用 Single
对于需要零个或一个实体的系统,使用 Option<Single>
对于需要至少一个实体的系统,使用 Populated
系统错误处理 系统可以返回 Result 来处理错误。
源代码文件 :bevy/examples/ecs/error_handling.rs
代码示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 use bevy::ecs::error::warn;fn main () { let mut app = App::new (); app.set_error_handler (warn); app.add_plugins (DefaultPlugins); app.add_systems (Startup, setup); app.add_systems (Startup, failing_commands); app.add_systems ( PostStartup, failing_system.pipe (|result: In<Result >| { let _ = result.0 .inspect_err (|err| info!("captured error: {err}" )); }), ); app.add_observer (fallible_observer); app.run (); } fn setup ( mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>, mut materials: ResMut<Assets<StandardMaterial>>, ) -> Result { Ok (()) } fn failing_system (world: &mut World) -> Result { world .get_resource::<UninitializedResource>() .ok_or ("Resource not initialized" )?; Ok (()) }
关键要点 :
系统可以返回 Result<(), BevyError> 来处理错误
使用 set_error_handler() 设置错误处理器
使用 .pipe() 方法处理系统错误
可失败系统可以用于错误处理
注意事项 :
默认情况下,返回错误的系统会 panic
可以设置自定义错误处理器
系统错误可以通过管道处理
最佳实践 :
对于可能失败的操作,使用可失败系统
设置适当的错误处理器
使用系统管道处理错误
实际应用 在游戏开发中的应用场景 系统在游戏开发中有广泛的应用:
游戏逻辑 :移动系统、伤害系统、AI 系统等
渲染系统 :渲染系统、UI 系统等
物理系统 :物理模拟、碰撞检测等
常见问题 问题 1 :何时使用普通系统,何时使用独占系统?
解决方案 :
大多数情况下使用普通系统
只在需要完全访问 World 时使用独占系统
考虑使用 Commands 代替独占系统
问题 2 :如何控制系统的执行顺序?
解决方案 :
使用 .before() 和 .after() 方法控制顺序
使用系统集(SystemSet)组织系统
使用系统管道连接系统
问题 3 :如何处理系统错误?
解决方案 :
使用可失败系统返回 Result
设置适当的错误处理器
使用系统管道处理错误
性能考虑
系统粒度 :保持系统粒度细,只访问需要的数据
并行执行 :系统可以并行执行,但可变访问会阻止并行
系统数量 :避免过多的小系统,考虑合并相关系统
相关资源 相关源代码文件 :
bevy/examples/ecs/ecs_guide.rs - ECS 完整指南示例(系统定义)
bevy/examples/ecs/system_closure.rs - 系统闭包示例
bevy/examples/ecs/generic_system.rs - 泛型系统示例
bevy/examples/ecs/one_shot_systems.rs - 一次性系统示例
bevy/examples/ecs/system_piping.rs - 系统管道示例
bevy/examples/ecs/system_param.rs - 自定义系统参数示例
bevy/examples/ecs/error_handling.rs - 系统错误处理示例
bevy/examples/ecs/fallible_params.rs - 可失败系统参数示例
官方文档链接 :
进一步学习建议 :
学习查询(Queries),了解如何访问组件
学习资源(Resources),了解如何访问资源
学习系统调度(Schedule & App),了解如何控制系统执行
索引 :返回上级目录