Skip to content

✍️ 필사 모드: Bevy 게임 엔진 — Rust로 다시 쓰는 ECS 패러다임의 게임 개발 (심층 핸즈온, 2026)

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

프롤로그 — 왜 Rust와 ECS가 게임 개발에 어울리는가

게임 엔진의 역사는 두 줄로 압축할 수 있다.

  1. 상속 시대(1995~2015) — Unity의 MonoBehaviour, Unreal의 UObject, Godot의 Node. 모든 게임 오브젝트는 클래스를 상속받고, "GameObject is a Component" 식의 깊은 계층을 만들었다. C++ 또는 C# 같은 OOP 언어가 자연스러웠다.
  2. 데이터 지향 시대(2015~) — Naughty Dog, Insomniac, Epic의 Mass(Unreal 5)가 "캐시 미스가 곧 프레임 드랍"이라는 걸 인정했다. Data-Oriented Design(DOD)이 부상했고, Entity-Component-System(ECS)이 모범 답안으로 등장했다.

ECS는 한 줄로 이렇다.

  • Entity = 그냥 ID(64비트 정수). 데이터도 동작도 없다.
  • Component = 순수 데이터(Position, Velocity, Sprite). 행동은 없다.
  • System = 함수. "어떤 component 조합을 가진 모든 entity에 대해" 행동한다.

상속 트리가 없다. Player extends Character extends Pawn 같은 것 자체가 사라진다. "플레이어"는 그냥 Player, Position, Velocity, Health component를 같이 가진 entity일 뿐이다.

여기에 Rust를 얹으면 ECS의 약점이었던 두 가지가 즉시 해결된다. 첫째, 동시성. 빌림 검사기(borrow checker)가 "두 system이 같은 component를 동시에 mutate하지 못한다"를 컴파일 타임에 보장한다. Bevy의 스케줄러는 이걸 이용해 system을 자동 병렬화한다. 둘째, 안정성. 게임 코드는 늘 포인터·null·범위 외 접근 버그로 멍든다. Rust는 그 카테고리 자체를 없앤다.

문제는 "현실"이다. 2026년 5월, Unity는 여전히 모바일·인디·VR 1위, Unreal은 AAA·시네마틱 1위, Godot은 인디 2D 진영에서 큰 점유, Bevy는 오픈소스 진영의 별이지만 출시된 상업 게임은 손에 꼽는다. 그래서 이 글은 "Year of Bevy"를 외치지 않는다. 대신 정직하게 답한다 — Bevy는 무엇을 잘하고, 어디까지 와 있고, 어디서 막혀 있는가.

이 글의 구성.

  1. ECS 패러다임 이해 — MonoBehaviour와 무엇이 다른가
  2. Bevy의 구조 — App, Schedule, Plugin
  3. 플러그인 아키텍처 — 모든 것이 플러그인이다
  4. wgpu로 렌더링 — 브라우저·데스크톱·모바일을 한 번에
  5. 첫 Bevy 게임 30분 — 키 입력으로 움직이는 원
  6. Godot·Unity·Unreal·Macroquad와의 비교
  7. 실제로 출시된 Bevy 게임들 — Foresight, Roll It Up, 그리고 Tiny Glade의 진실
  8. Bevy가 아직 준비 안 된 곳

기억할 한 줄: "엔진은 도구지 신앙이 아니다. 하지만 ECS는 도구 이전에 패러다임이고, Bevy는 그 패러다임을 Rust로 가장 정직하게 구현한 답이다."


1장 · ECS 패러다임 — Unity·Unreal과 무엇이 다른가

1.1 상속 모델의 작동 방식

Unity에서 적 캐릭터를 만들면 보통 이렇다.

// Unity / MonoBehaviour 스타일
public class Enemy : MonoBehaviour {
    public int health = 100;
    public float speed = 2.0f;
    private Transform target;

    void Start() { target = GameObject.FindWithTag("Player").transform; }
    void Update() {
        transform.position = Vector3.MoveTowards(
            transform.position, target.position, speed * Time.deltaTime);
        if (health <= 0) Destroy(gameObject);
    }
}

EnemyMonoBehaviour를 상속받고, 상태(체력·속도)와 동작(Update)을 같은 클래스에 둔다. 객체 100개면 Update가 100번 호출된다. 가상 함수 호출 + 캐시 미스 + 분기 예측 실패가 누적된다.

Unreal의 AActor + UCharacterMovementComponent 조합도 본질은 같다. AEnemy : public ACharacter : public APawn : public AActor라는 깊은 상속 체인, 그리고 각 컴포넌트는 부모 actor에 포인터를 들고 다닌다.

1.2 ECS의 작동 방식

같은 적 캐릭터를 Bevy로 쓰면.

// Bevy / ECS 스타일
use bevy::prelude::*;

#[derive(Component)]
struct Enemy;

#[derive(Component)]
struct Health(i32);

#[derive(Component)]
struct Speed(f32);

#[derive(Component)]
struct Target(Entity);

fn move_enemies(
    time: Res<Time>,
    mut enemies: Query<(&mut Transform, &Speed, &Target), With<Enemy>>,
    targets: Query<&Transform, Without<Enemy>>,
) {
    for (mut tf, speed, target) in &mut enemies {
        if let Ok(target_tf) = targets.get(target.0) {
            let dir = (target_tf.translation - tf.translation).normalize_or_zero();
            tf.translation += dir * speed.0 * time.delta_secs();
        }
    }
}

fn kill_dead_enemies(mut commands: Commands, q: Query<(Entity, &Health), With<Enemy>>) {
    for (e, hp) in &q {
        if hp.0 <= 0 { commands.entity(e).despawn(); }
    }
}

차이는 명확하다.

  • Enemy마커 component(빈 구조체). 데이터도 행동도 없다.
  • Health(i32), Speed(f32)순수 데이터. 메서드 없다.
  • move_enemiessystem. "Enemy 마커가 있는 entity"만 골라 한 번에 처리한다.
  • entity 100개의 Position·Speed·Target은 메모리에 연속 배치된다(archetype 기반 저장). CPU 캐시 히트율이 높다.
  • move_enemieskill_dead_enemies는 다른 component에 접근하므로 자동으로 병렬 실행된다.

1.3 Archetype과 데이터 배치

Bevy(그리고 Flecs, Unity DOTS, Unreal Mass)는 archetype 기반 ECS다. "어떤 component 집합을 가진 entity"별로 메모리 청크를 만든다.

archetype A: [Position, Velocity, Sprite] → entity 1000개의 Position이 연속, Velocity가 연속, Sprite가 연속. archetype B: [Position, Velocity, Sprite, Health, Enemy] → 또 다른 청크.

Query<(&Position, &Velocity)>는 A와 B 양쪽의 청크를 순회하면서, 각 청크 안에선 단순 배열 순회로 끝난다. 가상 함수 호출도 없고, 캐시 미스도 거의 없다. 이게 ECS가 빠른 이유다.

1.4 ECS의 단점도 정직하게

ECS는 만병통치약이 아니다.

  • 상태 머신·복잡한 동작 트리는 component로 모델링하면 component가 폭발한다. Idle, Walking, Attacking, Hurt, Dead를 각각 component로 두면 system이 모든 조합을 알아야 한다. 차라리 enum 한 개를 component 안에 두는 게 낫다.
  • 부모-자식 관계는 ECS의 원본에는 없는 개념이다. Bevy는 Parent/Children component로 트리를 흉내내지만, 본질적으로 ECS와는 결이 다르다.
  • 러닝 커브가 가파르다. "왜 메서드를 못 쓰지?", "이 query가 borrow를 어떻게 갈라?" 같은 질문을 한참 한다.

기억할 한 줄: "OOP는 동사를 명사 안에 가둔다. ECS는 명사(데이터)와 동사(system)를 끝까지 분리한다."


2장 · Bevy의 구조 — App, Schedule, System

2.1 App

Bevy 프로그램의 진입점은 App이다. main 함수는 보통 이렇게 생겼다.

use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, spawn_camera)
        .add_systems(Update, (move_player, count_fps).chain())
        .run();
}
  • App::new() — 비어 있는 ECS 월드와 스케줄러를 만든다.
  • .add_plugins(DefaultPlugins) — 윈도우, 입력, 렌더, 시간, 자산 로딩 등 기본 기능을 통째로 묶은 플러그인 묶음을 추가한다.
  • .add_systems(Startup, ...) — 게임 시작 시 한 번만 실행될 system.
  • .add_systems(Update, ...) — 매 프레임 실행될 system.
  • .run() — 메인 루프를 돌린다.

코드 전체에서 class GameManager : MonoBehaviour { void Awake() } 같은 매니저 클래스가 없다는 점이 핵심이다. App 자체가 매니저고, 게임 로직은 자유 함수다.

2.2 Schedule — 언제 실행되나

Bevy 0.10에서 "스테이지"가 사라지고 스케줄이 들어왔다. 0.15 기준 주요 스케줄은 이렇다.

  • Startup — 앱 시작 시 한 번.
  • PreUpdate — 매 프레임 시작, 입력 수집 후.
  • Update — 매 프레임 메인 로직.
  • PostUpdate — 매 프레임, transform 전파·물리 후처리 등.
  • FixedUpdate — 고정 timestep(기본 64Hz). 물리·네트워크 락스텝.
  • Last — 프레임 막판.

system 사이 순서는 .before(), .after(), .chain(), SystemSet으로 제어한다.

app.add_systems(
    Update,
    (
        input_system,
        movement_system.after(input_system),
        collision_system.after(movement_system),
    ),
);

2.3 Component·Resource·Event

데이터를 ECS 월드에 넣는 세 가지 방법.

  • Component — entity에 붙는 데이터. #[derive(Component)].
  • Resource — 월드에 하나만 존재하는 글로벌 데이터. 점수, 설정, 자산 핸들. #[derive(Resource)].
  • Event — 한 프레임 단위 메시지. system 간 통신. #[derive(Event)].
#[derive(Resource)]
struct Score(u32);

#[derive(Event)]
struct EnemyKilled { entity: Entity, by: Entity }

fn award_score(mut score: ResMut<Score>, mut events: EventReader<EnemyKilled>) {
    for _ in events.read() {
        score.0 += 100;
    }
}

Res<T>는 읽기 전용, ResMut<T>는 쓰기 가능. 두 system이 같은 ResMut를 잡으면 자동으로 직렬 실행되고, 한쪽이 Res 한쪽이 Res면 병렬이다.

2.4 Query — system이 데이터를 가져오는 법

fn move_player(
    time: Res<Time>,
    mut q: Query<(&mut Transform, &Speed), With<Player>>,
) {
    for (mut tf, speed) in &mut q {
        tf.translation.x += speed.0 * time.delta_secs();
    }
}

Query 시그니처가 곧 system의 "데이터 요구사항"이다. With<Player>는 필터(component를 받지는 않지만 entity가 가지고 있어야 함). 두 system이 충돌하지 않는 query를 가지면 Bevy는 자동으로 두 개를 병렬 실행한다.

기억할 한 줄: "Bevy 코드를 읽는 법 = system 시그니처만 봐도 그 system이 무엇을 읽고 무엇을 쓰는지 다 보인다."


3장 · 플러그인 아키텍처 — 모든 것이 플러그인이다

Bevy의 가장 큰 미적 결정은 "엔진 자체가 플러그인 컬렉션"이라는 점이다.

DefaultPlugins를 풀어보면 약 20개의 플러그인이 들어 있다.

  • WindowPlugin — 창 생성
  • InputPlugin — 키보드·마우스·게임패드
  • RenderPlugin — wgpu 기반 렌더
  • SpritePlugin — 2D 스프라이트
  • PbrPlugin — 3D 물리 기반 렌더링
  • UiPlugin — bevy_ui
  • AudioPlugin — kira 기반 오디오
  • AssetPlugin — 비동기 자산 로딩
  • TimePlugin, TransformPlugin, LogPlugin, ...

원하지 않으면 통째로 뺄 수 있다.

// 헤드리스 서버 — 렌더·윈도우 없이 ECS만
App::new()
    .add_plugins(MinimalPlugins)
    .add_plugins(LogPlugin::default())
    .add_systems(Update, game_logic)
    .run();

3.1 자기 플러그인 만들기

내 게임 코드도 플러그인으로 묶으면 모듈화가 깔끔하다.

use bevy::prelude::*;

pub struct PlayerPlugin;

impl Plugin for PlayerPlugin {
    fn build(&self, app: &mut App) {
        app
            .add_event::<PlayerHurt>()
            .add_systems(Startup, spawn_player)
            .add_systems(Update, (
                player_input,
                player_movement.after(player_input),
                player_animation,
            ));
    }
}

#[derive(Event)]
pub struct PlayerHurt { pub amount: i32 }

#[derive(Component)]
pub struct Player;

fn spawn_player(mut commands: Commands) {
    commands.spawn((
        Player,
        Transform::default(),
        // ... sprite, collider, etc.
    ));
}

fn player_input(/* ... */) {}
fn player_movement(/* ... */) {}
fn player_animation(/* ... */) {}

main은 이렇게 짧아진다.

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins((PlayerPlugin, EnemyPlugin, AudioPlugin, UiPlugin))
        .run();
}

3.2 생태계의 플러그인들 (2026년 5월 기준)

  • Avian — 2D/3D 물리. 옛 bevy_xpbd의 후속. position-based dynamics, 0.15 호환.
  • bevy_egui — egui를 Bevy에 띄우는 디버그 UI 플러그인. 인스펙터·툴 제작 표준.
  • bevy-inspector-egui — 월드의 component·resource를 런타임에 즉시 편집.
  • Lightyear — 클라이언트-서버 네트워킹. replication, rollback, prediction.
  • bevy_rapier — Rapier 물리 엔진 바인딩(Avian 대신 선택).
  • bevy_tweening, bevy_easings — 애니메이션 보간.
  • bevy_kira_audio — 더 풍부한 오디오 제어.
  • Big Brain — utility AI 결정 트리.

bevy.org 또는 bevy_assets 깃허브 레포에 큐레이션된 목록이 있다.

기억할 한 줄: "플러그인은 단순한 모듈이 아니라 Bevy의 사상이다 — 엔진과 게임 코드 사이의 경계가 없다."


4장 · wgpu 렌더링 — 브라우저·데스크톱·모바일을 한 번에

Bevy의 렌더러는 wgpu 위에 얹혀 있다. wgpu는 Rust 진영의 WebGPU 구현으로, 한 코드로 Vulkan·Metal·DirectX 12·WebGPU·OpenGL ES를 모두 타겟한다.

4.1 무엇이 좋은가

  • 크로스 플랫폼이 진짜다. macOS에서 Metal, Linux에서 Vulkan, 윈도우에서 DX12, 모바일에서 Vulkan/Metal, 웹에서 WebGPU. 셰이더는 WGSL로 한 번 쓰면 다 돌아간다.
  • WebGPU 지원이 1급 시민이다. cargo build --target wasm32-unknown-unknownwasm-bindgen으로 묶으면 브라우저에서 실행된다. 0.15 기준 itch.io 같은 곳에 올리기 충분하다.
  • 셰이더가 모던하다. WGSL은 GLSL보다 안전(타입·바운드 체크), HLSL보다 단순.

4.2 무엇이 약한가

  • WebGPU 지원 브라우저가 아직 좁다. 2026년 5월 기준, Chrome·Edge는 안정, Safari는 18부터 지원, Firefox는 일부 platform에서 nightly. 게임을 웹에 올릴 때 폴백을 고민해야 한다.
  • 셰이더 컴파일 시간·드라이버 호환성은 wgpu의 책임이고, Vulkan SDK가 직접 주는 도구만큼 매끄럽지 않다.
  • PBR 파이프라인은 모던하지만 UE5만큼 풍부하지 않다. Lumen·Nanite 같은 첨단 기능은 없다(그게 필요하면 Bevy를 쓸 이유가 거의 없다).

4.3 커스텀 셰이더

WGSL로 셰이더를 짠 다음 Material trait을 구현해 Bevy material로 등록한다.

use bevy::{
    prelude::*,
    render::render_resource::{AsBindGroup, ShaderRef},
    sprite::Material2d,
};

#[derive(Asset, TypePath, AsBindGroup, Clone, Debug)]
struct WaveMaterial {
    #[uniform(0)] time: f32,
    #[uniform(0)] color: LinearRgba,
}

impl Material2d for WaveMaterial {
    fn fragment_shader() -> ShaderRef { "shaders/wave.wgsl".into() }
}

그리고 셰이더 파일.

struct Uniforms {
    time: f32,
    color: vec4<f32>,
};

@group(2) @binding(0) var<uniform> u: Uniforms;

@fragment
fn fragment(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
    let wave = sin(uv.x * 10.0 + u.time) * 0.5 + 0.5;
    return vec4<f32>(u.color.rgb * wave, 1.0);
}

기억할 한 줄: "wgpu 덕에 Bevy는 '한 코드로 어디든 돌아가는 렌더러'를 거의 공짜로 가졌다 — 단, '어디든'이 '최적'은 아니다."


5장 · 첫 Bevy 게임 30분 — 키 입력으로 움직이는 원

이제 코드를 쓴다. "원이 화면에 떠 있고, 화살표 키로 움직인다." 30분 안에 끝낸다.

5.1 프로젝트 시작

# Rust가 없다면 rustup으로 설치
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# 새 프로젝트
cargo new moving_circle && cd moving_circle

Cargo.toml을 연다.

[package]
name = "moving_circle"
version = "0.1.0"
edition = "2021"

[dependencies]
bevy = "0.15"

# 빠른 컴파일을 위한 dev 프로파일 — 권장
[profile.dev]
opt-level = 1

[profile.dev.package."*"]
opt-level = 3

5.2 첫 코드 — 빈 창 띄우기

src/main.rs.

use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .run();
}

fn setup(mut commands: Commands) {
    commands.spawn(Camera2d);
}
cargo run

검은 빈 창이 뜬다. 처음 빌드는 2~3분 걸린다(의존성 컴파일). 이후 increment 빌드는 수 초.

5.3 원 그리기

use bevy::prelude::*;

#[derive(Component)]
struct Player;

#[derive(Component)]
struct Speed(f32);

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .run();
}

fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<ColorMaterial>>,
) {
    commands.spawn(Camera2d);

    commands.spawn((
        Player,
        Speed(300.0),
        Mesh2d(meshes.add(Circle::new(40.0))),
        MeshMaterial2d(materials.add(Color::srgb(0.2, 0.7, 1.0))),
        Transform::from_xyz(0.0, 0.0, 0.0),
    ));
}

다시 cargo run. 파란 원 하나가 가운데 떠 있다.

코드의 핵심.

  • commands.spawn((..., ..., ...)) — 튜플로 component 묶음을 한 번에 붙인다.
  • Mesh2d + MeshMaterial2d — Bevy 0.15의 새 2D 머티리얼 API. Mesh와 머티리얼이 각각 자기 component.
  • Transform — 위치·회전·스케일.

5.4 입력으로 움직이기

update 스케줄에 system을 추가한다.

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .add_systems(Update, move_player)
        .run();
}

fn move_player(
    time: Res<Time>,
    keyboard: Res<ButtonInput<KeyCode>>,
    mut q: Query<(&mut Transform, &Speed), With<Player>>,
) {
    let dt = time.delta_secs();
    for (mut tf, speed) in &mut q {
        let mut dir = Vec2::ZERO;
        if keyboard.pressed(KeyCode::ArrowLeft)  { dir.x -= 1.0; }
        if keyboard.pressed(KeyCode::ArrowRight) { dir.x += 1.0; }
        if keyboard.pressed(KeyCode::ArrowUp)    { dir.y += 1.0; }
        if keyboard.pressed(KeyCode::ArrowDown)  { dir.y -= 1.0; }
        if dir != Vec2::ZERO {
            tf.translation += (dir.normalize() * speed.0 * dt).extend(0.0);
        }
    }
}

cargo run. 화살표 키를 누르면 원이 움직인다. 끝.

핵심 패턴.

  • Res<Time> — Bevy가 자동으로 제공하는 시간 리소스.
  • Res<ButtonInput<KeyCode>> — 키보드 상태 리소스.
  • Query<(&mut Transform, &Speed), With<Player>> — Player 마커가 있는 entity의 Transform과 Speed만 가져온다.
  • time.delta_secs() — 프레임 독립적 이동.

5.5 적 한 마리 추가

#[derive(Component)]
struct Enemy;

fn setup(/* ... */) {
    // ... (위와 동일)

    commands.spawn((
        Enemy,
        Speed(150.0),
        Mesh2d(meshes.add(Circle::new(30.0))),
        MeshMaterial2d(materials.add(Color::srgb(1.0, 0.3, 0.3))),
        Transform::from_xyz(200.0, 200.0, 0.0),
    ));
}

fn chase_player(
    time: Res<Time>,
    player_q: Query<&Transform, With<Player>>,
    mut enemy_q: Query<(&mut Transform, &Speed), (With<Enemy>, Without<Player>)>,
) {
    let Ok(player_tf) = player_q.single() else { return };
    let dt = time.delta_secs();
    for (mut tf, speed) in &mut enemy_q {
        let dir = (player_tf.translation - tf.translation).normalize_or_zero();
        tf.translation += dir * speed.0 * dt;
    }
}

// main에 .add_systems(Update, (move_player, chase_player)) 추가

빨간 적이 플레이어를 따라온다. ECS의 진가 — 별도 클래스 정의 없이, 새 component와 system만 추가하면 새 행동이 생긴다.

기억할 한 줄: "30분 안에 '키로 움직이는 원'까지 가는 게 진짜 '엔진의 첫 인상' 테스트다. Bevy는 그 테스트를 통과한다."


6장 · Godot·Unity·Unreal·Macroquad와의 정직한 비교

엔진은 종교가 아니다. 표를 보자.

6.1 한 줄 요약

  • Unity (C#) — 산업 표준. 2D 인디·모바일·VR·간단한 3D의 압도적 1위. 에디터·자산 스토어가 무기.
  • Unreal Engine (C++/Blueprint) — AAA·시네마틱·고품질 3D의 1위. Nanite, Lumen, MetaHuman. 자체적으로 엔터프라이즈 솔루션.
  • Godot (GDScript/C#) — 인디 2D, 가벼운 3D. 오픈소스, MIT, ~50MB 에디터. 4.x 이후 3D가 많이 좋아졌다.
  • Bevy (Rust) — ECS 우선, 오픈소스, MIT/Apache. 에디터 없음(BSN 계획 중). 라이브러리에 가깝다.
  • Macroquad (Rust) — "그냥 2D 게임을 빨리 시작하고 싶다"에 답하는 미니멀 Rust 게임 라이브러리. ECS 없음.

6.2 항목별 비교

언어와 패러다임

  • Unity: C# + OOP/컴포넌트
  • Unreal: C++ + Blueprint(비주얼 스크립팅)
  • Godot: GDScript(Python-스러움) + C#
  • Bevy: Rust + ECS
  • Macroquad: Rust + immediate-mode 절차적

에디터

  • Unity·Unreal·Godot: 풍부한 GUI 에디터 내장.
  • Bevy: 공식 에디터 없음. 0.15 시점 BSN(Bevy Scene Notation) 신 포맷 RFC가 진행 중이고, 에디터는 그 위에 만들어질 예정이다. 그 전엔 bevy-inspector-egui로 런타임 인스펙터에 의존한다.
  • Macroquad: 에디터 없음.

컴파일 속도

  • Unity·Unreal: C++/C# 컴파일 + 핫 리로드 옵션.
  • Godot: GDScript는 즉시 실행, C#은 컴파일.
  • Bevy: Rust 컴파일이 느리다. dev 빌드 최적화(opt-level=1)와 cargo watch -x run, dynamic_linking 피처(0.x.x 한정)를 쓰면 수 초~십수 초.

런타임 성능

  • Unreal: AAA 게임에서 입증된 최고 성능, 다만 빌드가 무겁다.
  • Unity DOTS: ECS를 도입했지만 학습 곡선·생태계 분열.
  • Bevy: ECS + Rust가 메모리 안전·자동 병렬화로 작은 코드로 좋은 멀티스레드 성능.
  • Godot: 충분히 빠르나 AAA 타이틀에는 부족.

모바일/콘솔 빌드

  • Unity·Unreal: 1급, 콘솔 SDK 라이선스 보유.
  • Godot: iOS·Android 가능, 콘솔은 서드파티 포트.
  • Bevy: 데스크톱·웹은 잘 됨. iOS는 가능하나 까다로움. 안드로이드는 nightly 단계 작업이 많다. 콘솔은 사실상 불가(라이선스).

오픈소스 정책

  • Unity·Unreal: 매출 일정 이상 시 라이선스/로열티.
  • Godot·Bevy·Macroquad: MIT/Apache, 완전 자유.

6.3 언제 무엇을 쓰나

  • 모바일 인디 2D·간단한 3D → Unity
  • AAA 그래픽·시네마틱 → Unreal
  • 인디 2D, GDScript 친화 → Godot
  • Rust 좋아함, ECS 패러다임 학습, 도구·시뮬레이션 → Bevy
  • "2D 게임 잼 5시간 안에" → Macroquad

기억할 한 줄: "Bevy는 'Unity 대체재'가 아니다. 다른 도구다 — Rust·ECS·오픈소스·모듈성을 동시에 원할 때만 옳다."


7장 · 실제로 출시된 Bevy 게임들 — 무엇이 사실인가

여기는 정직해야 한다. 인터넷에 도는 "Bevy로 만든 게임" 리스트에는 가끔 거짓이 섞인다.

7.1 진짜 Bevy로 만들어진 상업 게임

  • Foresight — RTS 인디 게임. 개발사 Captured by Aliens가 명시적으로 Bevy 사용을 공개했다.
  • Roll It Up — 인디 캐주얼.
  • Tunnet — 네트워크 시뮬레이션 게임. Bevy 0.x 시절부터 개발됐고 출시.
  • Jarl — Steam 출시 그리드 기반 RTS, Bevy 기반.
  • 다수의 itch.io 게임 잼 작품 — Bevy Jam(연 1~2회)의 결과물들. bevyengine.orgitch.io/jam/bevy-jam 검색하면 수백 개.

7.2 종종 오해되는 케이스 — Tiny Glade

Pounce Light의 Tiny Glade는 2024년 9월 출시 직후 Steam 인기 1위까지 올라간 인디 명작 시뮬레이터다. 이 게임은 Bevy를 사용하지 않는다 — 자체 Rust 엔진을 만들었고, 일부 컴포넌트로 Rust 생태계의 라이브러리(wgpu 등)는 활용했지만 "Bevy 게임"으로 묶으면 잘못이다. 개발사가 여러 인터뷰와 GDC 토크에서 자체 엔진임을 분명히 했다.

"Tiny Glade가 Bevy로 만들어졌다"라는 글은 무시하라. Bevy 커뮤니티는 Tiny Glade의 성공으로 "Rust 게임 가능성"을 보여줬다는 점을 기뻐했지만, 엔진 자체는 다르다.

7.3 비게임 사용 사례

Bevy는 ECS + wgpu 조합 덕에 게임이 아닌 곳에서도 쓰인다.

  • Foresight Mining Software — 광산 시뮬레이션 도구.
  • Komodo — 일부 인터랙티브 시각화에 Bevy.
  • 다수의 데이터 시각화·로봇 시뮬레이션·교육용 데모.

엔진을 라이브러리처럼 임포트할 수 있다는 게 Bevy의 큰 특징이다 — Unity·Unreal로는 불가능에 가깝다.

기억할 한 줄: "Bevy는 'Tiny Glade 같은 게임을 만든다'고 거짓말하지 않는다. 'Foresight·Jarl·Tunnet 같은 게임을 만들었고, 데이터 시각화·시뮬레이션에서도 쓴다'고 정직하게 말한다."


8장 · Bevy가 아직 준비 안 된 곳 — 정직한 한계

"Year of Bevy"는 2024년에도, 2025년에도, 2026년에도 매년 외쳐졌다. 그 사이 진짜로 성숙한 부분과 여전히 거친 부분을 가른다.

8.1 성숙해진 것

  • 2D 렌더링·기본 UI — 0.15부터 Mesh2d/MeshMaterial2d로 일관성 정리.
  • 물리 — Avian이 안정. 2D/3D, 컨스트레인트, CCD까지.
  • 오디오bevy_audio(기본) + bevy_kira_audio(고급).
  • WebGPU 빌드 — 데모용으로 충분히 매끄럽다.
  • 플러그인 생태계bevy_assets 레포에 수백 개. 자주 쓰이는 건 메인테이너가 매 릴리스 따라간다.

8.2 여전히 거친 것

  • 공식 에디터 없음 — 0.15 시점 BSN(Bevy Scene Notation) RFC 진행 중. 이건 게임 오브젝트 계층·자산·머티리얼을 자료로 표현하는 신 포맷 제안인데, 받아들여지면 그 위에 에디터가 생긴다. 채택 일정은 2026~2027년 사이로 본다.
  • 핫 리로드 — 자산은 워치 모드로 어느 정도, 코드는 dynamic_linking 피처로 부분적. JIT 수준 핫 리로드는 아직 시기상조.
  • 버전 간 호환성 깨짐 — 0.10·0.11·0.12·0.13·0.14·0.15 사이 마이그레이션이 매번 적지 않다. 0.15 마이그레이션 가이드 한 번 읽어야 한다. 1.0이 나오기 전까지는 이 상황이 계속된다.
  • 콘솔 빌드 — Switch·PS5·Xbox는 사실상 불가. 라이선스 + 비공개 SDK 문제.
  • 모바일 빌드 — iOS는 동작하지만 도구가 거칠고, 안드로이드는 nightly·실험적. Unity·Unreal과는 비교 안 된다.
  • GUI 디자이너 부재 — bevy_ui가 코드 기반이라 디자이너가 마우스로 UI 짜는 워크플로가 없다(BSN으로 해결될 예정).
  • 러닝 커브 — Rust + ECS + Bevy API. 학교 후에 첫 게임 짜는 시간은 다른 엔진보다 길다.

8.3 0.15 → 1.0의 길

Bevy 공식 로드맵(2026년 5월 갱신 기준)이 1.0까지 미루는 큰 항목.

  1. BSN 정착과 공식 에디터의 최소 버전.
  2. 안정적 ABI(플러그인을 동적 로딩).
  3. 핫 리로드 시나리오 확립.
  4. 멀티 윈도우·고DPI·드래그 앤 드롭 같은 데스크톱 polish.
  5. 비주얼 스크립팅(필요하다면 외부 플러그인으로).

"1.0이 언제냐"고 묻지 마라. 답은 항상 "준비되면". 다만 0.15는 작은 인디 게임을 시작하기에 충분히 안정적이다.

기억할 한 줄: "Bevy 1.0을 기다리지 마라. 0.15로 만들 수 있는 게임을 지금 만들어라. 1.0 마이그레이션은 그때 한다."


에필로그 — 시작하는 사람을 위한 안내

30분 핸즈온 체크리스트

  • rustup으로 Rust 설치(1.81+)
  • cargo new + Cargo.tomlbevy = "0.15"
  • Camera2d 스폰 — 빈 창 확인
  • Mesh2d(Circle::new(40.0)) + MeshMaterial2d로 원 그리기
  • Update 스케줄에 move_player system, Res<ButtonInput<KeyCode>>로 키 입력
  • Enemy 마커 component + chase_player system 추가
  • cargo run --release로 최적화 빌드 한 번
  • cargo build --target wasm32-unknown-unknownwasm-bindgen으로 웹 빌드 시도
  • bevy-inspector-egui 플러그인 붙여 런타임 인스펙터 띄우기
  • Avian 추가해 두 원에 collider 붙이고 충돌 들어보기

Bevy 안티 패턴 7가지

  1. System 안에서 Vec<Entity>를 들고 다니며 매 프레임 lookup — query를 잘 짜면 안 해도 된다.
  2. 모든 걸 component로 — 상태 머신·간단한 enum은 component 안에 두는 게 낫다.
  3. Commands를 모든 곳에서 mut — 가능하지만 결과 반영은 다음 프레임이다. 즉시 반영이 필요하면 World를 직접 받는 exclusive system을 쓴다.
  4. Query::single()을 panic으로 풀기let Ok(...) = q.single() else { return }; 같은 형태가 안전하다.
  5. 이벤트 폭주EventReader를 매 프레임 들고 있지 않으면 이벤트가 사라진다. MyEvent를 두 system이 동시에 읽으려면 add_event + 일관된 system 순서.
  6. 렌더 system을 Update에 두기 — 렌더 system은 보통 Bevy 내부가 알아서 한다. 사용자 코드는 데이터만 바꾸면 된다.
  7. 0.x 마이그레이션 무시 — Bevy 0.x → 0.(x+1)는 매번 4~12시간 작업이 든다. 1.0 전에 큰 프로젝트면 마이그레이션 buffer를 일정에 넣어라.

다음 글 예고

  • Avian으로 2D 플랫포머 만들기 — 점프·코요테 타임·중력 조정·연속 충돌 감지.
  • Lightyear로 멀티플레이어 슈터 — replication·prediction·rollback의 클라이언트-서버 모델.
  • Bevy + wasm + itch.io 출시 — 웹 빌드, 키 캡처, 모바일 터치 매핑까지.
  • BSN 신 포맷 RFC 정독 — Bevy 1.0이 노리는 모양과 그게 게임 코드를 어떻게 바꾸는가.

기억할 한 줄: "Rust + ECS는 게임 엔진 패러다임의 답 중 가장 정직한 답이다. Bevy는 그 답을 직접 손으로 짜볼 수 있는 가장 빠른 길이다."


참고 / References

현재 단락 (1/446)

게임 엔진의 역사는 두 줄로 압축할 수 있다.

작성 글자: 0원문 글자: 17,299작성 단락: 0/446