Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/mobile/src/lib.rs
6595 views
1
//! A 3d Scene with a button and playing sound.
2
3
use bevy::{
4
color::palettes::basic::*,
5
input::{gestures::RotationGesture, touch::TouchPhase},
6
log::{Level, LogPlugin},
7
prelude::*,
8
window::{AppLifecycle, ScreenEdge, WindowMode},
9
winit::WinitSettings,
10
};
11
12
// the `bevy_main` proc_macro generates the required boilerplate for Android
13
#[bevy_main]
14
/// The entry point for the application. Is `pub` so that it can be used from
15
/// `main.rs`.
16
pub fn main() {
17
let mut app = App::new();
18
app.add_plugins(
19
DefaultPlugins
20
.set(LogPlugin {
21
// This will show some log events from Bevy to the native logger.
22
level: Level::DEBUG,
23
filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(),
24
..Default::default()
25
})
26
.set(WindowPlugin {
27
primary_window: Some(Window {
28
resizable: false,
29
mode: WindowMode::BorderlessFullscreen(MonitorSelection::Primary),
30
// on iOS, gestures must be enabled.
31
// This doesn't work on Android
32
recognize_rotation_gesture: true,
33
// Only has an effect on iOS
34
prefers_home_indicator_hidden: true,
35
// Only has an effect on iOS
36
prefers_status_bar_hidden: true,
37
// Only has an effect on iOS
38
preferred_screen_edges_deferring_system_gestures: ScreenEdge::Bottom,
39
..default()
40
}),
41
..default()
42
}),
43
)
44
// Make the winit loop wait more aggressively when no user input is received
45
// This can help reduce cpu usage on mobile devices
46
.insert_resource(WinitSettings::mobile())
47
.add_systems(Startup, (setup_scene, setup_music))
48
.add_systems(
49
Update,
50
(
51
touch_camera,
52
button_handler,
53
// Only run the lifetime handler when an [`AudioSink`] component exists in the world.
54
// This ensures we don't try to manage audio that hasn't been initialized yet.
55
handle_lifetime.run_if(any_with_component::<AudioSink>),
56
),
57
)
58
.run();
59
}
60
61
fn touch_camera(
62
window: Query<&Window>,
63
mut touches: EventReader<TouchInput>,
64
mut camera_transform: Single<&mut Transform, With<Camera3d>>,
65
mut last_position: Local<Option<Vec2>>,
66
mut rotations: EventReader<RotationGesture>,
67
) {
68
let Ok(window) = window.single() else {
69
return;
70
};
71
72
for touch in touches.read() {
73
if touch.phase == TouchPhase::Started {
74
*last_position = None;
75
}
76
if let Some(last_position) = *last_position {
77
**camera_transform = Transform::from_xyz(
78
camera_transform.translation.x
79
+ (touch.position.x - last_position.x) / window.width() * 5.0,
80
camera_transform.translation.y,
81
camera_transform.translation.z
82
+ (touch.position.y - last_position.y) / window.height() * 5.0,
83
)
84
.looking_at(Vec3::ZERO, Vec3::Y);
85
}
86
*last_position = Some(touch.position);
87
}
88
// Rotation gestures only work on iOS
89
for rotation in rotations.read() {
90
let forward = camera_transform.forward();
91
camera_transform.rotate_axis(forward, rotation.0 / 10.0);
92
}
93
}
94
95
/// set up a simple 3D scene
96
fn setup_scene(
97
mut commands: Commands,
98
mut meshes: ResMut<Assets<Mesh>>,
99
mut materials: ResMut<Assets<StandardMaterial>>,
100
) {
101
// plane
102
commands.spawn((
103
Mesh3d(meshes.add(Plane3d::default().mesh().size(5.0, 5.0))),
104
MeshMaterial3d(materials.add(Color::srgb(0.1, 0.2, 0.1))),
105
));
106
// cube
107
commands.spawn((
108
Mesh3d(meshes.add(Cuboid::default())),
109
MeshMaterial3d(materials.add(Color::srgb(0.5, 0.4, 0.3))),
110
Transform::from_xyz(0.0, 0.5, 0.0),
111
));
112
// sphere
113
commands.spawn((
114
Mesh3d(meshes.add(Sphere::new(0.5).mesh().ico(4).unwrap())),
115
MeshMaterial3d(materials.add(Color::srgb(0.1, 0.4, 0.8))),
116
Transform::from_xyz(1.5, 1.5, 1.5),
117
));
118
// light
119
commands.spawn((
120
PointLight {
121
intensity: 1_000_000.0,
122
// Shadows makes some Android devices segfault, this is under investigation
123
// https://github.com/bevyengine/bevy/issues/8214
124
#[cfg(not(target_os = "android"))]
125
shadows_enabled: true,
126
..default()
127
},
128
Transform::from_xyz(4.0, 8.0, 4.0),
129
));
130
// camera
131
commands.spawn((
132
Camera3d::default(),
133
Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
134
// MSAA makes some Android devices panic, this is under investigation
135
// https://github.com/bevyengine/bevy/issues/8229
136
#[cfg(target_os = "android")]
137
Msaa::Off,
138
));
139
140
// Test ui
141
commands
142
.spawn((
143
Button,
144
Node {
145
justify_content: JustifyContent::Center,
146
align_items: AlignItems::Center,
147
position_type: PositionType::Absolute,
148
left: px(50),
149
right: px(50),
150
bottom: px(50),
151
..default()
152
},
153
))
154
.with_child((
155
Text::new("Test Button"),
156
TextFont {
157
font_size: 30.0,
158
..default()
159
},
160
TextColor::BLACK,
161
TextLayout::new_with_justify(Justify::Center),
162
));
163
}
164
165
fn button_handler(
166
mut interaction_query: Query<
167
(&Interaction, &mut BackgroundColor),
168
(Changed<Interaction>, With<Button>),
169
>,
170
) {
171
for (interaction, mut color) in &mut interaction_query {
172
match *interaction {
173
Interaction::Pressed => {
174
*color = BLUE.into();
175
}
176
Interaction::Hovered => {
177
*color = GRAY.into();
178
}
179
Interaction::None => {
180
*color = WHITE.into();
181
}
182
}
183
}
184
}
185
186
fn setup_music(asset_server: Res<AssetServer>, mut commands: Commands) {
187
commands.spawn((
188
AudioPlayer::new(asset_server.load("sounds/Windless Slopes.ogg")),
189
PlaybackSettings::LOOP,
190
));
191
}
192
193
// Pause audio when app goes into background and resume when it returns.
194
// This is handled by the OS on iOS, but not on Android.
195
fn handle_lifetime(
196
mut lifecycle_events: EventReader<AppLifecycle>,
197
music_controller: Single<&AudioSink>,
198
) {
199
for event in lifecycle_events.read() {
200
match event {
201
AppLifecycle::Idle | AppLifecycle::WillSuspend | AppLifecycle::WillResume => {}
202
AppLifecycle::Suspended => music_controller.pause(),
203
AppLifecycle::Running => music_controller.play(),
204
}
205
}
206
}
207
208