代码组织

Bevy 代码组织指南

本指南介绍Bevy引擎中推荐的代码组织方式,帮助您构建可维护、可扩展的游戏项目。

模块 (Module) 组织

Rust模块基础

Rust的模块系统是代码组织的基础,在Bevy项目中尤为重要:

1
2
3
4
5
6
7
8
9
10
11
12
// lib.rs 或 main.rs
mod player;
mod enemy;
mod ui;
mod systems;
mod resources;

// 重新导出常用类型
pub use player::PlayerPlugin;
pub use enemy::EnemyPlugin;
pub use ui::UiPlugin;

按功能组织模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// player/mod.rs
mod components;
mod systems;
mod resources;

pub use components::*;
pub use systems::*;
pub use resources::*;

pub struct PlayerPlugin;

impl Plugin for PlayerPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, systems::spawn_player)
.add_systems(Update, systems::player_movement);
}
}

按层级组织模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 游戏逻辑层
mod game {
mod player;
mod enemy;
mod combat;
}

// 系统层
mod systems {
mod movement;
mod collision;
mod rendering;
}

// 资源层
mod resources {
mod assets;
mod config;
mod state;
}

插件 (Plugin) 系统

插件基础

插件是Bevy中最重要的代码组织方式,将相关功能打包成可重用的单元:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use bevy::prelude::*;

pub struct GamePlugin;

impl Plugin for GamePlugin {
fn build(&self, app: &mut App) {
app
// 添加子插件
.add_plugins((
PlayerPlugin,
EnemyPlugin,
UiPlugin,
))
// 添加资源
.init_resource::<GameState>()
.insert_resource(GameConfig::default())
// 添加事件
.add_event::<PlayerDied>()
.add_event::<EnemySpawned>()
// 添加系统
.add_systems(Startup, setup_game)
.add_systems(Update, game_loop);
}
}

条件插件

根据配置或环境决定是否启用插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pub struct DebugPlugin;

impl Plugin for DebugPlugin {
fn build(&self, app: &mut App) {
#[cfg(debug_assertions)]
{
app.add_systems(Update, debug_system);
}
}
}

// 或者使用特性标志
#[cfg(feature = "debug")]
pub struct DebugPlugin;

impl Plugin for DebugPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, debug_system);
}
}

插件配置

通过配置结构体自定义插件行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#[derive(Resource)]
pub struct PlayerConfig {
pub speed: f32,
pub health: f32,
pub spawn_position: Vec3,
}

impl Default for PlayerConfig {
fn default() -> Self {
Self {
speed: 5.0,
health: 100.0,
spawn_position: Vec3::ZERO,
}
}
}

pub struct PlayerPlugin {
config: PlayerConfig,
}

impl PlayerPlugin {
pub fn new(config: PlayerConfig) -> Self {
Self { config }
}

pub fn with_speed(mut self, speed: f32) -> Self {
self.config.speed = speed;
self
}
}

impl Plugin for PlayerPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(self.config.clone())
.add_systems(Startup, spawn_player)
.add_systems(Update, player_movement);
}
}

// 使用
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(PlayerPlugin::new(PlayerConfig {
speed: 10.0,
health: 150.0,
spawn_position: Vec3::new(0.0, 0.0, 0.0),
}))
.run();
}

插件依赖管理

1
2
3
4
5
6
7
8
9
10
11
12
13
pub struct CombatPlugin;

impl Plugin for CombatPlugin {
fn build(&self, app: &mut App) {
// 确保依赖的插件已经添加
if !app.is_plugin_added::<PlayerPlugin>() {
app.add_plugins(PlayerPlugin);
}

app.add_systems(Update, combat_system);
}
}

Bundle 组件集合

Bundle基础

Bundle是组件的集合,用于一次性添加多个相关组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#[derive(Bundle)]
struct PlayerBundle {
player: Player,
health: Health,
transform: Transform,
sprite: Sprite,
collider: Collider,
}

impl Default for PlayerBundle {
fn default() -> Self {
Self {
player: Player,
health: Health::new(100.0),
transform: Transform::from_xyz(0.0, 0.0, 0.0),
sprite: Sprite::new(Vec2::new(32.0, 32.0)),
collider: Collider::circle(16.0),
}
}
}

参数化Bundle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#[derive(Bundle)]
struct PlayerBundle {
player: Player,
health: Health,
transform: Transform,
sprite: Sprite,
collider: Collider,
}

impl PlayerBundle {
pub fn new(position: Vec3, health: f32) -> Self {
Self {
player: Player,
health: Health::new(health),
transform: Transform::from_translation(position),
sprite: Sprite::new(Vec2::new(32.0, 32.0)),
collider: Collider::circle(16.0),
}
}

pub fn with_sprite(mut self, size: Vec2) -> Self {
self.sprite = Sprite::new(size);
self
}

pub fn with_collider(mut self, radius: f32) -> Self {
self.collider = Collider::circle(radius);
self
}
}

组合Bundle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#[derive(Bundle)]
struct PhysicsBundle {
transform: Transform,
velocity: Velocity,
collider: Collider,
}

#[derive(Bundle)]
struct RenderBundle {
sprite: Sprite,
material: Handle<ColorMaterial>,
}

#[derive(Bundle)]
struct PlayerBundle {
#[bundle]
physics: PhysicsBundle,
#[bundle]
render: RenderBundle,
player: Player,
health: Health,
}

// 使用
commands.spawn(PlayerBundle {
physics: PhysicsBundle {
transform: Transform::from_xyz(0.0, 0.0, 0.0),
velocity: Velocity::default(),
collider: Collider::circle(16.0),
},
render: RenderBundle {
sprite: Sprite::new(Vec2::new(32.0, 32.0)),
material: materials.add(Color::RED),
},
player: Player,
health: Health::new(100.0),
});

动态Bundle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn spawn_player(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
let mut bundle = PlayerBundle::default();

// 根据配置动态修改
if let Some(texture) = asset_server.get_handle("player.png") {
bundle.sprite = Sprite::new(Vec2::new(64.0, 64.0));
bundle.material = materials.add(ColorMaterial::from(texture));
}

commands.spawn(bundle);
}

系统集 (SystemSet)

系统集基础

系统集用于组织和排序相关的系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
enum GameSet {
Input,
Movement,
Combat,
Rendering,
}

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.configure_sets(Update, (
GameSet::Input,
GameSet::Movement,
GameSet::Combat,
GameSet::Rendering,
).chain())
.add_systems(Update, (
handle_input.in_set(GameSet::Input),
player_movement.in_set(GameSet::Movement),
enemy_movement.in_set(GameSet::Movement),
combat_system.in_set(GameSet::Combat),
render_system.in_set(GameSet::Rendering),
))
.run();
}

条件系统集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
enum GameSet {
Input,
Movement,
Combat,
Rendering,
}

#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
enum PauseSet {
Paused,
Unpaused,
}

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.configure_sets(Update, (
GameSet::Input,
GameSet::Movement,
GameSet::Combat,
GameSet::Rendering,
).chain())
.configure_sets(Update, (
PauseSet::Paused,
PauseSet::Unpaused,
).chain())
.add_systems(Update, (
handle_input.in_set(GameSet::Input),
player_movement.in_set((GameSet::Movement, PauseSet::Unpaused)),
pause_menu.in_set((GameSet::Input, PauseSet::Paused)),
))
.run();
}

动态系统集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
enum GameState {
Menu,
Playing,
Paused,
GameOver,
}

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_state::<GameState>()
.add_systems(Update, (
menu_system.run_if(in_state(GameState::Menu)),
game_system.run_if(in_state(GameState::Playing)),
pause_system.run_if(in_state(GameState::Paused)),
game_over_system.run_if(in_state(GameState::GameOver)),
))
.run();
}

完整的项目结构示例

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
src/
├── main.rs
├── lib.rs
├── game/
│ ├── mod.rs
│ ├── player/
│ │ ├── mod.rs
│ │ ├── components.rs
│ │ ├── systems.rs
│ │ ├── resources.rs
│ │ └── plugin.rs
│ ├── enemy/
│ │ ├── mod.rs
│ │ ├── components.rs
│ │ ├── systems.rs
│ │ └── plugin.rs
│ └── combat/
│ ├── mod.rs
│ ├── components.rs
│ ├── systems.rs
│ └── plugin.rs
├── systems/
│ ├── mod.rs
│ ├── movement.rs
│ ├── collision.rs
│ └── rendering.rs
├── resources/
│ ├── mod.rs
│ ├── assets.rs
│ ├── config.rs
│ └── state.rs
└── ui/
├── mod.rs
├── components.rs
├── systems.rs
└── plugin.rs

主入口文件

1
2
3
4
5
6
7
8
9
10
11
// main.rs
use bevy::prelude::*;
use game::GamePlugin;

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(GamePlugin)
.run();
}

游戏模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// game/mod.rs
mod player;
mod enemy;
mod combat;

pub use player::PlayerPlugin;
pub use enemy::EnemyPlugin;
pub use combat::CombatPlugin;

pub struct GamePlugin;

impl Plugin for GamePlugin {
fn build(&self, app: &mut App) {
app.add_plugins((
PlayerPlugin,
EnemyPlugin,
CombatPlugin,
))
.add_systems(Startup, setup_game)
.add_systems(Update, game_loop);
}
}

fn setup_game(mut commands: Commands) {
// 游戏初始化逻辑
}

fn game_loop() {
// 游戏主循环逻辑
}

玩家模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// game/player/mod.rs
mod components;
mod systems;
mod resources;

pub use components::*;
pub use systems::*;
pub use resources::*;

pub struct PlayerPlugin;

impl Plugin for PlayerPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<PlayerConfig>()
.add_systems(Startup, spawn_player)
.add_systems(Update, (
player_movement,
player_animation,
player_health,
));
}
}

// game/player/components.rs
use bevy::prelude::*;

#[derive(Component)]
pub struct Player;

#[derive(Component)]
pub struct PlayerHealth {
pub current: f32,
pub maximum: f32,
}

#[derive(Bundle)]
pub struct PlayerBundle {
pub player: Player,
pub health: PlayerHealth,
pub transform: Transform,
pub sprite: Sprite,
}

// game/player/systems.rs
use bevy::prelude::*;
use super::components::*;

pub fn spawn_player(mut commands: Commands) {
commands.spawn(PlayerBundle {
player: Player,
health: PlayerHealth {
current: 100.0,
maximum: 100.0,
},
transform: Transform::from_xyz(0.0, 0.0, 0.0),
sprite: Sprite::new(Vec2::new(32.0, 32.0)),
});
}

pub fn player_movement(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut query: Query<&mut Transform, With<Player>>,
) {
for mut transform in &mut query {
if keyboard_input.pressed(KeyCode::KeyW) {
transform.translation.y += 1.0;
}
if keyboard_input.pressed(KeyCode::KeyS) {
transform.translation.y -= 1.0;
}
if keyboard_input.pressed(KeyCode::KeyA) {
transform.translation.x -= 1.0;
}
if keyboard_input.pressed(KeyCode::KeyD) {
transform.translation.x += 1.0;
}
}
}

最佳实践

1. 模块组织

  • 按功能划分模块,而不是按类型
  • 使用清晰的模块层次结构
  • 在模块根文件中重新导出常用类型
  • 避免过深的模块嵌套

2. 插件设计

  • 每个主要功能创建一个插件
  • 使用配置结构体自定义插件行为
  • 实现插件依赖管理
  • 使用条件编译控制插件功能

3. Bundle设计

  • 将经常一起使用的组件组合成Bundle
  • 提供便捷的构造方法
  • 支持参数化配置
  • 使用组合模式构建复杂Bundle

4. 系统集管理

  • 使用系统集组织相关系统
  • 明确定义系统执行顺序
  • 使用条件系统集控制执行
  • 避免系统集之间的循环依赖

5. 代码组织原则

  • 单一职责: 每个模块、插件、系统只负责一个功能
  • 开闭原则: 对扩展开放,对修改封闭
  • 依赖倒置: 依赖抽象而不是具体实现
  • 接口隔离: 提供小而专注的接口

6. 性能考虑

  • 合理使用系统集避免不必要的系统执行
  • 使用Bundle减少实体创建开销
  • 避免在插件初始化时进行复杂计算
  • 使用条件系统减少运行时开销

常见模式

1. 功能模块模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 每个功能一个模块
mod player {
pub struct PlayerPlugin;
impl Plugin for PlayerPlugin { /* ... */ }
}

mod enemy {
pub struct EnemyPlugin;
impl Plugin for EnemyPlugin { /* ... */ }
}

// 主插件组合所有功能
pub struct GamePlugin;
impl Plugin for GamePlugin {
fn build(&self, app: &mut App) {
app.add_plugins((
player::PlayerPlugin,
enemy::EnemyPlugin,
));
}
}

2. 分层架构模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 数据层
mod data {
mod components;
mod resources;
}

// 逻辑层
mod logic {
mod systems;
mod events;
}

// 表现层
mod presentation {
mod ui;
mod rendering;
}

3. 特性模块模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 核心功能
mod core {
pub struct CorePlugin;
}

// 可选功能
#[cfg(feature = "debug")]
mod debug {
pub struct DebugPlugin;
}

#[cfg(feature = "networking")]
mod networking {
pub struct NetworkingPlugin;
}

通过合理的代码组织,您可以构建出可维护、可扩展、高性能的Bevy游戏项目。记住,好的代码组织不仅能提高开发效率,还能让团队协作更加顺畅。