Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/gizmos/transform_gizmo.rs
30632 views
1
//! Interactive transform gizmo example.
2
//!
3
//! Demonstrates translate, rotate, and scale gizmos with click-to-select.
4
//! - Click an object to select it (primary mouse button)
5
//! - **1** = Translate, **2** = Rotate, **3** = Scale
6
//! - **X** = Toggle World/Local space
7
8
use bevy::{
9
camera_controller::free_camera::{FreeCamera, FreeCameraPlugin},
10
gizmos::transform_gizmo::{
11
TransformGizmoCamera, TransformGizmoFocus, TransformGizmoMode, TransformGizmoPlugin,
12
TransformGizmoSettings, TransformGizmoSpace,
13
},
14
picking::{pointer::PointerButton, Pickable},
15
prelude::*,
16
};
17
18
fn main() {
19
App::new()
20
.add_plugins((
21
DefaultPlugins,
22
FreeCameraPlugin,
23
MeshPickingPlugin,
24
TransformGizmoPlugin,
25
))
26
.add_systems(Startup, setup)
27
.add_systems(Update, (gizmo_mode_keys, update_instructions))
28
.run();
29
}
30
31
fn setup(
32
mut commands: Commands,
33
mut meshes: ResMut<Assets<Mesh>>,
34
mut materials: ResMut<Assets<StandardMaterial>>,
35
) {
36
// Instructions
37
commands.spawn((
38
Text::new(
39
"Click an object to select it\n1: Translate | 2: Rotate | 3: Scale | X: World/Local space",
40
),
41
Node {
42
position_type: PositionType::Absolute,
43
top: px(12),
44
left: px(12),
45
..default()
46
},
47
InstructionsText,
48
));
49
50
// Ground plane (not pickable)
51
commands.spawn((
52
Mesh3d(meshes.add(Plane3d::default().mesh().size(10.0, 10.0))),
53
MeshMaterial3d(materials.add(Color::srgb(0.3, 0.3, 0.3))),
54
Pickable::IGNORE,
55
));
56
57
// Table: a parent body with a child part, demonstrating local vs world space.
58
// The parent cube is selected by default.
59
commands
60
.spawn((
61
Mesh3d(meshes.add(Cuboid::new(1.5, 0.15, 1.0))),
62
MeshMaterial3d(materials.add(Color::srgb(0.8, 0.3, 0.3))),
63
Transform::from_xyz(-2.0, 1.0, 0.0),
64
TransformGizmoFocus,
65
))
66
.observe(on_click_select)
67
.with_children(|parent| {
68
// Table leg (child)
69
parent.spawn((
70
Mesh3d(meshes.add(Cuboid::new(0.1, 0.85, 0.1))),
71
MeshMaterial3d(materials.add(Color::srgb(0.6, 0.2, 0.2))),
72
Transform::from_xyz(-0.6, -0.5, 0.4),
73
Pickable::IGNORE,
74
));
75
parent.spawn((
76
Mesh3d(meshes.add(Cuboid::new(0.1, 0.85, 0.1))),
77
MeshMaterial3d(materials.add(Color::srgb(0.6, 0.2, 0.2))),
78
Transform::from_xyz(0.6, -0.5, 0.4),
79
Pickable::IGNORE,
80
));
81
parent.spawn((
82
Mesh3d(meshes.add(Cuboid::new(0.1, 0.85, 0.1))),
83
MeshMaterial3d(materials.add(Color::srgb(0.6, 0.2, 0.2))),
84
Transform::from_xyz(-0.6, -0.5, -0.4),
85
Pickable::IGNORE,
86
));
87
parent.spawn((
88
Mesh3d(meshes.add(Cuboid::new(0.1, 0.85, 0.1))),
89
MeshMaterial3d(materials.add(Color::srgb(0.6, 0.2, 0.2))),
90
Transform::from_xyz(0.6, -0.5, -0.4),
91
Pickable::IGNORE,
92
));
93
});
94
95
// Standalone cube
96
commands
97
.spawn((
98
Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
99
MeshMaterial3d(materials.add(Color::srgb(0.3, 0.8, 0.3))),
100
Transform::from_xyz(2.0, 0.5, 0.0),
101
))
102
.observe(on_click_select);
103
104
// Light
105
commands.spawn((
106
DirectionalLight {
107
shadow_maps_enabled: true,
108
..default()
109
},
110
Transform::from_rotation(Quat::from_euler(EulerRot::XYZ, -0.8, 0.4, 0.0)),
111
));
112
113
// Camera
114
commands.spawn((
115
Camera3d::default(),
116
Transform::from_xyz(0.0, 4.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y),
117
FreeCamera::default(),
118
TransformGizmoCamera,
119
));
120
}
121
122
fn on_click_select(
123
click: On<Pointer<Click>>,
124
mut commands: Commands,
125
existing: Query<Entity, With<TransformGizmoFocus>>,
126
) {
127
if click.button != PointerButton::Primary {
128
return;
129
}
130
// Remove focus from all entities
131
for e in &existing {
132
commands.entity(e).remove::<TransformGizmoFocus>();
133
}
134
// Add focus to clicked entity
135
commands.entity(click.entity).insert(TransformGizmoFocus);
136
}
137
138
// Note: Using 1/2/3 instead of Blender's G/R/S because S conflicts with
139
// the FreeCameraPlugin's WASD movement controls.
140
fn gizmo_mode_keys(
141
keyboard: Res<ButtonInput<KeyCode>>,
142
mut settings: ResMut<TransformGizmoSettings>,
143
) {
144
if keyboard.just_pressed(KeyCode::Digit1) {
145
settings.mode = TransformGizmoMode::Translate;
146
}
147
if keyboard.just_pressed(KeyCode::Digit2) {
148
settings.mode = TransformGizmoMode::Rotate;
149
}
150
if keyboard.just_pressed(KeyCode::Digit3) {
151
settings.mode = TransformGizmoMode::Scale;
152
}
153
if keyboard.just_pressed(KeyCode::KeyX) {
154
settings.space = match settings.space {
155
TransformGizmoSpace::World => TransformGizmoSpace::Local,
156
TransformGizmoSpace::Local => TransformGizmoSpace::World,
157
};
158
}
159
}
160
161
#[derive(Component)]
162
struct InstructionsText;
163
164
fn update_instructions(
165
settings: Res<TransformGizmoSettings>,
166
mut text: Single<&mut Text, With<InstructionsText>>,
167
) {
168
let mode_str = match settings.mode {
169
TransformGizmoMode::Translate => "Translate",
170
TransformGizmoMode::Rotate => "Rotate",
171
TransformGizmoMode::Scale => "Scale",
172
};
173
let space_str = match settings.space {
174
TransformGizmoSpace::World => "World",
175
TransformGizmoSpace::Local => "Local",
176
};
177
text.0 = format!(
178
"Click an object to select it\n1: Translate | 2: Rotate | 3: Scale | X: World/Local space\nMode: {mode_str} | Space: {space_str}"
179
);
180
}
181
182