Gizmos:保留模式与 ConfigGroup
即时模式 Gizmos 每帧重新提交——对于跟踪物体位置、实时可视化状态这类"每帧都变"的场景,这是最自然的方式。但如果你要画的东西基本不动(比如一个固定的参考网格、一个静态的包围圈),每帧重新提交就是浪费。
Bevy 的解决方案是保留模式(retained mode):把 Gizmo 数据存进 GizmoAsset,挂到实体上,引擎只在数据变化时重新上传。
2D 与 3D 图元一览
先把上一节的"分野"落地——Listing 27-2 在同一个窗口里同时展示 2D 和 3D Gizmos:
gizmos.line_2d(Vec2::new(-200., 0.), Vec2::new(200., 0.), Color::WHITE);
gizmos.rect_2d(Isometry2d::IDENTITY, Vec2::splat(160.), Color::srgb(0.9, 0.3, 0.3));
gizmos.circle_2d(Isometry2d::IDENTITY, 100., Color::srgb(0.2, 0.7, 0.4));
gizmos.arrow_2d(
Vec2::ZERO,
Vec2::from_angle(t) * 80.,
Color::srgb(0.3, 0.5, 0.9),
);Listing 27-2(节选):2D 图元——线、矩形、圆、箭头
2D 方法接受 Vec2、Isometry2d(位置 + 旋转),绘制在 2D 相机的平面上。
gizmos.rect(
Isometry3d::new(Vec3::new(-2., 1., 0.), Quat::from_rotation_y(PI / 4.)),
Vec2::splat(1.5),
Color::srgb(0.9, 0.6, 0.2),
);
gizmos.cube(
Transform::from_translation(Vec3::new(0., 0.75, 0.)).with_scale(Vec3::splat(1.5)),
Color::srgb(0.2, 0.5, 0.9),
);
gizmos
.circle(Quat::from_rotation_x(PI / 2.), 2., Color::srgb(0.8, 0.3, 0.8))
.resolution(64);
gizmos
.arc_3d(PI, 1.2, Isometry3d::new(Vec3::new(2., 1., 0.), Quat::IDENTITY), Color::srgb(0.3, 0.8, 0.8))
.resolution(32);Listing 27-2(节选):3D 图元——矩形、立方体、圆、弧
rect 需要一个 Isometry3d(位置 + 朝向)和尺寸;cube 接受 Transform(可以用缩放变成长方体);circle 的第一个参数是旋转——Quat::from_rotation_x(PI / 2.) 让圆平躺在 XZ 平面上;arc_3d 画圆弧,参数依次是弧度、半径、位姿和颜色。
运行:
cargo run -p ch27-devtools --example listing-27-022D 相机(order 0)和 3D 相机(order 1)同时生效。2D 图元画在底层,3D 场景叠在上面。
GizmoConfigGroup:分组控制
当你在同一个场景里有多组 Gizmos(比如"碰撞体可视化"和"路径可视化"),你可能想独立控制它们的线宽、颜色、开关。GizmoConfigGroup 就是为此设计的。
定义一个自定义组只需要三行:
#[derive(Default, Reflect, GizmoConfigGroup)]
struct MyDebugGizmos;然后在 App 里注册:
app.init_gizmo_group::<MyDebugGizmos>();使用时,把 Gizmos 的泛型参数换成你的组:
fn draw_gizmos(
mut default_gizmos: Gizmos,
mut my_gizmos: Gizmos<MyDebugGizmos>,
// ...
) {
default_gizmos.cross(pos, 0.4, RED); // 默认组
my_gizmos.sphere(pos, 0.6, RED); // 自定义组
}运行时可以通过 GizmoConfigStore 资源动态修改每组的配置:
fn toggle_config(mut config_store: ResMut<GizmoConfigStore>) {
let (config, _) = config_store.config_mut::<MyDebugGizmos>();
config.enabled = !config.enabled;
config.line.width = 5.0;
}GizmoConfig 的字段包括:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled | bool | true | 总开关 |
line.width | f32 | 2.0 | 线宽(像素) |
line.perspective | bool | false | 线宽是否随透视缩放 |
line.style | GizmoLineStyle | Solid | 实线 / 虚线 / 点线 |
line.joints | GizmoLineJoint | None | 线段连接样式 |
depth_bias | f32 | 0.0 | 深度偏移,负值让 Gizmo 画在所有物体前面 |
保留模式:GizmoAsset 与 Gizmo 组件
即时模式的 Gizmos 系统参数适合动态数据;保留模式的 GizmoAsset + Gizmo 组件适合静态或低频更新的数据:
let mut gizmo = GizmoAsset::new();
for i in 0..36 {
let angle = i as f32 * 10_f32.to_radians();
let (sin, cos) = ops::sin_cos(angle);
gizmo.line(
Vec3::new(cos, 0.5, sin),
Vec3::new(cos * 1.3, 0.5, sin * 1.3),
YELLOW,
);
}
commands.spawn((
Gizmo {
handle: gizmo_assets.add(gizmo),
line_config: GizmoLineConfig {
width: 3.,
..default()
},
..default()
},
Transform::from_xyz(-2., 0., 0.),
));GizmoAsset 是一个资产,Gizmo 组件把它挂到实体上。Gizmo 组件的 handle 指向资产,line_config 控制这组 Gizmos 的线宽等属性。实体的 Transform 控制 Gizmo 在世界中的位置。
性能差异的核心:即时模式每帧重建命令缓冲区,保留模式只在资产内容变化时重新上传 GPU 数据。画 30000 条静态线时差距明显;画几条每帧都动的线,即时模式反而更简单高效。
完整示例
use bevy::color::palettes::css::*;
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_gizmo_group::<MyDebugGizmos>()
.add_systems(Startup, setup)
.add_systems(Update, draw_gizmos)
.run();
}
// ANCHOR: group
#[derive(Default, Reflect, GizmoConfigGroup)]
struct MyDebugGizmos;
// ANCHOR_END: group
fn setup(
mut commands: Commands,
mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// ANCHOR: retained
let mut gizmo = GizmoAsset::new();
for i in 0..36 {
let angle = i as f32 * 10_f32.to_radians();
let (sin, cos) = ops::sin_cos(angle);
gizmo.line(
Vec3::new(cos, 0.5, sin),
Vec3::new(cos * 1.3, 0.5, sin * 1.3),
YELLOW,
);
}
commands.spawn((
Gizmo {
handle: gizmo_assets.add(gizmo),
line_config: GizmoLineConfig {
width: 3.,
..default()
},
..default()
},
Transform::from_xyz(-2., 0., 0.),
));
// ANCHOR_END: retained
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0., 3., 6.).looking_at(Vec3::ZERO, Vec3::Y),
));
commands.spawn((
PointLight::default(),
Transform::from_xyz(4.0, 8.0, 4.0),
));
commands.spawn((
Mesh3d(meshes.add(Cuboid::new(1., 1., 1.))),
MeshMaterial3d(materials.add(Color::srgb(0.6, 0.6, 0.6))),
Transform::from_xyz(0., 0.5, 0.),
));
}
// ANCHOR: immediate_vs_retained
fn draw_gizmos(
mut default_gizmos: Gizmos,
mut my_gizmos: Gizmos<MyDebugGizmos>,
time: Res<Time>,
) {
let t = time.elapsed_secs();
let pos = Vec3::new(ops::cos(t) * 2., 0.5, ops::sin(t) * 2.);
// 默认组:跟踪物体位置的十字标
default_gizmos.cross(pos, 0.4, RED);
// 自定义组:红色包围球
my_gizmos.sphere(pos, 0.6, RED);
}
// ANCHOR_END: immediate_vs_retainedListing 27-3:ConfigGroup 分组与保留模式 GizmoAsset
运行:
cargo run -p ch27-devtools --example listing-27-03你会看到一个灰白色立方体、一个由 36 条放射线组成的静态"太阳"(保留模式),以及一个围绕立方体旋转的十字标和红色包围球(即时模式,分属两个 ConfigGroup)。