본문으로 건너뛰기
cccskills
무료GitHub에 공개

bevy-ecs-patterns

Use when writing Bevy (Rust ECS) game code - pin and respect the repo's minor version, plugins/systems/queries/resources, schedules with FixedUpdate and Time, states and run conditions, messages vs observers across versions, required components, testing systems with App::update and a manual time strategy, compile-time and allocation hygiene.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper스타 121(GitHub 121 + cccskills 0)

실행 점수 미검증

설치 방법 보기

포함된 파일(1)

  • SKILL.md7.3 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Bevy ECS Patterns

Overview

Bevy is a code-first Rust engine built on an ECS: entities are IDs, components are plain structs, systems are ordinary functions whose parameters declare the data they touch, and the scheduler runs them in parallel where it can. Bevy also ships a breaking release every few months — the API you remember may not be the API the repository pins.

Core principle: The repository's pinned Bevy minor is the API. Systems are small functions over queries, simulation runs in FixedUpdate, and every system is testable by building an App and calling update().

Version first

# Cargo.toml — for 0.x crates "0.18" already means >=0.18.0, <0.19.0: the minor is pinned
bevy = "0.18"

Read Cargo.lock for the resolved version and that version's migration guide before writing code. Never bump the minor in a feature task. Changes that bite code written from memory:

SinceChange
0.15Time::delta_seconds() → delta_secs(); required components (#[require(...)]) replace most bundles; Single<...> system parameter
0.16Query::single() / get_single() return Result; systems may return Result and use ?
0.17Buffered events renamed to messages: #[derive(Message)], MessageWriter::write, MessageReader::read, app.add_message::<T>(); observers use On<T> for entity events

Plugins, components, systems

use bevy::prelude::*;

#[derive(Component, Default)]
#[require(Transform, Velocity)]
pub struct Player;

#[derive(Component, Default, Deref, DerefMut)]
pub struct Velocity(pub Vec2);

#[derive(Resource)]
pub struct MoveTuning { pub speed: f32 }

#[derive(Message)]
pub struct PlayerHit { pub damage: u32 }

pub struct MovementPlugin;

impl Plugin for MovementPlugin {
    fn build(&self, app: &mut App) {
        app.insert_resource(MoveTuning { speed: 220.0 })
            .insert_resource(Time::<Fixed>::from_hz(60.0))
            .add_message::<PlayerHit>()
            .add_systems(Update, read_input.run_if(in_state(GameState::Playing)))
            .add_systems(FixedUpdate, (apply_velocity, resolve_hits).chain());
    }
}

fn read_input(keys: Res<ButtonInput<KeyCode>>, tuning: Res<MoveTuning>, mut player: Single<&mut Velocity, With<Player>>) {
    let x = keys.pressed(KeyCode::KeyD) as i8 as f32 - keys.pressed(KeyCode::KeyA) as i8 as f32;
    player.0 = Vec2::new(x * tuning.speed, player.0.y);
}

fn apply_velocity(time: Res<Time>, mut q: Query<(&mut Transform, &Velocity)>) {
    for (mut t, v) in &mut q {
        t.translation += (v.0 * time.delta_secs()).extend(0.0);   // in FixedUpdate, Time is the fixed clock
    }
}
  • One plugin per feature; main.rs only adds plugins.
  • Systems are small and named for what they do; order with .chain(), .before()/.after() or SystemSets, not by hoping.
  • Read input in Update, simulate in FixedUpdate (default 64 Hz; set it explicitly), render-side smoothing in Update/PostUpdate (game-loop-and-time).
  • Commands are deferred: an entity spawned in a system is visible to queries only after the command flush (the next sync point), not later in the same system.
  • Use change detection (Changed<T>, Added<T>, Ref<T>::is_changed) instead of recomputing every frame; filter queries (With, Without) so systems touch only what they need.
  • Res<T> reads run in parallel; ResMut<T> serialises every system that touches T — keep shared mutable resources few.
  • Tuning values in resources or assets loaded from RON/JSON (data-driven-gameplay-design), not literals in systems.

States and conditions

#[derive(States, Default, Debug, Clone, PartialEq, Eq, Hash)]
pub enum GameState { #[default] Loading, Menu, Playing, Paused }

app.init_state::<GameState>()
   .add_systems(OnEnter(GameState::Playing), spawn_level)
   .add_systems(OnExit(GameState::Playing), despawn_level);

Entities spawned for a state are cleaned up on exit (a marker component plus a despawn system, or the state-scoped entity helpers of the pinned version).

Testing systems

#[cfg(test)]
mod tests {
    use super::*;
    use bevy::time::TimeUpdateStrategy;
    use std::time::Duration;

    fn app() -> App {
        let mut app = App::new();
        app.add_plugins(MinimalPlugins)
            .insert_resource(TimeUpdateStrategy::ManualDuration(Duration::from_secs_f64(1.0 / 60.0)))
            .insert_resource(Time::<Fixed>::from_hz(60.0))
            .add_systems(FixedUpdate, apply_velocity);
        app.update();                     // the first update has zero delta; prime the clock
        app
    }

    #[test]
    fn moves_by_speed_times_elapsed_time() {
        let mut app = app();
        let e = app.world_mut().spawn((Transform::default(), Velocity(Vec2::new(60.0, 0.0)))).id();
        for _ in 0..60 { app.update(); }
        let x = app.world().get::<Transform>(e).unwrap().translation.x;
        assert!((x - 60.0).abs() < 1.5, "x = {x}");
    }
}
  • MinimalPlugins gives time and the schedule runner without a window or GPU — runs anywhere cargo test runs.
  • Control time with TimeUpdateStrategy::ManualDuration; never sleep.
  • Send messages in a test through the world (write_message on the pinned version, send_event before 0.17) and read results back with queries or a resource.
  • world.run_system_once(my_system) for a one-off system call in a test.
  • Pure math (damage, steering) as plain functions with ordinary unit tests.

Build hygiene

  • cargo test && cargo clippy -- -D warnings before hand-off (rule game-build-check); Bevy projects commonly allow clippy::too_many_arguments and clippy::type_complexity — follow the repository's lint config.
  • Fast iteration: opt-level = 1 for the crate and opt-level = 3 for dependencies in [profile.dev]/[profile.dev.package."*"]; the dynamic_linking feature only in dev, never in release builds.
  • First compile takes minutes: give run_terminal a long timeout or run it detached to a log.
  • Allocation in hot systems: reuse buffers with Local<Vec<T>> instead of collecting into a new Vec every frame.
  • Headless runs: MinimalPlugins + ScheduleRunnerPlugin, or DefaultPlugins with no primary window; screenshots need a GPU and a window (game-visual-self-review).

Common Mistakes

  • Code from an older or newer Bevy version (delta_seconds, EventWriter, get_single) that does not compile on the pinned one.
  • Movement in Update with Time and physics in Update — frame-rate dependent.
  • Expecting a just-spawned entity in the same system's query.
  • One giant system per feature with ResMut on everything, killing parallelism.
  • Bumping Bevy "while here".

Red Flags

  • bevy = "*", a >= range, or a version change in Cargo.toml the task did not ask for.
  • std::thread::sleep or Instant::now() inside systems or tests.
  • A system test that opens a window.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1212026년 10월 21일 업데이트

Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1212026년 10월 21일 업데이트

Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1212026년 10월 21일 업데이트

Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1212026년 10월 21일 업데이트

How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1212026년 10월 21일 업데이트

Use when the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1212026년 10월 21일 업데이트

makifbaysal의 스킬 모두 보기

이 스킬의 문제 신고