理解 ECS
Bevy 是基于 ECS 的游戏引擎。
ECS 即表示 Entity, Component, System。
Componnet 是定义游戏世界各种概念的基本单元,比如代表玩家的 Player, 代表位置的 Position, 代表计时器的 Timer,代表窗口的 Window 等;
Entity 是游戏运行时一组相关 Component 的集合,比如描述一个玩家精灵时,需要 (Player, Position) 两个 Component 来标识;
System 定义游戏运行的规则,即 Entity 间如何交互。
下面通过一个例子来直观理解下:
use bevy::prelude::*; // Player 是一个 Component struct Player { name: String, } // Position 是一个 Component struct Position { pub x: f32, pub y: f32, } // setup_system 是一个 System fn setup_system( mut commands: Commands ) { // 生成一个 Entity commands.spawn() .insert(Player {name: "player1".to_string()}) .insert(Position {x: 0.0, y: 0.0}); // 又生成一个 Entity commands.spawn() .insert(Player {name: "player2".to_string()}) .insert(Position {x: 0.0, y: 0.0}); } // debug_system 是一个 System fn debug_system( mut user_query: Query<(&Player, &mut Position)>, ) { println!("debug_system 开始"); for (player, mut position) in user_query.iter_mut() { println!("查询到 {}, 当前位置: {},{}", player.name, position.x, position.y); println!("更新 {}", player.name); position.x += 1.0; position.y += 1.0; } println!("debug_system 结束\n\n"); } fn main() { App::build() .add_plugins(DefaultPlugins) .add_startup_system(setup_system.system()) .add_system(debug_system.system()) .run(); }
运行结果:
debug_system 开始
查询到 player1, 当前位置: 0,0
更新 player1
查询到 player2, 当前位置: 0,0
更新 player2
debug_system 结束
debug_system 开始
查询到 player1, 当前位置: 1,1
更新 player1
查询到 player2, 当前位置: 1,1
更新 player2
debug_system 结束
debug_system 开始
查询到 player1, 当前位置: 2,2
更新 player1
查询到 player2, 当前位置: 2,2
更新 player2
debug_system 结束
...
在 Bevy 中,Component 可以是任意 struct 或 enum;Entity 可以通过 Commands::spawn 生成;System 则是一些具有特殊参数的函数。
上面的例子中,Player 和 Position 都是 Component,在 setup_system 中,声明 mut commands: Commands
通过 commands 生成两个 Entity,这两个 Entity 都各自包含了 Player 和 Position 两个 Component 实例。
在 debug_system 中,我们声明了 Query 参数,Query 赋予了我们查询符合条件的 Entity 的能力,比如 Query<(&Player, &mut Position)
就表示查询同时包含 Player 和 Position 这两个 Component 的 Entity。
最后,我们使用 add_startup_system
添加 setup_system 是因为 setup_system 只需要运行一次,而通 add_system
添加 debug_system 是因为 debug_system 在游戏运行的每个物理帧都要执行。
简单来说我们可以根据下图理解 ECS:
假设我们已经有了 Player, Position, Robot 等 Component,然后通过 commands.spawn().insert(Player).insert(Position)
和 commands.spawn().insert(Player).insert(Position).insert(Robot)
生成两个 Entity。
在 System 中,我们通过 Query 可以查询不同的 Entity:
Query<&Player>
查询所有带有 Player 的 Entity (entity1,entity2)Query<(&Player, &Robot)>
查询所有同时带有 Player 和 Robot 的 Entity (entity2)Query<&Player, With<Robot>>
查询所有同时带有 Player 和 Robot 的 Entity (entity2),和上面的区别是这个只能读取 Player,不能读取 Robot,相对的查询效率更高效Query<&Player, Without<Robot>>
查询所有不带有 Robot 的 Player 的 Entity (entity1)Query<(&Player, Option<&Robot>)>
查询所有带有 Player 的 Entity,并且尝试获取该 Entity 的 Robot Component (entity1, entity2)
Query 中的 Component 前面加上 & 表示只读,加上 &mut 表示可写。