用途
本技能用于在 Bevy 0.20 中实现一个计时赛(time trial)循环:当一次跑动需要记录已用时间、分段(splits)或终点线时加载它。它把比赛数据组织为 ECS 数据,而不是把计时逻辑塞进移动系统。
主要能力
- 比赛时钟:已用时间(elapsed)保存在一个 Resource 上。
- 分段记录:当角色经过 SplitGate 时,把当前 elapsed 写入该 Resource 上的 splits 列表。
- 终点判定:角色越过 FinishLine 时设置 finished 标志,并在成绩优于 best 时更新 best。
- 固定步长移动:角色由 FixedUpdate 中的系统按固定步长推进,该系统不拥有比赛时钟。
ECS 结构
组件(Component):
- Racer:角色标记。
- FinishLine:终点体积标记。
- SplitGate:分段门,含 at_x 与 taken 字段。
资源(Resource):
- RaceClock:包含 elapsed、splits、finished、best,比赛时钟、分段列表和最佳成绩都放在这一个资源上。
系统(Systems):
- tick_race:推进时钟。
- record_split:记录分段。
- cross_finish:检测终点线。 三者按 tick_race → record_split → cross_finish 的顺序用 .chain() 串联,因为它们都写入同一个资源。
消息(Messages):一次性事实(命中、拾取、任务推进)走 bevy-events-messaging;本文件不新增事件类型名。
使用方式
依赖声明:
[package]
name = "time-trial"
version = "0.1.0"
edition = "2021"
[dependencies]
bevy = "0.20"
核心代码结构如下(保留原文代码):
#[derive(Resource)]
struct RaceClock {
elapsed: f32,
splits: Vec<f32>,
finished: bool,
best: f32,
}
#[derive(Component)]
struct Racer;
#[derive(Component)]
struct FinishLine;
fn spawn_course(mut commands: Commands) {
commands.insert_resource(RaceClock {
elapsed: 0.0,
splits: Vec::new(),
finished: false,
best: f32::MAX,
});
commands.spawn((Racer, Transform::from_xyz(0.0, 0.0, 0.0)));
commands.spawn((FinishLine, Transform::from_xyz(10.0, 0.0, 0.0)));
commands.spawn(SplitGate {
at_x: 5.0,
taken: false,
});
}
fn tick_race(time: Res<Time>, mut clock: ResMut<RaceClock>) {
if !clock.finished {
clock.elapsed += time.delta_secs();
}
}
#[derive(Component)]
struct SplitGate {
at_x: f32,
taken: bool,
}
fn record_split(
mut clock: ResMut<RaceClock>,
racers: Query<&Transform, With<Racer>>,
mut gates: Query<&mut SplitGate>,
) {
if clock.finished {
return;
}
let Some(racer) = racers.iter().next() else {
return;
};
for mut gate in &mut gates {
if !gate.taken && racer.translation.x >= gate.at_x {
gate.taken = true;
let elapsed = clock.elapsed;
clock.splits.push(elapsed);
}
}
}
fn cross_finish(
mut clock: ResMut<RaceClock>,
racers: Query<&Transform, With<Racer>>,
lines: Query<&Transform, With<FinishLine>>,
) {
if clock.finished {
return;
}
let Some(racer) = racers.iter().next() else {
return;
};
let Some(line) = lines.iter().next() else {
return;
};
if racer.translation.x >= line.translation.x {
clock.finished = true;
if clock.elapsed < clock.best {
clock.best = clock.elapsed;
}
}
}
pub struct TimeTrialPlugin;
impl Plugin for TimeTrialPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, spawn_course).add_systems(
Update,
(tick_race, record_split, cross_finish).chain(),
);
}
}
固定步长移动角色(不拥有比赛时钟):
#[derive(Component)]
struct Racer;
fn drive(time: Res<Time<Fixed>>, mut racers: Query<&mut Transform, With<Racer>>) {
let step = time.delta_secs() * 8.0;
for mut transform in &mut racers {
transform.translation.x += step;
}
}
pub struct DrivePlugin;
impl Plugin for DrivePlugin {
fn build(&self, app: &mut App) {
app.add_systems(FixedUpdate, drive);
}
}
状态归属
- 单个实体上的数据:Component,由一个系统通过 Query 修改,运行在 Update 或 FixedMain。
- 整个应用共享的一个值:Resource,由一个系统修改,Startup 插入,之后由读取它的调度使用。
- 生成:Commands,由生成系统负责;初始世界在 Startup,运行时生成在后续调度。
- 不能竞态的顺序:.chain(),仅约束被链的系统,处于同一调度。
- 插件边界:Plugin,build 注册系统与资源,在应用运行前完成。
借用规则:两个都可变查询同一组件的系统必须排序(.chain())或拆分,使只有一个写入;无关系统保持并行。
决策表
- 需要逐实体数据:用 Component + Query,不要用一个结构体存所有实体的字段。
- 需要应用级配置或时钟:用 Resource,不要把值复制到每个实体。
- 需要稳定模拟步长:用 FixedMain 和 Fixed 时钟,不要把模拟绑到原始帧 Update。
- 需要启动生成:在 Startup 中用 Commands,不要从渲染系统生成。
- 需要非共享状态的跨系统通知:用 bevy-events-messaging 中的消息或观察者槽位,不要在本文件里新造事件类型。
- 需要文档集之外的 API:阅读链接的 Bevy 页面,不要编造 crate 或 API。
适用场景
- 竞速类玩法中的单圈计时、分段计时与终点判定。
- 需要把比赛时钟、分段列表、最佳成绩作为共享状态统一管理的场景。
- 需要固定步长驱动角色、同时让计时逻辑独立于移动系统的场景。
限制与边界
- 比赛计时、分段和幽灵数据由使用者自行以组件形式构建;幽灵回放属于 bevy-save-load。
- 本技能只命名有文档的 Bevy crate 与模块,不引入物理、导航、网络或 XR crate。
- 分段或终点不使用信号总线:时钟资源是共享状态,一次性通知属于 bevy-events-messaging。
- Bevy 代码保持惯用 ECS,只使用有文档的 Bevy API,不使用节点类型。
- 文档 URL 不要固定 /0.20.0/,链接保持最新;0.20 目标只在正文中说明一次。
- 不要从两个无序系统可变查询同一组件。
相关技能
前置:bevy-master(枢纽与调度)、bevy-ecs-composition(组件与系统)、bevy-state-machines(倒计时与完成)、bevy-data-resources(表格与定义)、bevy-events-messaging(一次性事实)。 互补:bevy-state-machines(模式标志)。 下游/消费者:暂无。 主技能:bevy-master(Bevy 路由)。
参考
官方文档:Assets(官方资源目录,未选择第三方 crate)、Examples(最新分支示例树)、ECS(Component、systems、Query、Startup、Update 与 .chain())、Plugins(Plugin、DefaultPlugins、MinimalPlugins 与 add_plugins)。bevy-master 上的库固定版本保持 0.20.0,官方链接保持无版本号的 learn 页面。