命令(Commands)
概述
学习目标:
- 理解 Commands 的概念和作用
- 掌握如何使用 Commands 修改世界
- 了解 Commands 的各种操作方法
- 理解 Commands 的执行时机
前置知识要求:
- 核心编程框架(ECS)
- 组件(Components)
- 实体(Entities)
- 系统(Systems)
- Rust 基础语法
核心概念
什么是 Commands?
Commands 是一种安全的方式来修改世界(World)。由于系统可以并行执行,直接访问世界是不安全的。Commands 将修改操作排队,在系统执行完成后统一应用。
为什么使用 Commands?
- 线程安全:Commands 允许系统安全地修改世界,即使系统并行执行
- 延迟执行:Commands 将修改操作排队,在系统执行完成后统一应用
- 命令缓冲:Commands 提供命令缓冲,允许批量操作
Commands 的设计思想
Commands 采用命令模式,将修改操作封装为命令,在系统执行完成后统一应用。这种设计使得:
- 系统可以并行执行,同时安全地修改世界
- 修改操作可以延迟执行,提高性能
- 修改操作可以批量处理,减少开销
基础用法
创建实体
使用 spawn() 和 spawn_batch() 创建实体。
源代码文件:bevy/examples/ecs/ecs_guide.rs
代码示例:
1 | fn startup_system(mut commands: Commands, mut game_state: ResMut<GameState>) { |
关键要点:
- 使用
spawn()创建单个实体 - 使用
spawn_batch()批量创建实体 - 可以在创建实体时同时添加多个组件
- Commands 将修改操作排队,在系统执行完成后统一应用
说明:
Commands 提供了一种安全的方式来修改世界。由于系统可以并行执行,直接访问世界是不安全的。Commands 将修改操作排队,在系统执行完成后统一应用。
操作实体
使用 entity() 方法获取实体命令,然后可以插入、移除组件或销毁实体。
源代码文件:bevy/examples/ecs/ecs_guide.rs
代码示例:
1 | // 调用 `.id()` 在生成实体后将返回生成实体的 `Entity` 标识符 |
源代码文件:bevy/examples/ecs/entity_disabling.rs
代码示例:
1 | fn disable_entities_on_click( |
源代码文件:bevy/examples/ecs/fallible_params.rs
代码示例:
1 | fn user_input( |
关键要点:
- 使用
entity()方法获取实体命令 - 使用
insert()方法插入组件 - 使用
remove()方法移除组件 - 使用
despawn()方法销毁实体 - 可以链式调用多个操作
说明:entity() 方法返回一个 EntityCommands,可以用于操作实体。可以链式调用多个操作,如 commands.entity(entity).insert(component).remove::<OtherComponent>()。
管理资源
使用 insert_resource() 和 init_resource() 管理资源。
源代码文件:bevy/examples/ecs/ecs_guide.rs
代码示例:
1 | fn startup_system(mut commands: Commands, mut game_state: ResMut<GameState>) { |
关键要点:
- 使用
insert_resource()插入资源 - 使用
init_resource()初始化资源(需要实现Default或FromWorldtrait) - 资源可以在系统运行时插入或初始化
说明:
Commands 可以用于管理资源。使用 insert_resource() 可以插入资源,使用 init_resource() 可以初始化资源。
进阶用法
注册和运行系统
使用 register_system() 和 run_system() 注册和运行一次性系统。
源代码文件:bevy/examples/ecs/one_shot_systems.rs
代码示例:
1 | use bevy::{ |
关键要点:
- 使用
register_system()注册系统并获取系统 ID - 使用
run_system()运行一次性系统 - 一次性系统在触发时运行一次,而不是每次更新都运行
- 一次性系统可以用于推送式逻辑
注意事项:
- 一次性系统在触发时运行一次
- 一次性系统可以减少很少运行的系统开销
- 一次性系统可以提高调度灵活性
最佳实践:
- 对于很少运行的系统,使用一次性系统
- 对于事件驱动的逻辑,使用一次性系统
- 注意一次性系统的生命周期管理
触发事件
使用 trigger() 方法触发事件。
源代码文件:bevy/examples/ecs/observers.rs
代码示例:
1 | /// [`EntityEvent`] 是一种专门的 [`Event`] 类型,可以针对特定实体 |
关键要点:
- 使用
trigger()方法触发事件 - 事件可以是普通事件或实体事件
- 实体事件可以针对特定实体
- 观察者会响应触发的事件
注意事项:
- 事件可以是普通事件或实体事件
- 实体事件可以针对特定实体
- 观察者会响应触发的事件
最佳实践:
- 对于事件驱动的逻辑,使用事件
- 对于需要针对特定实体的逻辑,使用实体事件
- 注意事件的性能影响
发送消息
使用 write_message() 方法发送消息。
源代码文件:bevy/examples/ecs/message.rs
代码示例:
1 | /// 这是一个 [`Message`],任何观察它的观察者都会在它被激活时运行 |
关键要点:
- 使用
write_message()方法发送消息 - 消息需要实现
Messagetrait - 观察者会响应发送的消息
- 消息可以用于系统间通信
注意事项:
- 消息需要实现
Messagetrait - 观察者会响应发送的消息
- 消息可以用于系统间通信
最佳实践:
- 对于系统间通信,使用消息
- 对于事件驱动逻辑,使用消息
- 注意消息的性能影响
实体层次结构
使用 with_children() 和 add_child() 管理实体层次结构。
源代码文件:bevy/examples/ecs/hierarchy.rs
代码示例:
1 | fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { |
关键要点:
- 使用
with_children()方法在创建实体时添加子实体 - 使用
add_child()方法在实体创建后添加子实体 - 使用
remove_child()方法移除子实体 - 销毁父实体会自动销毁所有子实体
注意事项:
- 层次结构通过
ChildOf和Children组件实现 - 销毁父实体会自动销毁所有子实体
- 可以通过移除
ChildOf组件来断开父子关系
最佳实践:
- 使用层次结构组织相关的实体
- 注意层次结构对变换和可见性传播的影响
- 合理使用层次结构,避免过深的嵌套
实际应用
在游戏开发中的应用场景
Commands 在游戏开发中有广泛的应用:
- 实体创建:创建玩家、敌人、道具等游戏对象
- 组件管理:添加、移除、修改组件
- 资源管理:插入和初始化资源
- 事件驱动:触发事件和发送消息
- 系统管理:注册和运行一次性系统
常见问题
问题 1:Commands 何时执行?
解决方案:Commands 在系统执行完成后统一应用。这意味着在系统执行期间,Commands 的修改不会立即生效。
问题 2:如何获取刚创建的实体 ID?
解决方案:使用 .id() 方法在创建实体后立即获取实体 ID。即使实体本身尚未在世界中实例化,也可以获取 ID。
问题 3:Commands 和直接访问 World 有什么区别?
解决方案:
- Commands 是线程安全的,可以在并行系统中使用
- 直接访问 World 需要独占系统,会阻止并行执行
- Commands 将修改操作排队,在系统执行完成后统一应用
性能考虑
- 命令缓冲:Commands 将修改操作排队,批量处理,减少开销
- 延迟执行:Commands 的修改在系统执行完成后统一应用,提高性能
- 批量操作:使用
spawn_batch()批量创建实体,比逐个创建更高效
相关资源
相关源代码文件:
bevy/examples/ecs/ecs_guide.rs- ECS 完整指南示例(Commands 使用)bevy/examples/ecs/one_shot_systems.rs- 一次性系统示例(注册和运行系统)bevy/examples/ecs/observers.rs- 观察者模式示例(触发事件)bevy/examples/ecs/hierarchy.rs- 实体层次结构示例(管理层次结构)bevy/examples/ecs/entity_disabling.rs- 实体禁用示例(操作实体)
官方文档链接:
进一步学习建议:
- 学习实体(Entities),了解如何创建和操作实体
- 学习系统(Systems),了解如何在系统中使用 Commands
- 学习 ECS 进阶,了解 Commands 的高级功能
索引:返回上级目录