Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/3d/split_screen.rs
6849 views
1
//! Renders four cameras to the same window to accomplish "split screen".
2
3
use std::f32::consts::PI;
4
5
use bevy::{
6
camera::Viewport, light::CascadeShadowConfigBuilder, prelude::*, window::WindowResized,
7
};
8
9
fn main() {
10
App::new()
11
.add_plugins(DefaultPlugins)
12
.add_systems(Startup, setup)
13
.add_systems(Update, (set_camera_viewports, button_system))
14
.run();
15
}
16
17
/// set up a simple 3D scene
18
fn setup(
19
mut commands: Commands,
20
asset_server: Res<AssetServer>,
21
mut meshes: ResMut<Assets<Mesh>>,
22
mut materials: ResMut<Assets<StandardMaterial>>,
23
) {
24
// plane
25
commands.spawn((
26
Mesh3d(meshes.add(Plane3d::default().mesh().size(100.0, 100.0))),
27
MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
28
));
29
30
commands.spawn(SceneRoot(
31
asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")),
32
));
33
34
// Light
35
commands.spawn((
36
Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 1.0, -PI / 4.)),
37
DirectionalLight {
38
shadows_enabled: true,
39
..default()
40
},
41
CascadeShadowConfigBuilder {
42
num_cascades: if cfg!(all(
43
feature = "webgl2",
44
target_arch = "wasm32",
45
not(feature = "webgpu")
46
)) {
47
// Limited to 1 cascade in WebGL
48
1
49
} else {
50
2
51
},
52
first_cascade_far_bound: 200.0,
53
maximum_distance: 280.0,
54
..default()
55
}
56
.build(),
57
));
58
59
// Cameras and their dedicated UI
60
for (index, (camera_name, camera_pos)) in [
61
("Player 1", Vec3::new(0.0, 200.0, -150.0)),
62
("Player 2", Vec3::new(150.0, 150., 50.0)),
63
("Player 3", Vec3::new(100.0, 150., -150.0)),
64
("Player 4", Vec3::new(-100.0, 80., 150.0)),
65
]
66
.iter()
67
.enumerate()
68
{
69
let camera = commands
70
.spawn((
71
Camera3d::default(),
72
Transform::from_translation(*camera_pos).looking_at(Vec3::ZERO, Vec3::Y),
73
Camera {
74
// Renders cameras with different priorities to prevent ambiguities
75
order: index as isize,
76
..default()
77
},
78
CameraPosition {
79
pos: UVec2::new((index % 2) as u32, (index / 2) as u32),
80
},
81
))
82
.id();
83
84
// Set up UI
85
commands.spawn((
86
UiTargetCamera(camera),
87
Node {
88
width: percent(100),
89
height: percent(100),
90
..default()
91
},
92
children![
93
(
94
Text::new(*camera_name),
95
Node {
96
position_type: PositionType::Absolute,
97
top: px(12),
98
left: px(12),
99
..default()
100
},
101
),
102
buttons_panel(),
103
],
104
));
105
}
106
107
fn buttons_panel() -> impl Bundle {
108
(
109
Node {
110
position_type: PositionType::Absolute,
111
width: percent(100),
112
height: percent(100),
113
display: Display::Flex,
114
flex_direction: FlexDirection::Row,
115
justify_content: JustifyContent::SpaceBetween,
116
align_items: AlignItems::Center,
117
padding: UiRect::all(px(20)),
118
..default()
119
},
120
children![
121
rotate_button("<", Direction::Left),
122
rotate_button(">", Direction::Right),
123
],
124
)
125
}
126
127
fn rotate_button(caption: &str, direction: Direction) -> impl Bundle {
128
(
129
RotateCamera(direction),
130
Button,
131
Node {
132
width: px(40),
133
height: px(40),
134
border: UiRect::all(px(2)),
135
justify_content: JustifyContent::Center,
136
align_items: AlignItems::Center,
137
..default()
138
},
139
BorderColor::all(Color::WHITE),
140
BackgroundColor(Color::srgb(0.25, 0.25, 0.25)),
141
children![Text::new(caption)],
142
)
143
}
144
}
145
146
#[derive(Component)]
147
struct CameraPosition {
148
pos: UVec2,
149
}
150
151
#[derive(Component)]
152
struct RotateCamera(Direction);
153
154
enum Direction {
155
Left,
156
Right,
157
}
158
159
fn set_camera_viewports(
160
windows: Query<&Window>,
161
mut window_resized_reader: MessageReader<WindowResized>,
162
mut query: Query<(&CameraPosition, &mut Camera)>,
163
) {
164
// We need to dynamically resize the camera's viewports whenever the window size changes
165
// so then each camera always takes up half the screen.
166
// A resize_event is sent when the window is first created, allowing us to reuse this system for initial setup.
167
for window_resized in window_resized_reader.read() {
168
let window = windows.get(window_resized.window).unwrap();
169
let size = window.physical_size() / 2;
170
171
for (camera_position, mut camera) in &mut query {
172
camera.viewport = Some(Viewport {
173
physical_position: camera_position.pos * size,
174
physical_size: size,
175
..default()
176
});
177
}
178
}
179
}
180
181
fn button_system(
182
interaction_query: Query<
183
(&Interaction, &ComputedUiTargetCamera, &RotateCamera),
184
(Changed<Interaction>, With<Button>),
185
>,
186
mut camera_query: Query<&mut Transform, With<Camera>>,
187
) {
188
for (interaction, computed_target, RotateCamera(direction)) in &interaction_query {
189
if let Interaction::Pressed = *interaction {
190
// Since TargetCamera propagates to the children, we can use it to find
191
// which side of the screen the button is on.
192
if let Some(mut camera_transform) = computed_target
193
.get()
194
.and_then(|camera| camera_query.get_mut(camera).ok())
195
{
196
let angle = match direction {
197
Direction::Left => -0.1,
198
Direction::Right => 0.1,
199
};
200
camera_transform.rotate_around(Vec3::ZERO, Quat::from_axis_angle(Vec3::Y, angle));
201
}
202
}
203
}
204
}
205
206