逻辑-渲染分离

概述

学习目标

  • 理解逻辑-渲染分离的基本概念
  • 掌握 MainWorld 和 RenderApp 的使用
  • 了解 ExtractSchedule 的作用
  • 学会使用 Headless 模式和无渲染模式

前置知识要求

  • Bevy 快速入门
  • ECS 基础
  • 代码组织基础

核心概念

什么是逻辑-渲染分离?

逻辑-渲染分离是 Bevy 的重要架构模式。它将游戏逻辑和渲染逻辑分离到不同的线程中,实现并行执行。

为什么需要逻辑-渲染分离?

  1. 性能优化:逻辑和渲染可以并行执行,提高性能
  2. 架构清晰:逻辑和渲染分离,使架构更加清晰
  3. 灵活性:可以独立优化逻辑和渲染
  4. 可测试性:可以独立测试逻辑和渲染

逻辑-渲染分离的核心组件

Bevy 逻辑-渲染分离包含以下核心组件:

  • MainWorld:主世界,运行游戏逻辑
  • RenderApp:渲染应用,运行渲染逻辑
  • ExtractSchedule:提取调度,用于从 MainWorld 同步数据到 RenderApp
  • SyncToRenderWorld:同步到渲染世界,用于同步组件数据

基础用法

MainWorld 和 RenderApp

理解 MainWorld 和 RenderApp 的关系。

架构

1
2
3
4
5
主线程(MainWorld)         渲染线程(RenderApp)
├─ 游戏逻辑更新 ├─ Extract(提取数据)
├─ 物理模拟 ├─ Prepare(准备渲染)
├─ AI 计算 ├─ Queue(队列渲染)
└─ 输入处理 └─ Render(实际渲染)

关键要点

  • MainWorld 运行游戏逻辑
  • RenderApp 运行渲染逻辑
  • 两者通过 ExtractSchedule 同步数据
  • 渲染逻辑在独立线程中运行

说明
MainWorld 和 RenderApp 的分离是 Bevy 性能优化的关键。通过将逻辑和渲染分离,可以实现并行执行,提高性能。

ExtractSchedule

使用 ExtractSchedule 同步数据。

关键信息

  • ExtractSchedule 在每帧开始时运行
  • 从 MainWorld 提取数据到 RenderApp
  • 可以提取组件、资源等数据
  • 数据同步是单向的(MainWorld → RenderApp)

说明
ExtractSchedule 是逻辑-渲染分离的关键。通过 ExtractSchedule,可以将游戏逻辑数据同步到渲染世界,供渲染使用。

Headless 模式

使用 Headless 模式运行无窗口应用。

源代码文件bevy/examples/app/headless.rs

代码示例

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
use bevy::{app::ScheduleRunnerPlugin, log::LogPlugin, prelude::*};
use core::time::Duration;

fn main() {
// 禁用默认特性,只启用需要的功能
// Cargo.toml:
// [dependencies]
// bevy = { version = "*", default-features = false, features = ["bevy_log"] }

// 运行一次的应用
App::new()
.add_plugins(DefaultPlugins.set(ScheduleRunnerPlugin::run_once()))
.add_systems(Update, hello_world_system)
.run();

// 以 60 FPS 循环运行的应用
App::new()
.add_plugins(
DefaultPlugins
.set(ScheduleRunnerPlugin::run_loop(Duration::from_secs_f64(1.0 / 60.0)))
.disable::<LogPlugin>(),
)
.add_systems(Update, counter)
.run();
}

fn hello_world_system() {
println!("hello world");
}

fn counter(mut state: Local<CounterState>) {
if state.count.is_multiple_of(60) {
println!("{}", state.count);
}
state.count += 1;
}

#[derive(Default)]
struct CounterState {
count: u32,
}

关键要点

  • Headless 模式不需要窗口
  • 可以用于服务器端应用
  • 可以用于纯逻辑处理
  • 需要禁用默认特性并只启用需要的功能

说明
Headless 模式适合服务器端应用、纯逻辑处理等场景。通过禁用窗口和渲染功能,可以减少资源消耗。

无渲染模式

使用无渲染模式运行有窗口但无渲染的应用。

源代码文件bevy/examples/app/no_renderer.rs

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use bevy::{
prelude::*,
render::{settings::WgpuSettings, RenderPlugin},
};

fn main() {
App::new()
.add_plugins(
DefaultPlugins.set(RenderPlugin {
render_creation: WgpuSettings {
backends: None,
..default()
}
.into(),
..default()
}),
)
.run();
}

关键要点

  • 无渲染模式显示窗口但不渲染
  • 可以用于集成测试或 CI
  • 可以用于调试逻辑
  • 需要禁用渲染后端

说明
无渲染模式适合集成测试、CI 等场景。通过禁用渲染后端,可以运行应用但不进行实际渲染。

进阶用法

数据同步

在 MainWorld 和 RenderApp 之间同步数据。

关键信息

  • 使用 Extract 系统提取数据
  • 使用 SyncToRenderWorld 同步组件
  • 数据同步是单向的
  • 可以同步组件、资源等数据

说明
数据同步是逻辑-渲染分离的关键。通过正确同步数据,可以确保渲染世界有正确的数据。

渲染世界访问

从 MainWorld 访问渲染世界。

关键信息

  • 可以使用 RenderApp 访问渲染世界
  • 可以添加渲染系统
  • 可以配置渲染管线
  • 需要注意线程安全

说明
渲染世界访问允许从主世界配置渲染。这对于创建自定义渲染效果非常有用。

Headless 渲染器

创建 Headless 渲染器。

源代码文件bevy/examples/app/headless_renderer.rs

关键信息

  • 可以创建无窗口的渲染器
  • 可以渲染到图像
  • 可以保存渲染结果
  • 适合批量渲染、CI 等场景

说明
Headless 渲染器允许在没有窗口的情况下进行渲染。这对于批量渲染、CI 等场景非常有用。

实际应用

在游戏开发中的应用场景

逻辑-渲染分离在游戏开发中有广泛的应用:

  1. 性能优化:通过并行执行逻辑和渲染提高性能
  2. 服务器端应用:使用 Headless 模式创建服务器
  3. 集成测试:使用无渲染模式进行集成测试
  4. 批量渲染:使用 Headless 渲染器进行批量渲染

常见问题

问题 1:如何确保数据同步正确?

解决方案:使用 Extract 系统正确提取数据,并使用 SyncToRenderWorld 同步组件。

问题 2:如何在 Headless 模式下运行应用?

解决方案:禁用默认特性,只启用需要的功能,并使用 ScheduleRunnerPlugin 控制运行方式。

问题 3:如何从 MainWorld 访问 RenderApp?

解决方案:使用 App::sub_app_mut() 访问 RenderApp,但需要注意线程安全。

性能考虑

  1. 数据同步开销:尽量减少需要同步的数据量
  2. 线程安全:注意 MainWorld 和 RenderApp 之间的线程安全
  3. 内存使用:注意 MainWorld 和 RenderApp 之间的内存使用
  4. 同步频率:合理控制数据同步频率

相关资源

相关源代码文件

  • bevy/examples/app/headless.rs - Headless 模式示例
  • bevy/examples/app/no_renderer.rs - 无渲染模式示例
  • bevy/examples/app/headless_renderer.rs - Headless 渲染器示例

官方文档链接

进一步学习建议

  • 学习代码组织,了解如何组织逻辑-渲染分离的代码
  • 学习性能优化,了解如何优化逻辑-渲染分离的性能
  • 学习自定义渲染,了解如何在渲染世界中创建自定义效果

索引返回上级目录