pub struct FrameContext<'a, G: Game> { /* private fields */ }Expand description
The work Game::frame may do.
Implementations§
Source§impl<'a, G: Game> FrameContext<'a, G>
impl<'a, G: Game> FrameContext<'a, G>
Sourcepub fn down<A: InputButtonAction>(&self, action: A) -> bool
pub fn down<A: InputButtonAction>(&self, action: A) -> bool
Whether action is held right now.
Examples found in repository?
More examples
359 fn handle_camera(&mut self, ctx: &mut FrameContext<'_, Self>, elapsed: f32) {
360 if ctx.ui_wants_pointer() || ctx.ui_wants_keyboard() {
361 return;
362 }
363 let pan = ctx.axis2(Motion::Pan);
364 let wheel = ctx.axis(Height::Wheel);
365 let look = if ctx.down(Drag::Turn) {
366 ctx.axis2(Motion::Look)
367 } else {
368 Vec2::ZERO
369 };
370 if pan == Vec2::ZERO && wheel == 0.0 && look == Vec2::ZERO {
371 return;
372 }
373
374 let player = self.player.get_or_insert_with(|| {
375 let eye = Self::orbit_eye(elapsed);
376 let forward = (Vec3::ZERO - eye).normalize();
377 Player {
378 eye,
379 yaw: (-forward.x).atan2(-forward.z),
380 pitch: forward.y.asin(),
381 }
382 });
383
384 player.yaw -= look.x;
385 player.pitch = (player.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
386
387 let forward = Vec3::new(-player.yaw.sin(), 0.0, -player.yaw.cos());
388 let right = Vec3::new(player.yaw.cos(), 0.0, -player.yaw.sin());
389 player.eye += (forward * pan.y + right * pan.x) * PAN_SPEED * ctx.dt().as_secs_f32();
390 player.eye.y =
391 (player.eye.y + wheel * WHEEL_STEP).clamp(MIN_CAMERA_HEIGHT, MAX_CAMERA_HEIGHT);
392 }802 fn fly_camera(&mut self, ctx: &mut FrameContext<'_, Self>) {
803 if !ctx.ui_wants_pointer() && ctx.down(Move::Look) {
804 let look = ctx.axis2(Turn::Look);
805 self.yaw -= look.x;
806 self.pitch = (self.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
807 }
808
809 let wheel = ctx.axis(Speed::Wheel);
810 if !ctx.ui_wants_pointer() && wheel != 0.0 {
811 self.speed_scale =
812 (self.speed_scale * SPEED_STEP.powf(wheel)).clamp(MIN_SPEED_SCALE, MAX_SPEED_SCALE);
813 }
814
815 let forward = self.forward();
816 let right = Vec3::new(self.yaw.cos(), 0.0, -self.yaw.sin());
817 let mut move_by = Vec3::ZERO;
818 if ctx.down(Move::Forward) {
819 move_by += forward;
820 }
821 if ctx.down(Move::Back) {
822 move_by -= forward;
823 }
824 if ctx.down(Move::Right) {
825 move_by += right;
826 }
827 if ctx.down(Move::Left) {
828 move_by -= right;
829 }
830 if ctx.down(Move::Up) {
831 move_by += Vec3::Y;
832 }
833 if ctx.down(Move::Down) {
834 move_by -= Vec3::Y;
835 }
836 if move_by.length_squared() > 1.0 {
837 move_by = move_by.normalize();
838 }
839
840 self.eye += move_by * MOVE_SPEED * self.speed_scale * ctx.dt().as_secs_f32();
841 self.eye.y = self.eye.y.max(MIN_EYE_HEIGHT);
842 }238 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239 // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240 // is taken first, and `rebind` is applied once the closure returns.
241 let buttons: Vec<_> = ButtonAction::all()
242 .into_iter()
243 .map(|action| {
244 (
245 action,
246 bindings_text(ctx.bindings(action)),
247 ctx.down(action),
248 ctx.pressed(action),
249 ctx.released(action),
250 ctx.clicks(action),
251 )
252 })
253 .collect();
254 let axes: Vec<_> = AxisAction::all()
255 .into_iter()
256 .map(|action| {
257 (
258 action,
259 bindings_text(ctx.bindings(action)),
260 ctx.axis(action),
261 )
262 })
263 .collect();
264 let axes2: Vec<_> = Axis2Action::all()
265 .into_iter()
266 .map(|action| {
267 (
268 action,
269 bindings_text(ctx.bindings(action)),
270 ctx.axis2(action),
271 )
272 })
273 .collect();
274
275 let capturing = !ctx.ui_wants_keyboard();
276 let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277 let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278 let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279 if actuated_button.is_some() {
280 self.last_button = actuated_button;
281 }
282 if actuated_axis.is_some() {
283 self.last_axis = actuated_axis;
284 }
285 if actuated_axis2.is_some() {
286 self.last_axis2 = actuated_axis2;
287 }
288 let pointer = ctx.pointer();
289 let mut edits = RowEdits {
290 listening: self.listening,
291 start_listening: None,
292 cancel: false,
293 reset: None,
294 };
295
296 ctx.ui(|ui| {
297 egui::CentralPanel::default().show(ui, |ui| {
298 ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299 ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300 ui.label("rebinds persist across runs");
301 ui.label(format!(
302 "last captured: button {}, pad axis {}, pad stick {}",
303 text_of(self.last_button),
304 text_of(self.last_axis),
305 text_of(self.last_axis2),
306 ));
307 ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308 ui.separator();
309
310 ui.horizontal(|ui| {
311 ui.vertical(|ui| {
312 ui.heading("buttons");
313 egui::Grid::new("buttons-grid")
314 .num_columns(5)
315 .spacing([6.0, 2.0])
316 .show(ui, |ui| {
317 for (action, bindings, down, pressed, released, clicks) in &buttons
318 {
319 let control = Control::Button(*action);
320 ui.label(action.name());
321 ui.label(bindings);
322 ui.horizontal(|ui| {
323 mark(ui, "down", *down);
324 mark(ui, "pressed", *pressed);
325 mark(ui, "released", *released);
326 ui.label(format!("clicks {clicks}"));
327 });
328 rebind_cell(ui, control, &mut edits);
329 reset_cell(ui, control, &mut edits);
330 ui.end_row();
331 }
332 });
333 });
334
335 ui.separator();
336
337 ui.vertical(|ui| {
338 egui::Grid::new("axes-grid")
339 .num_columns(5)
340 .spacing([6.0, 2.0])
341 .show(ui, |ui| {
342 ui.heading("axes");
343 ui.end_row();
344 for (action, bindings, value) in &axes {
345 let control = Control::Axis(*action);
346 ui.label(action.name());
347 ui.label(bindings);
348 axis_bar(ui, *value);
349 rebind_cell(ui, control, &mut edits);
350 reset_cell(ui, control, &mut edits);
351 ui.end_row();
352 }
353
354 ui.heading("vectors");
355 ui.end_row();
356 for (action, bindings, value) in &axes2 {
357 let control = Control::Axis2(*action);
358 ui.label(action.name());
359 ui.label(bindings);
360 axis2_dot(ui, *value);
361 rebind_cell(ui, control, &mut edits);
362 reset_cell(ui, control, &mut edits);
363 ui.end_row();
364 }
365 });
366 });
367 });
368 });
369 });
370
371 if edits.cancel {
372 self.listening = None;
373 }
374 if let Some(control) = edits.start_listening {
375 self.listening = Some(control);
376 }
377 if let Some(control) = edits.reset {
378 match control {
379 Control::Button(action) => ctx.rebind(action, action.bindings()),
380 Control::Axis(action) => ctx.rebind(action, action.bindings()),
381 Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382 }
383 }
384 match (
385 self.listening,
386 actuated_button,
387 actuated_axis,
388 actuated_axis2,
389 ) {
390 (Some(Control::Button(action)), Some(binding), _, _) => {
391 ctx.rebind(action, vec![binding]);
392 self.listening = None;
393 }
394 (Some(Control::Axis(action)), _, Some(binding), _) => {
395 ctx.rebind(action, vec![binding]);
396 self.listening = None;
397 }
398 (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399 ctx.rebind(action, vec![binding]);
400 self.listening = None;
401 }
402 _ => {}
403 }
404 }Sourcepub fn pressed<A: InputButtonAction>(&self, action: A) -> bool
pub fn pressed<A: InputButtonAction>(&self, action: A) -> bool
Whether action went down during this frame.
Examples found in repository?
995 fn frame(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
996 if matches!(self.phase, Phase::Serving | Phase::Playing) && ctx.pressed(Button::Pause) {
997 self.paused = !self.paused;
998 }
999
1000 ctx.set_volume(self.master_volume);
1001 self.sustain_music(ctx);
1002
1003 ctx.set_camera(Self::camera());
1004
1005 let brick_pulse = (self.brick_flash / BRICK_FLASH).clamp(0.0, 1.0);
1006 ctx.set_bloom((BLOOM_BASE + brick_pulse * BLOOM_PULSE_PEAK).clamp(0.0, 1.0));
1007
1008 let life_lost_t = (self.life_lost_flash / LIFE_LOST_FLASH).clamp(0.0, 1.0);
1009 ctx.set_exposure((1.0 - life_lost_t * EXPOSURE_DIP_DEPTH).clamp(0.0, 1.0));
1010
1011 // The tick moves nothing behind a menu, so a frame there draws the last
1012 // step whole rather than interpolating from the one before.
1013 let alpha = match self.phase {
1014 Phase::Serving | Phase::Playing if !self.paused => ctx.alpha(),
1015 _ => 1.0,
1016 };
1017 let paddle_x = self.paddle_prev_x.lerp(self.paddle_x, alpha);
1018 let ball_pos = self.ball_prev.lerp(self.ball_pos, alpha);
1019
1020 ctx.light(Light::point(ball_pos, BALL_GLOW, BALL_LIGHT_RANGE).shadow());
1021
1022 self.draw_court(ctx);
1023 self.draw_bricks(ctx);
1024 self.draw_sparks(ctx);
1025 self.draw_lives(ctx);
1026
1027 ctx.draw(
1028 Paddle
1029 .at(Transform::from_translation(Vec3::new(
1030 paddle_x,
1031 PADDLE_HALF_HEIGHT,
1032 PADDLE_Z,
1033 )))
1034 .material_of(PaddlePart::Face, self.paddle_face_material()),
1035 );
1036
1037 self.draw_trail(ctx, alpha);
1038 ctx.draw(
1039 Sphere { subdivisions: 2 }
1040 .at(Transform::from_scale_rotation_translation(
1041 Vec3::splat(BALL_RADIUS * 2.0),
1042 Quat::IDENTITY,
1043 ball_pos,
1044 ))
1045 .material(
1046 Material::color(BALL_GLOW)
1047 .emissive(BALL_EMISSIVE)
1048 .additive(),
1049 ),
1050 );
1051
1052 self.overlay(ctx);
1053 }More examples
238 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239 // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240 // is taken first, and `rebind` is applied once the closure returns.
241 let buttons: Vec<_> = ButtonAction::all()
242 .into_iter()
243 .map(|action| {
244 (
245 action,
246 bindings_text(ctx.bindings(action)),
247 ctx.down(action),
248 ctx.pressed(action),
249 ctx.released(action),
250 ctx.clicks(action),
251 )
252 })
253 .collect();
254 let axes: Vec<_> = AxisAction::all()
255 .into_iter()
256 .map(|action| {
257 (
258 action,
259 bindings_text(ctx.bindings(action)),
260 ctx.axis(action),
261 )
262 })
263 .collect();
264 let axes2: Vec<_> = Axis2Action::all()
265 .into_iter()
266 .map(|action| {
267 (
268 action,
269 bindings_text(ctx.bindings(action)),
270 ctx.axis2(action),
271 )
272 })
273 .collect();
274
275 let capturing = !ctx.ui_wants_keyboard();
276 let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277 let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278 let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279 if actuated_button.is_some() {
280 self.last_button = actuated_button;
281 }
282 if actuated_axis.is_some() {
283 self.last_axis = actuated_axis;
284 }
285 if actuated_axis2.is_some() {
286 self.last_axis2 = actuated_axis2;
287 }
288 let pointer = ctx.pointer();
289 let mut edits = RowEdits {
290 listening: self.listening,
291 start_listening: None,
292 cancel: false,
293 reset: None,
294 };
295
296 ctx.ui(|ui| {
297 egui::CentralPanel::default().show(ui, |ui| {
298 ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299 ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300 ui.label("rebinds persist across runs");
301 ui.label(format!(
302 "last captured: button {}, pad axis {}, pad stick {}",
303 text_of(self.last_button),
304 text_of(self.last_axis),
305 text_of(self.last_axis2),
306 ));
307 ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308 ui.separator();
309
310 ui.horizontal(|ui| {
311 ui.vertical(|ui| {
312 ui.heading("buttons");
313 egui::Grid::new("buttons-grid")
314 .num_columns(5)
315 .spacing([6.0, 2.0])
316 .show(ui, |ui| {
317 for (action, bindings, down, pressed, released, clicks) in &buttons
318 {
319 let control = Control::Button(*action);
320 ui.label(action.name());
321 ui.label(bindings);
322 ui.horizontal(|ui| {
323 mark(ui, "down", *down);
324 mark(ui, "pressed", *pressed);
325 mark(ui, "released", *released);
326 ui.label(format!("clicks {clicks}"));
327 });
328 rebind_cell(ui, control, &mut edits);
329 reset_cell(ui, control, &mut edits);
330 ui.end_row();
331 }
332 });
333 });
334
335 ui.separator();
336
337 ui.vertical(|ui| {
338 egui::Grid::new("axes-grid")
339 .num_columns(5)
340 .spacing([6.0, 2.0])
341 .show(ui, |ui| {
342 ui.heading("axes");
343 ui.end_row();
344 for (action, bindings, value) in &axes {
345 let control = Control::Axis(*action);
346 ui.label(action.name());
347 ui.label(bindings);
348 axis_bar(ui, *value);
349 rebind_cell(ui, control, &mut edits);
350 reset_cell(ui, control, &mut edits);
351 ui.end_row();
352 }
353
354 ui.heading("vectors");
355 ui.end_row();
356 for (action, bindings, value) in &axes2 {
357 let control = Control::Axis2(*action);
358 ui.label(action.name());
359 ui.label(bindings);
360 axis2_dot(ui, *value);
361 rebind_cell(ui, control, &mut edits);
362 reset_cell(ui, control, &mut edits);
363 ui.end_row();
364 }
365 });
366 });
367 });
368 });
369 });
370
371 if edits.cancel {
372 self.listening = None;
373 }
374 if let Some(control) = edits.start_listening {
375 self.listening = Some(control);
376 }
377 if let Some(control) = edits.reset {
378 match control {
379 Control::Button(action) => ctx.rebind(action, action.bindings()),
380 Control::Axis(action) => ctx.rebind(action, action.bindings()),
381 Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382 }
383 }
384 match (
385 self.listening,
386 actuated_button,
387 actuated_axis,
388 actuated_axis2,
389 ) {
390 (Some(Control::Button(action)), Some(binding), _, _) => {
391 ctx.rebind(action, vec![binding]);
392 self.listening = None;
393 }
394 (Some(Control::Axis(action)), _, Some(binding), _) => {
395 ctx.rebind(action, vec![binding]);
396 self.listening = None;
397 }
398 (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399 ctx.rebind(action, vec![binding]);
400 self.listening = None;
401 }
402 _ => {}
403 }
404 }Sourcepub fn released<A: InputButtonAction>(&self, action: A) -> bool
pub fn released<A: InputButtonAction>(&self, action: A) -> bool
Whether action came up during this frame.
Examples found in repository?
238 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239 // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240 // is taken first, and `rebind` is applied once the closure returns.
241 let buttons: Vec<_> = ButtonAction::all()
242 .into_iter()
243 .map(|action| {
244 (
245 action,
246 bindings_text(ctx.bindings(action)),
247 ctx.down(action),
248 ctx.pressed(action),
249 ctx.released(action),
250 ctx.clicks(action),
251 )
252 })
253 .collect();
254 let axes: Vec<_> = AxisAction::all()
255 .into_iter()
256 .map(|action| {
257 (
258 action,
259 bindings_text(ctx.bindings(action)),
260 ctx.axis(action),
261 )
262 })
263 .collect();
264 let axes2: Vec<_> = Axis2Action::all()
265 .into_iter()
266 .map(|action| {
267 (
268 action,
269 bindings_text(ctx.bindings(action)),
270 ctx.axis2(action),
271 )
272 })
273 .collect();
274
275 let capturing = !ctx.ui_wants_keyboard();
276 let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277 let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278 let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279 if actuated_button.is_some() {
280 self.last_button = actuated_button;
281 }
282 if actuated_axis.is_some() {
283 self.last_axis = actuated_axis;
284 }
285 if actuated_axis2.is_some() {
286 self.last_axis2 = actuated_axis2;
287 }
288 let pointer = ctx.pointer();
289 let mut edits = RowEdits {
290 listening: self.listening,
291 start_listening: None,
292 cancel: false,
293 reset: None,
294 };
295
296 ctx.ui(|ui| {
297 egui::CentralPanel::default().show(ui, |ui| {
298 ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299 ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300 ui.label("rebinds persist across runs");
301 ui.label(format!(
302 "last captured: button {}, pad axis {}, pad stick {}",
303 text_of(self.last_button),
304 text_of(self.last_axis),
305 text_of(self.last_axis2),
306 ));
307 ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308 ui.separator();
309
310 ui.horizontal(|ui| {
311 ui.vertical(|ui| {
312 ui.heading("buttons");
313 egui::Grid::new("buttons-grid")
314 .num_columns(5)
315 .spacing([6.0, 2.0])
316 .show(ui, |ui| {
317 for (action, bindings, down, pressed, released, clicks) in &buttons
318 {
319 let control = Control::Button(*action);
320 ui.label(action.name());
321 ui.label(bindings);
322 ui.horizontal(|ui| {
323 mark(ui, "down", *down);
324 mark(ui, "pressed", *pressed);
325 mark(ui, "released", *released);
326 ui.label(format!("clicks {clicks}"));
327 });
328 rebind_cell(ui, control, &mut edits);
329 reset_cell(ui, control, &mut edits);
330 ui.end_row();
331 }
332 });
333 });
334
335 ui.separator();
336
337 ui.vertical(|ui| {
338 egui::Grid::new("axes-grid")
339 .num_columns(5)
340 .spacing([6.0, 2.0])
341 .show(ui, |ui| {
342 ui.heading("axes");
343 ui.end_row();
344 for (action, bindings, value) in &axes {
345 let control = Control::Axis(*action);
346 ui.label(action.name());
347 ui.label(bindings);
348 axis_bar(ui, *value);
349 rebind_cell(ui, control, &mut edits);
350 reset_cell(ui, control, &mut edits);
351 ui.end_row();
352 }
353
354 ui.heading("vectors");
355 ui.end_row();
356 for (action, bindings, value) in &axes2 {
357 let control = Control::Axis2(*action);
358 ui.label(action.name());
359 ui.label(bindings);
360 axis2_dot(ui, *value);
361 rebind_cell(ui, control, &mut edits);
362 reset_cell(ui, control, &mut edits);
363 ui.end_row();
364 }
365 });
366 });
367 });
368 });
369 });
370
371 if edits.cancel {
372 self.listening = None;
373 }
374 if let Some(control) = edits.start_listening {
375 self.listening = Some(control);
376 }
377 if let Some(control) = edits.reset {
378 match control {
379 Control::Button(action) => ctx.rebind(action, action.bindings()),
380 Control::Axis(action) => ctx.rebind(action, action.bindings()),
381 Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382 }
383 }
384 match (
385 self.listening,
386 actuated_button,
387 actuated_axis,
388 actuated_axis2,
389 ) {
390 (Some(Control::Button(action)), Some(binding), _, _) => {
391 ctx.rebind(action, vec![binding]);
392 self.listening = None;
393 }
394 (Some(Control::Axis(action)), _, Some(binding), _) => {
395 ctx.rebind(action, vec![binding]);
396 self.listening = None;
397 }
398 (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399 ctx.rebind(action, vec![binding]);
400 self.listening = None;
401 }
402 _ => {}
403 }
404 }Sourcepub fn clicks<A: InputButtonAction>(&self, action: A) -> u32
pub fn clicks<A: InputButtonAction>(&self, action: A) -> u32
Presses of action in a row, counting this frame’s: 1 for a
single click, 2 for a double, and 0 on a frame where action
was not pressed.
A press counts with the one before it where the same control took
both, no later than the double click interval after it — the
platform’s own until
Config::with_double_click_interval
sets one. The engine counts one control at a time, so an action
whose press lands in the same frame as another control’s reads 0
where the count is on that other one. A headless session counts
against the clock its caller drives, so presses at one instant of it
count together.
Examples found in repository?
238 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239 // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240 // is taken first, and `rebind` is applied once the closure returns.
241 let buttons: Vec<_> = ButtonAction::all()
242 .into_iter()
243 .map(|action| {
244 (
245 action,
246 bindings_text(ctx.bindings(action)),
247 ctx.down(action),
248 ctx.pressed(action),
249 ctx.released(action),
250 ctx.clicks(action),
251 )
252 })
253 .collect();
254 let axes: Vec<_> = AxisAction::all()
255 .into_iter()
256 .map(|action| {
257 (
258 action,
259 bindings_text(ctx.bindings(action)),
260 ctx.axis(action),
261 )
262 })
263 .collect();
264 let axes2: Vec<_> = Axis2Action::all()
265 .into_iter()
266 .map(|action| {
267 (
268 action,
269 bindings_text(ctx.bindings(action)),
270 ctx.axis2(action),
271 )
272 })
273 .collect();
274
275 let capturing = !ctx.ui_wants_keyboard();
276 let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277 let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278 let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279 if actuated_button.is_some() {
280 self.last_button = actuated_button;
281 }
282 if actuated_axis.is_some() {
283 self.last_axis = actuated_axis;
284 }
285 if actuated_axis2.is_some() {
286 self.last_axis2 = actuated_axis2;
287 }
288 let pointer = ctx.pointer();
289 let mut edits = RowEdits {
290 listening: self.listening,
291 start_listening: None,
292 cancel: false,
293 reset: None,
294 };
295
296 ctx.ui(|ui| {
297 egui::CentralPanel::default().show(ui, |ui| {
298 ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299 ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300 ui.label("rebinds persist across runs");
301 ui.label(format!(
302 "last captured: button {}, pad axis {}, pad stick {}",
303 text_of(self.last_button),
304 text_of(self.last_axis),
305 text_of(self.last_axis2),
306 ));
307 ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308 ui.separator();
309
310 ui.horizontal(|ui| {
311 ui.vertical(|ui| {
312 ui.heading("buttons");
313 egui::Grid::new("buttons-grid")
314 .num_columns(5)
315 .spacing([6.0, 2.0])
316 .show(ui, |ui| {
317 for (action, bindings, down, pressed, released, clicks) in &buttons
318 {
319 let control = Control::Button(*action);
320 ui.label(action.name());
321 ui.label(bindings);
322 ui.horizontal(|ui| {
323 mark(ui, "down", *down);
324 mark(ui, "pressed", *pressed);
325 mark(ui, "released", *released);
326 ui.label(format!("clicks {clicks}"));
327 });
328 rebind_cell(ui, control, &mut edits);
329 reset_cell(ui, control, &mut edits);
330 ui.end_row();
331 }
332 });
333 });
334
335 ui.separator();
336
337 ui.vertical(|ui| {
338 egui::Grid::new("axes-grid")
339 .num_columns(5)
340 .spacing([6.0, 2.0])
341 .show(ui, |ui| {
342 ui.heading("axes");
343 ui.end_row();
344 for (action, bindings, value) in &axes {
345 let control = Control::Axis(*action);
346 ui.label(action.name());
347 ui.label(bindings);
348 axis_bar(ui, *value);
349 rebind_cell(ui, control, &mut edits);
350 reset_cell(ui, control, &mut edits);
351 ui.end_row();
352 }
353
354 ui.heading("vectors");
355 ui.end_row();
356 for (action, bindings, value) in &axes2 {
357 let control = Control::Axis2(*action);
358 ui.label(action.name());
359 ui.label(bindings);
360 axis2_dot(ui, *value);
361 rebind_cell(ui, control, &mut edits);
362 reset_cell(ui, control, &mut edits);
363 ui.end_row();
364 }
365 });
366 });
367 });
368 });
369 });
370
371 if edits.cancel {
372 self.listening = None;
373 }
374 if let Some(control) = edits.start_listening {
375 self.listening = Some(control);
376 }
377 if let Some(control) = edits.reset {
378 match control {
379 Control::Button(action) => ctx.rebind(action, action.bindings()),
380 Control::Axis(action) => ctx.rebind(action, action.bindings()),
381 Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382 }
383 }
384 match (
385 self.listening,
386 actuated_button,
387 actuated_axis,
388 actuated_axis2,
389 ) {
390 (Some(Control::Button(action)), Some(binding), _, _) => {
391 ctx.rebind(action, vec![binding]);
392 self.listening = None;
393 }
394 (Some(Control::Axis(action)), _, Some(binding), _) => {
395 ctx.rebind(action, vec![binding]);
396 self.listening = None;
397 }
398 (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399 ctx.rebind(action, vec![binding]);
400 self.listening = None;
401 }
402 _ => {}
403 }
404 }Sourcepub fn axis<A: InputAxisAction>(&self, action: A) -> f32
pub fn axis<A: InputAxisAction>(&self, action: A) -> f32
Analog reading of action: a fraction in -1..=1 from a pad axis,
a joystick axis or a button composite, of which a trigger reads
0..=1, and the scaled distance, which nothing clamps, from a
PointerDelta or
WheelDelta lane.
Examples found in repository?
More examples
359 fn handle_camera(&mut self, ctx: &mut FrameContext<'_, Self>, elapsed: f32) {
360 if ctx.ui_wants_pointer() || ctx.ui_wants_keyboard() {
361 return;
362 }
363 let pan = ctx.axis2(Motion::Pan);
364 let wheel = ctx.axis(Height::Wheel);
365 let look = if ctx.down(Drag::Turn) {
366 ctx.axis2(Motion::Look)
367 } else {
368 Vec2::ZERO
369 };
370 if pan == Vec2::ZERO && wheel == 0.0 && look == Vec2::ZERO {
371 return;
372 }
373
374 let player = self.player.get_or_insert_with(|| {
375 let eye = Self::orbit_eye(elapsed);
376 let forward = (Vec3::ZERO - eye).normalize();
377 Player {
378 eye,
379 yaw: (-forward.x).atan2(-forward.z),
380 pitch: forward.y.asin(),
381 }
382 });
383
384 player.yaw -= look.x;
385 player.pitch = (player.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
386
387 let forward = Vec3::new(-player.yaw.sin(), 0.0, -player.yaw.cos());
388 let right = Vec3::new(player.yaw.cos(), 0.0, -player.yaw.sin());
389 player.eye += (forward * pan.y + right * pan.x) * PAN_SPEED * ctx.dt().as_secs_f32();
390 player.eye.y =
391 (player.eye.y + wheel * WHEEL_STEP).clamp(MIN_CAMERA_HEIGHT, MAX_CAMERA_HEIGHT);
392 }802 fn fly_camera(&mut self, ctx: &mut FrameContext<'_, Self>) {
803 if !ctx.ui_wants_pointer() && ctx.down(Move::Look) {
804 let look = ctx.axis2(Turn::Look);
805 self.yaw -= look.x;
806 self.pitch = (self.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
807 }
808
809 let wheel = ctx.axis(Speed::Wheel);
810 if !ctx.ui_wants_pointer() && wheel != 0.0 {
811 self.speed_scale =
812 (self.speed_scale * SPEED_STEP.powf(wheel)).clamp(MIN_SPEED_SCALE, MAX_SPEED_SCALE);
813 }
814
815 let forward = self.forward();
816 let right = Vec3::new(self.yaw.cos(), 0.0, -self.yaw.sin());
817 let mut move_by = Vec3::ZERO;
818 if ctx.down(Move::Forward) {
819 move_by += forward;
820 }
821 if ctx.down(Move::Back) {
822 move_by -= forward;
823 }
824 if ctx.down(Move::Right) {
825 move_by += right;
826 }
827 if ctx.down(Move::Left) {
828 move_by -= right;
829 }
830 if ctx.down(Move::Up) {
831 move_by += Vec3::Y;
832 }
833 if ctx.down(Move::Down) {
834 move_by -= Vec3::Y;
835 }
836 if move_by.length_squared() > 1.0 {
837 move_by = move_by.normalize();
838 }
839
840 self.eye += move_by * MOVE_SPEED * self.speed_scale * ctx.dt().as_secs_f32();
841 self.eye.y = self.eye.y.max(MIN_EYE_HEIGHT);
842 }238 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239 // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240 // is taken first, and `rebind` is applied once the closure returns.
241 let buttons: Vec<_> = ButtonAction::all()
242 .into_iter()
243 .map(|action| {
244 (
245 action,
246 bindings_text(ctx.bindings(action)),
247 ctx.down(action),
248 ctx.pressed(action),
249 ctx.released(action),
250 ctx.clicks(action),
251 )
252 })
253 .collect();
254 let axes: Vec<_> = AxisAction::all()
255 .into_iter()
256 .map(|action| {
257 (
258 action,
259 bindings_text(ctx.bindings(action)),
260 ctx.axis(action),
261 )
262 })
263 .collect();
264 let axes2: Vec<_> = Axis2Action::all()
265 .into_iter()
266 .map(|action| {
267 (
268 action,
269 bindings_text(ctx.bindings(action)),
270 ctx.axis2(action),
271 )
272 })
273 .collect();
274
275 let capturing = !ctx.ui_wants_keyboard();
276 let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277 let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278 let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279 if actuated_button.is_some() {
280 self.last_button = actuated_button;
281 }
282 if actuated_axis.is_some() {
283 self.last_axis = actuated_axis;
284 }
285 if actuated_axis2.is_some() {
286 self.last_axis2 = actuated_axis2;
287 }
288 let pointer = ctx.pointer();
289 let mut edits = RowEdits {
290 listening: self.listening,
291 start_listening: None,
292 cancel: false,
293 reset: None,
294 };
295
296 ctx.ui(|ui| {
297 egui::CentralPanel::default().show(ui, |ui| {
298 ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299 ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300 ui.label("rebinds persist across runs");
301 ui.label(format!(
302 "last captured: button {}, pad axis {}, pad stick {}",
303 text_of(self.last_button),
304 text_of(self.last_axis),
305 text_of(self.last_axis2),
306 ));
307 ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308 ui.separator();
309
310 ui.horizontal(|ui| {
311 ui.vertical(|ui| {
312 ui.heading("buttons");
313 egui::Grid::new("buttons-grid")
314 .num_columns(5)
315 .spacing([6.0, 2.0])
316 .show(ui, |ui| {
317 for (action, bindings, down, pressed, released, clicks) in &buttons
318 {
319 let control = Control::Button(*action);
320 ui.label(action.name());
321 ui.label(bindings);
322 ui.horizontal(|ui| {
323 mark(ui, "down", *down);
324 mark(ui, "pressed", *pressed);
325 mark(ui, "released", *released);
326 ui.label(format!("clicks {clicks}"));
327 });
328 rebind_cell(ui, control, &mut edits);
329 reset_cell(ui, control, &mut edits);
330 ui.end_row();
331 }
332 });
333 });
334
335 ui.separator();
336
337 ui.vertical(|ui| {
338 egui::Grid::new("axes-grid")
339 .num_columns(5)
340 .spacing([6.0, 2.0])
341 .show(ui, |ui| {
342 ui.heading("axes");
343 ui.end_row();
344 for (action, bindings, value) in &axes {
345 let control = Control::Axis(*action);
346 ui.label(action.name());
347 ui.label(bindings);
348 axis_bar(ui, *value);
349 rebind_cell(ui, control, &mut edits);
350 reset_cell(ui, control, &mut edits);
351 ui.end_row();
352 }
353
354 ui.heading("vectors");
355 ui.end_row();
356 for (action, bindings, value) in &axes2 {
357 let control = Control::Axis2(*action);
358 ui.label(action.name());
359 ui.label(bindings);
360 axis2_dot(ui, *value);
361 rebind_cell(ui, control, &mut edits);
362 reset_cell(ui, control, &mut edits);
363 ui.end_row();
364 }
365 });
366 });
367 });
368 });
369 });
370
371 if edits.cancel {
372 self.listening = None;
373 }
374 if let Some(control) = edits.start_listening {
375 self.listening = Some(control);
376 }
377 if let Some(control) = edits.reset {
378 match control {
379 Control::Button(action) => ctx.rebind(action, action.bindings()),
380 Control::Axis(action) => ctx.rebind(action, action.bindings()),
381 Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382 }
383 }
384 match (
385 self.listening,
386 actuated_button,
387 actuated_axis,
388 actuated_axis2,
389 ) {
390 (Some(Control::Button(action)), Some(binding), _, _) => {
391 ctx.rebind(action, vec![binding]);
392 self.listening = None;
393 }
394 (Some(Control::Axis(action)), _, Some(binding), _) => {
395 ctx.rebind(action, vec![binding]);
396 self.listening = None;
397 }
398 (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399 ctx.rebind(action, vec![binding]);
400 self.listening = None;
401 }
402 _ => {}
403 }
404 }Sourcepub fn axis2<A: InputAxis2Action>(&self, action: A) -> Vec2
pub fn axis2<A: InputAxis2Action>(&self, action: A) -> Vec2
action’s reading: a vector no longer than 1 from a stick or a
button composite, and the scaled distance, which nothing clamps,
from Axis2Binding::pointer.
Examples found in repository?
More examples
359 fn handle_camera(&mut self, ctx: &mut FrameContext<'_, Self>, elapsed: f32) {
360 if ctx.ui_wants_pointer() || ctx.ui_wants_keyboard() {
361 return;
362 }
363 let pan = ctx.axis2(Motion::Pan);
364 let wheel = ctx.axis(Height::Wheel);
365 let look = if ctx.down(Drag::Turn) {
366 ctx.axis2(Motion::Look)
367 } else {
368 Vec2::ZERO
369 };
370 if pan == Vec2::ZERO && wheel == 0.0 && look == Vec2::ZERO {
371 return;
372 }
373
374 let player = self.player.get_or_insert_with(|| {
375 let eye = Self::orbit_eye(elapsed);
376 let forward = (Vec3::ZERO - eye).normalize();
377 Player {
378 eye,
379 yaw: (-forward.x).atan2(-forward.z),
380 pitch: forward.y.asin(),
381 }
382 });
383
384 player.yaw -= look.x;
385 player.pitch = (player.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
386
387 let forward = Vec3::new(-player.yaw.sin(), 0.0, -player.yaw.cos());
388 let right = Vec3::new(player.yaw.cos(), 0.0, -player.yaw.sin());
389 player.eye += (forward * pan.y + right * pan.x) * PAN_SPEED * ctx.dt().as_secs_f32();
390 player.eye.y =
391 (player.eye.y + wheel * WHEEL_STEP).clamp(MIN_CAMERA_HEIGHT, MAX_CAMERA_HEIGHT);
392 }802 fn fly_camera(&mut self, ctx: &mut FrameContext<'_, Self>) {
803 if !ctx.ui_wants_pointer() && ctx.down(Move::Look) {
804 let look = ctx.axis2(Turn::Look);
805 self.yaw -= look.x;
806 self.pitch = (self.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
807 }
808
809 let wheel = ctx.axis(Speed::Wheel);
810 if !ctx.ui_wants_pointer() && wheel != 0.0 {
811 self.speed_scale =
812 (self.speed_scale * SPEED_STEP.powf(wheel)).clamp(MIN_SPEED_SCALE, MAX_SPEED_SCALE);
813 }
814
815 let forward = self.forward();
816 let right = Vec3::new(self.yaw.cos(), 0.0, -self.yaw.sin());
817 let mut move_by = Vec3::ZERO;
818 if ctx.down(Move::Forward) {
819 move_by += forward;
820 }
821 if ctx.down(Move::Back) {
822 move_by -= forward;
823 }
824 if ctx.down(Move::Right) {
825 move_by += right;
826 }
827 if ctx.down(Move::Left) {
828 move_by -= right;
829 }
830 if ctx.down(Move::Up) {
831 move_by += Vec3::Y;
832 }
833 if ctx.down(Move::Down) {
834 move_by -= Vec3::Y;
835 }
836 if move_by.length_squared() > 1.0 {
837 move_by = move_by.normalize();
838 }
839
840 self.eye += move_by * MOVE_SPEED * self.speed_scale * ctx.dt().as_secs_f32();
841 self.eye.y = self.eye.y.max(MIN_EYE_HEIGHT);
842 }238 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239 // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240 // is taken first, and `rebind` is applied once the closure returns.
241 let buttons: Vec<_> = ButtonAction::all()
242 .into_iter()
243 .map(|action| {
244 (
245 action,
246 bindings_text(ctx.bindings(action)),
247 ctx.down(action),
248 ctx.pressed(action),
249 ctx.released(action),
250 ctx.clicks(action),
251 )
252 })
253 .collect();
254 let axes: Vec<_> = AxisAction::all()
255 .into_iter()
256 .map(|action| {
257 (
258 action,
259 bindings_text(ctx.bindings(action)),
260 ctx.axis(action),
261 )
262 })
263 .collect();
264 let axes2: Vec<_> = Axis2Action::all()
265 .into_iter()
266 .map(|action| {
267 (
268 action,
269 bindings_text(ctx.bindings(action)),
270 ctx.axis2(action),
271 )
272 })
273 .collect();
274
275 let capturing = !ctx.ui_wants_keyboard();
276 let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277 let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278 let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279 if actuated_button.is_some() {
280 self.last_button = actuated_button;
281 }
282 if actuated_axis.is_some() {
283 self.last_axis = actuated_axis;
284 }
285 if actuated_axis2.is_some() {
286 self.last_axis2 = actuated_axis2;
287 }
288 let pointer = ctx.pointer();
289 let mut edits = RowEdits {
290 listening: self.listening,
291 start_listening: None,
292 cancel: false,
293 reset: None,
294 };
295
296 ctx.ui(|ui| {
297 egui::CentralPanel::default().show(ui, |ui| {
298 ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299 ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300 ui.label("rebinds persist across runs");
301 ui.label(format!(
302 "last captured: button {}, pad axis {}, pad stick {}",
303 text_of(self.last_button),
304 text_of(self.last_axis),
305 text_of(self.last_axis2),
306 ));
307 ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308 ui.separator();
309
310 ui.horizontal(|ui| {
311 ui.vertical(|ui| {
312 ui.heading("buttons");
313 egui::Grid::new("buttons-grid")
314 .num_columns(5)
315 .spacing([6.0, 2.0])
316 .show(ui, |ui| {
317 for (action, bindings, down, pressed, released, clicks) in &buttons
318 {
319 let control = Control::Button(*action);
320 ui.label(action.name());
321 ui.label(bindings);
322 ui.horizontal(|ui| {
323 mark(ui, "down", *down);
324 mark(ui, "pressed", *pressed);
325 mark(ui, "released", *released);
326 ui.label(format!("clicks {clicks}"));
327 });
328 rebind_cell(ui, control, &mut edits);
329 reset_cell(ui, control, &mut edits);
330 ui.end_row();
331 }
332 });
333 });
334
335 ui.separator();
336
337 ui.vertical(|ui| {
338 egui::Grid::new("axes-grid")
339 .num_columns(5)
340 .spacing([6.0, 2.0])
341 .show(ui, |ui| {
342 ui.heading("axes");
343 ui.end_row();
344 for (action, bindings, value) in &axes {
345 let control = Control::Axis(*action);
346 ui.label(action.name());
347 ui.label(bindings);
348 axis_bar(ui, *value);
349 rebind_cell(ui, control, &mut edits);
350 reset_cell(ui, control, &mut edits);
351 ui.end_row();
352 }
353
354 ui.heading("vectors");
355 ui.end_row();
356 for (action, bindings, value) in &axes2 {
357 let control = Control::Axis2(*action);
358 ui.label(action.name());
359 ui.label(bindings);
360 axis2_dot(ui, *value);
361 rebind_cell(ui, control, &mut edits);
362 reset_cell(ui, control, &mut edits);
363 ui.end_row();
364 }
365 });
366 });
367 });
368 });
369 });
370
371 if edits.cancel {
372 self.listening = None;
373 }
374 if let Some(control) = edits.start_listening {
375 self.listening = Some(control);
376 }
377 if let Some(control) = edits.reset {
378 match control {
379 Control::Button(action) => ctx.rebind(action, action.bindings()),
380 Control::Axis(action) => ctx.rebind(action, action.bindings()),
381 Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382 }
383 }
384 match (
385 self.listening,
386 actuated_button,
387 actuated_axis,
388 actuated_axis2,
389 ) {
390 (Some(Control::Button(action)), Some(binding), _, _) => {
391 ctx.rebind(action, vec![binding]);
392 self.listening = None;
393 }
394 (Some(Control::Axis(action)), _, Some(binding), _) => {
395 ctx.rebind(action, vec![binding]);
396 self.listening = None;
397 }
398 (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399 ctx.rebind(action, vec![binding]);
400 self.listening = None;
401 }
402 _ => {}
403 }
404 }Sourcepub fn pointer(&self) -> Vec2
pub fn pointer(&self) -> Vec2
Pointer position, in physical pixels from the drawing area’s top left; the origin until it is first seen.
The mouse and the first touch share it, and
window_size is in the same pixels, so
Camera::ray_through takes it as it is.
Examples found in repository?
More examples
459 fn hovered(&self, ctx: &FrameContext<'_, Board>) -> Hover {
460 if ctx.ui_wants_pointer() {
461 return Hover::None;
462 }
463 let ray = ctx
464 .last_camera()
465 .ray_through(ctx.pointer(), ctx.window_size());
466
467 if self.current().target.is_none() {
468 let (_, half) = unit_geometry(self.turn);
469 let center = self.current().position;
470 if ray.hit_aabb(center - half, center + half).is_some() {
471 return Hover::CurrentUnit;
472 }
473 }
474 let Some(distance) = ray.hit_plane(ray::Plane {
475 point: Vec3::ZERO,
476 normal: Vec3::Y,
477 }) else {
478 return Hover::None;
479 };
480 match tile_at(ray.at(distance)) {
481 Some(tile) => Hover::Tile(tile),
482 None => Hover::None,
483 }
484 }238 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239 // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240 // is taken first, and `rebind` is applied once the closure returns.
241 let buttons: Vec<_> = ButtonAction::all()
242 .into_iter()
243 .map(|action| {
244 (
245 action,
246 bindings_text(ctx.bindings(action)),
247 ctx.down(action),
248 ctx.pressed(action),
249 ctx.released(action),
250 ctx.clicks(action),
251 )
252 })
253 .collect();
254 let axes: Vec<_> = AxisAction::all()
255 .into_iter()
256 .map(|action| {
257 (
258 action,
259 bindings_text(ctx.bindings(action)),
260 ctx.axis(action),
261 )
262 })
263 .collect();
264 let axes2: Vec<_> = Axis2Action::all()
265 .into_iter()
266 .map(|action| {
267 (
268 action,
269 bindings_text(ctx.bindings(action)),
270 ctx.axis2(action),
271 )
272 })
273 .collect();
274
275 let capturing = !ctx.ui_wants_keyboard();
276 let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277 let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278 let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279 if actuated_button.is_some() {
280 self.last_button = actuated_button;
281 }
282 if actuated_axis.is_some() {
283 self.last_axis = actuated_axis;
284 }
285 if actuated_axis2.is_some() {
286 self.last_axis2 = actuated_axis2;
287 }
288 let pointer = ctx.pointer();
289 let mut edits = RowEdits {
290 listening: self.listening,
291 start_listening: None,
292 cancel: false,
293 reset: None,
294 };
295
296 ctx.ui(|ui| {
297 egui::CentralPanel::default().show(ui, |ui| {
298 ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299 ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300 ui.label("rebinds persist across runs");
301 ui.label(format!(
302 "last captured: button {}, pad axis {}, pad stick {}",
303 text_of(self.last_button),
304 text_of(self.last_axis),
305 text_of(self.last_axis2),
306 ));
307 ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308 ui.separator();
309
310 ui.horizontal(|ui| {
311 ui.vertical(|ui| {
312 ui.heading("buttons");
313 egui::Grid::new("buttons-grid")
314 .num_columns(5)
315 .spacing([6.0, 2.0])
316 .show(ui, |ui| {
317 for (action, bindings, down, pressed, released, clicks) in &buttons
318 {
319 let control = Control::Button(*action);
320 ui.label(action.name());
321 ui.label(bindings);
322 ui.horizontal(|ui| {
323 mark(ui, "down", *down);
324 mark(ui, "pressed", *pressed);
325 mark(ui, "released", *released);
326 ui.label(format!("clicks {clicks}"));
327 });
328 rebind_cell(ui, control, &mut edits);
329 reset_cell(ui, control, &mut edits);
330 ui.end_row();
331 }
332 });
333 });
334
335 ui.separator();
336
337 ui.vertical(|ui| {
338 egui::Grid::new("axes-grid")
339 .num_columns(5)
340 .spacing([6.0, 2.0])
341 .show(ui, |ui| {
342 ui.heading("axes");
343 ui.end_row();
344 for (action, bindings, value) in &axes {
345 let control = Control::Axis(*action);
346 ui.label(action.name());
347 ui.label(bindings);
348 axis_bar(ui, *value);
349 rebind_cell(ui, control, &mut edits);
350 reset_cell(ui, control, &mut edits);
351 ui.end_row();
352 }
353
354 ui.heading("vectors");
355 ui.end_row();
356 for (action, bindings, value) in &axes2 {
357 let control = Control::Axis2(*action);
358 ui.label(action.name());
359 ui.label(bindings);
360 axis2_dot(ui, *value);
361 rebind_cell(ui, control, &mut edits);
362 reset_cell(ui, control, &mut edits);
363 ui.end_row();
364 }
365 });
366 });
367 });
368 });
369 });
370
371 if edits.cancel {
372 self.listening = None;
373 }
374 if let Some(control) = edits.start_listening {
375 self.listening = Some(control);
376 }
377 if let Some(control) = edits.reset {
378 match control {
379 Control::Button(action) => ctx.rebind(action, action.bindings()),
380 Control::Axis(action) => ctx.rebind(action, action.bindings()),
381 Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382 }
383 }
384 match (
385 self.listening,
386 actuated_button,
387 actuated_axis,
388 actuated_axis2,
389 ) {
390 (Some(Control::Button(action)), Some(binding), _, _) => {
391 ctx.rebind(action, vec![binding]);
392 self.listening = None;
393 }
394 (Some(Control::Axis(action)), _, Some(binding), _) => {
395 ctx.rebind(action, vec![binding]);
396 self.listening = None;
397 }
398 (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399 ctx.rebind(action, vec![binding]);
400 self.listening = None;
401 }
402 _ => {}
403 }
404 }Sourcepub fn set_cursor(&mut self, cursor: Cursor)
pub fn set_cursor(&mut self, cursor: Cursor)
Draws the pointer as cursor this frame; the last call in a frame
is the one it draws.
A frame that never calls this draws it as Cursor::Arrow. Where
the UI sets a cursor of its own, the UI’s is drawn instead.
Cursor::Held holds the pointer in place, and the UI’s own
cursor does not take Held over: pointer reads
the place it was held at, a PointerDelta
binding keeps reading how far it moves, and the first frame to set
another cursor releases it. A browser takes the pointer lock only from
inside a gesture of the player’s, so there the hold is taken on the
player’s next press or touch. A window loses the hold as it loses
focus, and takes it again on the first frame to set Held after the
focus returns.
Examples found in repository?
816 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
817 self.steer(ctx);
818
819 let camera = self.orbit.camera();
820 ctx.set_camera(camera);
821 ctx.set_skybox(Sky::Dusk);
822 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
823
824 ctx.draw(
825 Plane
826 .at(Transform::from_scale(Vec3::new(
827 PLATFORM_SIZE,
828 1.0,
829 PLATFORM_SIZE,
830 )))
831 .material(Material::lit(PLATFORM_COLOR)),
832 );
833 for station in StationKind::ALL {
834 self.draw_station(ctx, station);
835 }
836
837 let hovered = Self::hovered(ctx);
838 if !self.sheet_open {
839 if let Some(station) = hovered {
840 ctx.set_cursor(Cursor::Pointer);
841 self.draw_bracket(ctx, camera, station);
842 }
843 self.draw_prompts(ctx, camera, hovered);
844 }
845 if self.dialogue.is_some() {
846 self.draw_dialogue(ctx);
847 }
848 if self.sheet_open {
849 ctx.ui(sheet);
850 }
851 self.panel(ctx);
852 }More examples
920 fn frame(&mut self, ctx: &mut FrameContext<'_, Scene>) {
921 self.steer_camera(ctx);
922
923 let alpha = ctx.alpha();
924 let elf_pos = self.elf_prev.lerp(self.elf_pos, alpha);
925 let elf_height = self.elf_height_prev + (self.elf_height - self.elf_height_prev) * alpha;
926 let (butterfly_pos, butterfly_yaw) = butterfly_pose(ctx.elapsed().as_secs_f32());
927
928 let camera = orbit_camera(elf_pos, self.camera_yaw, self.camera_pitch);
929 ctx.set_camera(camera);
930 ctx.set_cursor(if self.holding {
931 Cursor::Held
932 } else {
933 Cursor::Arrow
934 });
935 ctx.set_skybox(Sky::Day);
936 ctx.set_exposure(3.0);
937 ctx.set_bloom(0.2);
938 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
939 ctx.light(
940 Light::point(
941 LAMP_POST_POSITION + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP * 0.5),
942 LAMP_LIGHT_COLOR,
943 LAMP_LIGHT_RANGE,
944 )
945 .shadow(),
946 );
947 ctx.light(
948 Light::spot(Spot {
949 position: SPOT_POSITION,
950 direction: SPOT_DIRECTION,
951 color: SPOT_COLOR,
952 range: SPOT_RANGE,
953 angle: SPOT_ANGLE,
954 })
955 .shadow(),
956 );
957 ctx.light(
958 Light::point(butterfly_pos, BUTTERFLY_LIGHT_COLOR, BUTTERFLY_LIGHT_RANGE).shadow(),
959 );
960
961 ctx.draw(
962 Plane
963 .at(Transform::from_scale(Vec3::new(
964 GROUND_SIZE,
965 1.0,
966 GROUND_SIZE,
967 )))
968 .material(Material::lit(GROUND_COLOR)),
969 );
970 for patch in HURT_PATCHES {
971 ctx.draw(
972 Plane
973 .at(Transform::from_scale_rotation_translation(
974 Vec3::splat(HURT_RADIUS * 2.0),
975 Quat::IDENTITY,
976 patch,
977 ))
978 .material(Material::lit(HURT_COLOR)),
979 );
980 }
981 ctx.draw(
982 Cube.at(Transform::from_scale_rotation_translation(
983 Vec3::new(SEAT_FOOTPRINT, SEAT_HEIGHT, SEAT_FOOTPRINT),
984 Quat::IDENTITY,
985 SEAT_POSITION + Vec3::Y * SEAT_HEIGHT * 0.5,
986 ))
987 .material(Material::lit(SEAT_COLOR)),
988 );
989 ctx.draw(
990 Cube.at(Transform::from_scale_rotation_translation(
991 Vec3::new(LAMP_POST_THICKNESS, LAMP_POST_HEIGHT, LAMP_POST_THICKNESS),
992 Quat::IDENTITY,
993 LAMP_POST_POSITION + Vec3::Y * LAMP_POST_HEIGHT * 0.5,
994 ))
995 .material(Material::lit(LAMP_POST_COLOR)),
996 );
997 ctx.draw(
998 Cube.at(Transform::from_scale_rotation_translation(
999 Vec3::splat(LAMP_HEAD_SIZE),
1000 Quat::IDENTITY,
1001 LAMP_POST_POSITION
1002 + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP + LAMP_HEAD_SIZE * 0.5),
1003 ))
1004 .material(Material::color(Color::BLACK).emissive(LAMP_LIGHT_COLOR)),
1005 );
1006 ctx.draw(
1007 Cube.at(Transform::from_scale_rotation_translation(
1008 Vec3::splat(SPOT_FIXTURE_SIZE),
1009 Quat::IDENTITY,
1010 SPOT_POSITION + Vec3::Y * SPOT_FIXTURE_SIZE * 0.5,
1011 ))
1012 .material(Material::lit(SPOT_FIXTURE_COLOR)),
1013 );
1014
1015 ctx.draw(
1016 Elf.at(Transform::from_rotation_translation(
1017 Quat::from_rotation_y(self.elf_yaw),
1018 elf_pos + Vec3::Y * elf_height,
1019 ))
1020 .posed(&self.elf_animator),
1021 );
1022 ctx.draw(
1023 Elf.at(Transform::from_rotation_translation(
1024 Quat::from_rotation_y(core::f32::consts::PI),
1025 SCRUBBED_ELF_POSITION,
1026 ))
1027 .posed(&self.scrubbed_animator),
1028 );
1029 ctx.draw(
1030 Butterfly
1031 .at(Transform::from_rotation_translation(
1032 Quat::from_rotation_y(butterfly_yaw),
1033 butterfly_pos,
1034 ))
1035 .posed(&self.butterfly_animator)
1036 .material(Material::lit(Color::WHITE).emissive(BUTTERFLY_EMISSIVE)),
1037 );
1038
1039 self.draw_prompts(ctx, camera);
1040 self.panel(ctx);
1041 }Sourcepub fn rebind<A: InputAction>(&mut self, action: A, bindings: Vec<A::Binding>)
pub fn rebind<A: InputAction>(&mut self, action: A, bindings: Vec<A::Binding>)
Binds action to bindings for the rest of the run, and keeps it
for the runs after that — written when the frame ends, and only
when these bindings are not already what action is bound to.
The kind of binding follows the action, so a stick cannot be bound to a button.
Examples found in repository?
765 fn menu(&mut self, ctx: &mut FrameContext<'_, Breakout>, title: &str, over: bool) {
766 let mut clicked = false;
767 let mut quit = false;
768
769 // `ctx.ui` cannot borrow `ctx`, so anything the controls list needs is
770 // read first and applied after.
771 let buttons: Vec<(Button, String)> = Button::all()
772 .into_iter()
773 .map(|action| (action, bindings_text(ctx.bindings(action))))
774 .collect();
775 let axes: Vec<(Move, String)> = Move::all()
776 .into_iter()
777 .map(|action| (action, bindings_text(ctx.bindings(action))))
778 .collect();
779 let listening = self.listening;
780 let actuated_button = (!ctx.ui_wants_keyboard())
781 .then(|| ctx.actuated_button())
782 .flatten();
783 let actuated_axis = (!ctx.ui_wants_keyboard())
784 .then(|| ctx.actuated_axis())
785 .flatten();
786 let mut reset = None;
787
788 ctx.ui(|ui| {
789 egui::Window::new(title)
790 .collapsible(false)
791 .resizable(false)
792 .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
793 .show(ui.ctx(), |ui| {
794 if over {
795 ui.label(format!("score {}", self.score));
796 }
797 if !over {
798 ui.add(
799 egui::Slider::new(&mut self.master_volume, 0.0..=1.0).text("volume"),
800 );
801 if ui.button("resume").clicked() {
802 self.paused = false;
803 clicked = true;
804 }
805 ui.separator();
806 ui.heading("controls");
807 for (action, text) in &buttons {
808 controls_row(
809 ui,
810 action.name(),
811 text,
812 listening == Some(Listening::Button(*action)),
813 &mut self.listening,
814 Listening::Button(*action),
815 &mut reset,
816 );
817 }
818 for (action, text) in &axes {
819 controls_row(
820 ui,
821 action.name(),
822 text,
823 listening == Some(Listening::Move(*action)),
824 &mut self.listening,
825 Listening::Move(*action),
826 &mut reset,
827 );
828 }
829 }
830 if ui.button("restart").clicked() {
831 self.restart();
832 clicked = true;
833 }
834 if ui.button("quit").clicked() {
835 quit = true;
836 }
837 });
838 });
839
840 match (self.listening, actuated_button, actuated_axis) {
841 (Some(Listening::Button(action)), Some(binding), _) => {
842 ctx.rebind(action, vec![binding]);
843 self.listening = None;
844 }
845 (Some(Listening::Move(action)), _, Some(binding)) => {
846 ctx.rebind(action, vec![binding]);
847 self.listening = None;
848 }
849 _ => {}
850 }
851 match reset {
852 Some(Listening::Button(action)) => ctx.rebind(action, action.bindings()),
853 Some(Listening::Move(action)) => ctx.rebind(action, action.bindings()),
854 None => {}
855 }
856
857 if clicked {
858 ctx.play(Sound::Click);
859 }
860 if quit {
861 ctx.close();
862 }
863 }More examples
238 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239 // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240 // is taken first, and `rebind` is applied once the closure returns.
241 let buttons: Vec<_> = ButtonAction::all()
242 .into_iter()
243 .map(|action| {
244 (
245 action,
246 bindings_text(ctx.bindings(action)),
247 ctx.down(action),
248 ctx.pressed(action),
249 ctx.released(action),
250 ctx.clicks(action),
251 )
252 })
253 .collect();
254 let axes: Vec<_> = AxisAction::all()
255 .into_iter()
256 .map(|action| {
257 (
258 action,
259 bindings_text(ctx.bindings(action)),
260 ctx.axis(action),
261 )
262 })
263 .collect();
264 let axes2: Vec<_> = Axis2Action::all()
265 .into_iter()
266 .map(|action| {
267 (
268 action,
269 bindings_text(ctx.bindings(action)),
270 ctx.axis2(action),
271 )
272 })
273 .collect();
274
275 let capturing = !ctx.ui_wants_keyboard();
276 let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277 let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278 let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279 if actuated_button.is_some() {
280 self.last_button = actuated_button;
281 }
282 if actuated_axis.is_some() {
283 self.last_axis = actuated_axis;
284 }
285 if actuated_axis2.is_some() {
286 self.last_axis2 = actuated_axis2;
287 }
288 let pointer = ctx.pointer();
289 let mut edits = RowEdits {
290 listening: self.listening,
291 start_listening: None,
292 cancel: false,
293 reset: None,
294 };
295
296 ctx.ui(|ui| {
297 egui::CentralPanel::default().show(ui, |ui| {
298 ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299 ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300 ui.label("rebinds persist across runs");
301 ui.label(format!(
302 "last captured: button {}, pad axis {}, pad stick {}",
303 text_of(self.last_button),
304 text_of(self.last_axis),
305 text_of(self.last_axis2),
306 ));
307 ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308 ui.separator();
309
310 ui.horizontal(|ui| {
311 ui.vertical(|ui| {
312 ui.heading("buttons");
313 egui::Grid::new("buttons-grid")
314 .num_columns(5)
315 .spacing([6.0, 2.0])
316 .show(ui, |ui| {
317 for (action, bindings, down, pressed, released, clicks) in &buttons
318 {
319 let control = Control::Button(*action);
320 ui.label(action.name());
321 ui.label(bindings);
322 ui.horizontal(|ui| {
323 mark(ui, "down", *down);
324 mark(ui, "pressed", *pressed);
325 mark(ui, "released", *released);
326 ui.label(format!("clicks {clicks}"));
327 });
328 rebind_cell(ui, control, &mut edits);
329 reset_cell(ui, control, &mut edits);
330 ui.end_row();
331 }
332 });
333 });
334
335 ui.separator();
336
337 ui.vertical(|ui| {
338 egui::Grid::new("axes-grid")
339 .num_columns(5)
340 .spacing([6.0, 2.0])
341 .show(ui, |ui| {
342 ui.heading("axes");
343 ui.end_row();
344 for (action, bindings, value) in &axes {
345 let control = Control::Axis(*action);
346 ui.label(action.name());
347 ui.label(bindings);
348 axis_bar(ui, *value);
349 rebind_cell(ui, control, &mut edits);
350 reset_cell(ui, control, &mut edits);
351 ui.end_row();
352 }
353
354 ui.heading("vectors");
355 ui.end_row();
356 for (action, bindings, value) in &axes2 {
357 let control = Control::Axis2(*action);
358 ui.label(action.name());
359 ui.label(bindings);
360 axis2_dot(ui, *value);
361 rebind_cell(ui, control, &mut edits);
362 reset_cell(ui, control, &mut edits);
363 ui.end_row();
364 }
365 });
366 });
367 });
368 });
369 });
370
371 if edits.cancel {
372 self.listening = None;
373 }
374 if let Some(control) = edits.start_listening {
375 self.listening = Some(control);
376 }
377 if let Some(control) = edits.reset {
378 match control {
379 Control::Button(action) => ctx.rebind(action, action.bindings()),
380 Control::Axis(action) => ctx.rebind(action, action.bindings()),
381 Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382 }
383 }
384 match (
385 self.listening,
386 actuated_button,
387 actuated_axis,
388 actuated_axis2,
389 ) {
390 (Some(Control::Button(action)), Some(binding), _, _) => {
391 ctx.rebind(action, vec![binding]);
392 self.listening = None;
393 }
394 (Some(Control::Axis(action)), _, Some(binding), _) => {
395 ctx.rebind(action, vec![binding]);
396 self.listening = None;
397 }
398 (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399 ctx.rebind(action, vec![binding]);
400 self.listening = None;
401 }
402 _ => {}
403 }
404 }Sourcepub fn bindings<A: InputAction>(&self, action: A) -> Vec<A::Binding>
pub fn bindings<A: InputAction>(&self, action: A) -> Vec<A::Binding>
The bindings action reads through right now, which a controls menu
shows through each binding’s text.
Examples found in repository?
703 fn draw_prompts(
704 &self,
705 ctx: &mut FrameContext<'_, Self>,
706 camera: Camera,
707 hovered: Option<StationKind>,
708 ) {
709 let Some(binding) = ctx.bindings(Trigger::Hail).into_iter().next() else {
710 return;
711 };
712 let hint = prompt(&binding);
713 let glyph = ctx.text_layout(&hint.text(), egui::FontId::new(PROMPT_SIZE, hint.family()));
714 let window_size = ctx.window_size();
715 let pixels_per_point = ctx.pixels_per_point();
716
717 ctx.ui(|ui| {
718 let painter = ui.painter();
719 for station in StationKind::ALL {
720 if Some(station) == hovered {
721 continue;
722 }
723 let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
724 let Some(pixel) = camera.pixel_of(top, window_size) else {
725 continue;
726 };
727 let at = logical(pixel, pixels_per_point);
728 let at = egui::pos2(at.x, at.y - PROMPT_LIFT);
729 prompt_at(painter, at, glyph.clone());
730 }
731 });
732 }More examples
735 fn overlay(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
736 let bricks_left = self
737 .bricks
738 .iter()
739 .filter(|brick| brick.hits_remaining > 0)
740 .count();
741 // Read before `ctx.ui` so a rebind changes what the hint reads this
742 // frame too.
743 let move_hint = bindings_text(ctx.bindings(Move::Paddle));
744 let pause_hint = bindings_text(ctx.bindings(Button::Pause));
745 let serve_hint = bindings_text(ctx.bindings(Button::Serve));
746 ctx.ui(|ui| {
747 ui.horizontal(|ui| {
748 ui.label(egui::RichText::new(format!("score {}", self.score)).size(32.0));
749 ui.label(format!("{bricks_left} bricks left"));
750 });
751 ui.label(format!("move: {move_hint} · {pause_hint} to pause"));
752 if self.phase == Phase::Serving {
753 ui.label(format!("{serve_hint} to serve"));
754 }
755 });
756
757 match self.phase {
758 Phase::Serving | Phase::Playing if self.paused => self.menu(ctx, "paused", false),
759 Phase::Won => self.menu(ctx, "you win", true),
760 Phase::Lost => self.menu(ctx, "game over", true),
761 _ => {}
762 }
763 }
764
765 fn menu(&mut self, ctx: &mut FrameContext<'_, Breakout>, title: &str, over: bool) {
766 let mut clicked = false;
767 let mut quit = false;
768
769 // `ctx.ui` cannot borrow `ctx`, so anything the controls list needs is
770 // read first and applied after.
771 let buttons: Vec<(Button, String)> = Button::all()
772 .into_iter()
773 .map(|action| (action, bindings_text(ctx.bindings(action))))
774 .collect();
775 let axes: Vec<(Move, String)> = Move::all()
776 .into_iter()
777 .map(|action| (action, bindings_text(ctx.bindings(action))))
778 .collect();
779 let listening = self.listening;
780 let actuated_button = (!ctx.ui_wants_keyboard())
781 .then(|| ctx.actuated_button())
782 .flatten();
783 let actuated_axis = (!ctx.ui_wants_keyboard())
784 .then(|| ctx.actuated_axis())
785 .flatten();
786 let mut reset = None;
787
788 ctx.ui(|ui| {
789 egui::Window::new(title)
790 .collapsible(false)
791 .resizable(false)
792 .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
793 .show(ui.ctx(), |ui| {
794 if over {
795 ui.label(format!("score {}", self.score));
796 }
797 if !over {
798 ui.add(
799 egui::Slider::new(&mut self.master_volume, 0.0..=1.0).text("volume"),
800 );
801 if ui.button("resume").clicked() {
802 self.paused = false;
803 clicked = true;
804 }
805 ui.separator();
806 ui.heading("controls");
807 for (action, text) in &buttons {
808 controls_row(
809 ui,
810 action.name(),
811 text,
812 listening == Some(Listening::Button(*action)),
813 &mut self.listening,
814 Listening::Button(*action),
815 &mut reset,
816 );
817 }
818 for (action, text) in &axes {
819 controls_row(
820 ui,
821 action.name(),
822 text,
823 listening == Some(Listening::Move(*action)),
824 &mut self.listening,
825 Listening::Move(*action),
826 &mut reset,
827 );
828 }
829 }
830 if ui.button("restart").clicked() {
831 self.restart();
832 clicked = true;
833 }
834 if ui.button("quit").clicked() {
835 quit = true;
836 }
837 });
838 });
839
840 match (self.listening, actuated_button, actuated_axis) {
841 (Some(Listening::Button(action)), Some(binding), _) => {
842 ctx.rebind(action, vec![binding]);
843 self.listening = None;
844 }
845 (Some(Listening::Move(action)), _, Some(binding)) => {
846 ctx.rebind(action, vec![binding]);
847 self.listening = None;
848 }
849 _ => {}
850 }
851 match reset {
852 Some(Listening::Button(action)) => ctx.rebind(action, action.bindings()),
853 Some(Listening::Move(action)) => ctx.rebind(action, action.bindings()),
854 None => {}
855 }
856
857 if clicked {
858 ctx.play(Sound::Click);
859 }
860 if quit {
861 ctx.close();
862 }
863 }823 fn draw_prompts(&self, ctx: &mut FrameContext<'_, Scene>, camera: Camera) {
824 let sit_key = ctx
825 .bindings(Button::Interact)
826 .into_iter()
827 .next()
828 .map_or_else(|| "interact".to_owned(), |binding| binding.to_string());
829 let sit = ctx.text_layout(
830 &format!("{sit_key} sits"),
831 egui::FontId::proportional(PROMPT_SIZE),
832 );
833 let hurts = ctx.text_layout("hurts", egui::FontId::proportional(PROMPT_SIZE));
834 let walk_closer = ctx.text_layout("walk closer", egui::FontId::proportional(PROMPT_SIZE));
835
836 let mut prompts = vec![(
837 SCRUBBED_ELF_POSITION + Vec3::Y * (ELF_HEIGHT + PROMPT_LIFT),
838 walk_closer,
839 )];
840 if !self.elf_animator.state().seated() {
841 prompts.push((
842 SEAT_POSITION + Vec3::Y * (SEAT_HEAD_HEIGHT + PROMPT_LIFT),
843 sit,
844 ));
845 }
846 prompts.extend(HURT_PATCHES.map(|patch| (patch + Vec3::Y * PROMPT_LIFT, hurts.clone())));
847
848 let window_size = ctx.window_size();
849 let pixels_per_point = ctx.pixels_per_point();
850 ctx.ui(|ui| {
851 let painter = ui.painter();
852 for (point, galley) in prompts {
853 let Some(pixel) = camera.pixel_of(point, window_size) else {
854 continue;
855 };
856 let at = logical(pixel, pixels_per_point);
857 let ink = galley.mesh_bounds;
858 let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
859 let backdrop = egui::Rect::from_center_size(
860 at,
861 ink.size() + egui::Vec2::splat(PROMPT_PADDING * 2.0),
862 );
863 painter.rect_filled(
864 backdrop,
865 PROMPT_PADDING,
866 egui::Color32::from_black_alpha(PANEL_BACKDROP),
867 );
868 painter.galley(pos, galley, PANEL_TEXT_COLOR);
869 }
870 });
871 }238 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239 // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240 // is taken first, and `rebind` is applied once the closure returns.
241 let buttons: Vec<_> = ButtonAction::all()
242 .into_iter()
243 .map(|action| {
244 (
245 action,
246 bindings_text(ctx.bindings(action)),
247 ctx.down(action),
248 ctx.pressed(action),
249 ctx.released(action),
250 ctx.clicks(action),
251 )
252 })
253 .collect();
254 let axes: Vec<_> = AxisAction::all()
255 .into_iter()
256 .map(|action| {
257 (
258 action,
259 bindings_text(ctx.bindings(action)),
260 ctx.axis(action),
261 )
262 })
263 .collect();
264 let axes2: Vec<_> = Axis2Action::all()
265 .into_iter()
266 .map(|action| {
267 (
268 action,
269 bindings_text(ctx.bindings(action)),
270 ctx.axis2(action),
271 )
272 })
273 .collect();
274
275 let capturing = !ctx.ui_wants_keyboard();
276 let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277 let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278 let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279 if actuated_button.is_some() {
280 self.last_button = actuated_button;
281 }
282 if actuated_axis.is_some() {
283 self.last_axis = actuated_axis;
284 }
285 if actuated_axis2.is_some() {
286 self.last_axis2 = actuated_axis2;
287 }
288 let pointer = ctx.pointer();
289 let mut edits = RowEdits {
290 listening: self.listening,
291 start_listening: None,
292 cancel: false,
293 reset: None,
294 };
295
296 ctx.ui(|ui| {
297 egui::CentralPanel::default().show(ui, |ui| {
298 ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299 ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300 ui.label("rebinds persist across runs");
301 ui.label(format!(
302 "last captured: button {}, pad axis {}, pad stick {}",
303 text_of(self.last_button),
304 text_of(self.last_axis),
305 text_of(self.last_axis2),
306 ));
307 ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308 ui.separator();
309
310 ui.horizontal(|ui| {
311 ui.vertical(|ui| {
312 ui.heading("buttons");
313 egui::Grid::new("buttons-grid")
314 .num_columns(5)
315 .spacing([6.0, 2.0])
316 .show(ui, |ui| {
317 for (action, bindings, down, pressed, released, clicks) in &buttons
318 {
319 let control = Control::Button(*action);
320 ui.label(action.name());
321 ui.label(bindings);
322 ui.horizontal(|ui| {
323 mark(ui, "down", *down);
324 mark(ui, "pressed", *pressed);
325 mark(ui, "released", *released);
326 ui.label(format!("clicks {clicks}"));
327 });
328 rebind_cell(ui, control, &mut edits);
329 reset_cell(ui, control, &mut edits);
330 ui.end_row();
331 }
332 });
333 });
334
335 ui.separator();
336
337 ui.vertical(|ui| {
338 egui::Grid::new("axes-grid")
339 .num_columns(5)
340 .spacing([6.0, 2.0])
341 .show(ui, |ui| {
342 ui.heading("axes");
343 ui.end_row();
344 for (action, bindings, value) in &axes {
345 let control = Control::Axis(*action);
346 ui.label(action.name());
347 ui.label(bindings);
348 axis_bar(ui, *value);
349 rebind_cell(ui, control, &mut edits);
350 reset_cell(ui, control, &mut edits);
351 ui.end_row();
352 }
353
354 ui.heading("vectors");
355 ui.end_row();
356 for (action, bindings, value) in &axes2 {
357 let control = Control::Axis2(*action);
358 ui.label(action.name());
359 ui.label(bindings);
360 axis2_dot(ui, *value);
361 rebind_cell(ui, control, &mut edits);
362 reset_cell(ui, control, &mut edits);
363 ui.end_row();
364 }
365 });
366 });
367 });
368 });
369 });
370
371 if edits.cancel {
372 self.listening = None;
373 }
374 if let Some(control) = edits.start_listening {
375 self.listening = Some(control);
376 }
377 if let Some(control) = edits.reset {
378 match control {
379 Control::Button(action) => ctx.rebind(action, action.bindings()),
380 Control::Axis(action) => ctx.rebind(action, action.bindings()),
381 Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382 }
383 }
384 match (
385 self.listening,
386 actuated_button,
387 actuated_axis,
388 actuated_axis2,
389 ) {
390 (Some(Control::Button(action)), Some(binding), _, _) => {
391 ctx.rebind(action, vec![binding]);
392 self.listening = None;
393 }
394 (Some(Control::Axis(action)), _, Some(binding), _) => {
395 ctx.rebind(action, vec![binding]);
396 self.listening = None;
397 }
398 (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399 ctx.rebind(action, vec![binding]);
400 self.listening = None;
401 }
402 _ => {}
403 }
404 }The button control the player pressed this frame, for a controls menu listening for one to bind.
Nothing where the player pressed nothing new; polling is the whole mechanism, so a menu that stops calling this stops listening.
Examples found in repository?
765 fn menu(&mut self, ctx: &mut FrameContext<'_, Breakout>, title: &str, over: bool) {
766 let mut clicked = false;
767 let mut quit = false;
768
769 // `ctx.ui` cannot borrow `ctx`, so anything the controls list needs is
770 // read first and applied after.
771 let buttons: Vec<(Button, String)> = Button::all()
772 .into_iter()
773 .map(|action| (action, bindings_text(ctx.bindings(action))))
774 .collect();
775 let axes: Vec<(Move, String)> = Move::all()
776 .into_iter()
777 .map(|action| (action, bindings_text(ctx.bindings(action))))
778 .collect();
779 let listening = self.listening;
780 let actuated_button = (!ctx.ui_wants_keyboard())
781 .then(|| ctx.actuated_button())
782 .flatten();
783 let actuated_axis = (!ctx.ui_wants_keyboard())
784 .then(|| ctx.actuated_axis())
785 .flatten();
786 let mut reset = None;
787
788 ctx.ui(|ui| {
789 egui::Window::new(title)
790 .collapsible(false)
791 .resizable(false)
792 .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
793 .show(ui.ctx(), |ui| {
794 if over {
795 ui.label(format!("score {}", self.score));
796 }
797 if !over {
798 ui.add(
799 egui::Slider::new(&mut self.master_volume, 0.0..=1.0).text("volume"),
800 );
801 if ui.button("resume").clicked() {
802 self.paused = false;
803 clicked = true;
804 }
805 ui.separator();
806 ui.heading("controls");
807 for (action, text) in &buttons {
808 controls_row(
809 ui,
810 action.name(),
811 text,
812 listening == Some(Listening::Button(*action)),
813 &mut self.listening,
814 Listening::Button(*action),
815 &mut reset,
816 );
817 }
818 for (action, text) in &axes {
819 controls_row(
820 ui,
821 action.name(),
822 text,
823 listening == Some(Listening::Move(*action)),
824 &mut self.listening,
825 Listening::Move(*action),
826 &mut reset,
827 );
828 }
829 }
830 if ui.button("restart").clicked() {
831 self.restart();
832 clicked = true;
833 }
834 if ui.button("quit").clicked() {
835 quit = true;
836 }
837 });
838 });
839
840 match (self.listening, actuated_button, actuated_axis) {
841 (Some(Listening::Button(action)), Some(binding), _) => {
842 ctx.rebind(action, vec![binding]);
843 self.listening = None;
844 }
845 (Some(Listening::Move(action)), _, Some(binding)) => {
846 ctx.rebind(action, vec![binding]);
847 self.listening = None;
848 }
849 _ => {}
850 }
851 match reset {
852 Some(Listening::Button(action)) => ctx.rebind(action, action.bindings()),
853 Some(Listening::Move(action)) => ctx.rebind(action, action.bindings()),
854 None => {}
855 }
856
857 if clicked {
858 ctx.play(Sound::Click);
859 }
860 if quit {
861 ctx.close();
862 }
863 }More examples
238 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239 // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240 // is taken first, and `rebind` is applied once the closure returns.
241 let buttons: Vec<_> = ButtonAction::all()
242 .into_iter()
243 .map(|action| {
244 (
245 action,
246 bindings_text(ctx.bindings(action)),
247 ctx.down(action),
248 ctx.pressed(action),
249 ctx.released(action),
250 ctx.clicks(action),
251 )
252 })
253 .collect();
254 let axes: Vec<_> = AxisAction::all()
255 .into_iter()
256 .map(|action| {
257 (
258 action,
259 bindings_text(ctx.bindings(action)),
260 ctx.axis(action),
261 )
262 })
263 .collect();
264 let axes2: Vec<_> = Axis2Action::all()
265 .into_iter()
266 .map(|action| {
267 (
268 action,
269 bindings_text(ctx.bindings(action)),
270 ctx.axis2(action),
271 )
272 })
273 .collect();
274
275 let capturing = !ctx.ui_wants_keyboard();
276 let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277 let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278 let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279 if actuated_button.is_some() {
280 self.last_button = actuated_button;
281 }
282 if actuated_axis.is_some() {
283 self.last_axis = actuated_axis;
284 }
285 if actuated_axis2.is_some() {
286 self.last_axis2 = actuated_axis2;
287 }
288 let pointer = ctx.pointer();
289 let mut edits = RowEdits {
290 listening: self.listening,
291 start_listening: None,
292 cancel: false,
293 reset: None,
294 };
295
296 ctx.ui(|ui| {
297 egui::CentralPanel::default().show(ui, |ui| {
298 ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299 ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300 ui.label("rebinds persist across runs");
301 ui.label(format!(
302 "last captured: button {}, pad axis {}, pad stick {}",
303 text_of(self.last_button),
304 text_of(self.last_axis),
305 text_of(self.last_axis2),
306 ));
307 ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308 ui.separator();
309
310 ui.horizontal(|ui| {
311 ui.vertical(|ui| {
312 ui.heading("buttons");
313 egui::Grid::new("buttons-grid")
314 .num_columns(5)
315 .spacing([6.0, 2.0])
316 .show(ui, |ui| {
317 for (action, bindings, down, pressed, released, clicks) in &buttons
318 {
319 let control = Control::Button(*action);
320 ui.label(action.name());
321 ui.label(bindings);
322 ui.horizontal(|ui| {
323 mark(ui, "down", *down);
324 mark(ui, "pressed", *pressed);
325 mark(ui, "released", *released);
326 ui.label(format!("clicks {clicks}"));
327 });
328 rebind_cell(ui, control, &mut edits);
329 reset_cell(ui, control, &mut edits);
330 ui.end_row();
331 }
332 });
333 });
334
335 ui.separator();
336
337 ui.vertical(|ui| {
338 egui::Grid::new("axes-grid")
339 .num_columns(5)
340 .spacing([6.0, 2.0])
341 .show(ui, |ui| {
342 ui.heading("axes");
343 ui.end_row();
344 for (action, bindings, value) in &axes {
345 let control = Control::Axis(*action);
346 ui.label(action.name());
347 ui.label(bindings);
348 axis_bar(ui, *value);
349 rebind_cell(ui, control, &mut edits);
350 reset_cell(ui, control, &mut edits);
351 ui.end_row();
352 }
353
354 ui.heading("vectors");
355 ui.end_row();
356 for (action, bindings, value) in &axes2 {
357 let control = Control::Axis2(*action);
358 ui.label(action.name());
359 ui.label(bindings);
360 axis2_dot(ui, *value);
361 rebind_cell(ui, control, &mut edits);
362 reset_cell(ui, control, &mut edits);
363 ui.end_row();
364 }
365 });
366 });
367 });
368 });
369 });
370
371 if edits.cancel {
372 self.listening = None;
373 }
374 if let Some(control) = edits.start_listening {
375 self.listening = Some(control);
376 }
377 if let Some(control) = edits.reset {
378 match control {
379 Control::Button(action) => ctx.rebind(action, action.bindings()),
380 Control::Axis(action) => ctx.rebind(action, action.bindings()),
381 Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382 }
383 }
384 match (
385 self.listening,
386 actuated_button,
387 actuated_axis,
388 actuated_axis2,
389 ) {
390 (Some(Control::Button(action)), Some(binding), _, _) => {
391 ctx.rebind(action, vec![binding]);
392 self.listening = None;
393 }
394 (Some(Control::Axis(action)), _, Some(binding), _) => {
395 ctx.rebind(action, vec![binding]);
396 self.listening = None;
397 }
398 (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399 ctx.rebind(action, vec![binding]);
400 self.listening = None;
401 }
402 _ => {}
403 }
404 }Sourcepub fn actuated_axis(&self) -> Option<AxisBinding>
pub fn actuated_axis(&self) -> Option<AxisBinding>
The analog control the player pushed this frame, past the deadzone a binding starts with.
The pointer and the wheel are never returned: they would take the smallest nudge for a choice.
Examples found in repository?
765 fn menu(&mut self, ctx: &mut FrameContext<'_, Breakout>, title: &str, over: bool) {
766 let mut clicked = false;
767 let mut quit = false;
768
769 // `ctx.ui` cannot borrow `ctx`, so anything the controls list needs is
770 // read first and applied after.
771 let buttons: Vec<(Button, String)> = Button::all()
772 .into_iter()
773 .map(|action| (action, bindings_text(ctx.bindings(action))))
774 .collect();
775 let axes: Vec<(Move, String)> = Move::all()
776 .into_iter()
777 .map(|action| (action, bindings_text(ctx.bindings(action))))
778 .collect();
779 let listening = self.listening;
780 let actuated_button = (!ctx.ui_wants_keyboard())
781 .then(|| ctx.actuated_button())
782 .flatten();
783 let actuated_axis = (!ctx.ui_wants_keyboard())
784 .then(|| ctx.actuated_axis())
785 .flatten();
786 let mut reset = None;
787
788 ctx.ui(|ui| {
789 egui::Window::new(title)
790 .collapsible(false)
791 .resizable(false)
792 .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
793 .show(ui.ctx(), |ui| {
794 if over {
795 ui.label(format!("score {}", self.score));
796 }
797 if !over {
798 ui.add(
799 egui::Slider::new(&mut self.master_volume, 0.0..=1.0).text("volume"),
800 );
801 if ui.button("resume").clicked() {
802 self.paused = false;
803 clicked = true;
804 }
805 ui.separator();
806 ui.heading("controls");
807 for (action, text) in &buttons {
808 controls_row(
809 ui,
810 action.name(),
811 text,
812 listening == Some(Listening::Button(*action)),
813 &mut self.listening,
814 Listening::Button(*action),
815 &mut reset,
816 );
817 }
818 for (action, text) in &axes {
819 controls_row(
820 ui,
821 action.name(),
822 text,
823 listening == Some(Listening::Move(*action)),
824 &mut self.listening,
825 Listening::Move(*action),
826 &mut reset,
827 );
828 }
829 }
830 if ui.button("restart").clicked() {
831 self.restart();
832 clicked = true;
833 }
834 if ui.button("quit").clicked() {
835 quit = true;
836 }
837 });
838 });
839
840 match (self.listening, actuated_button, actuated_axis) {
841 (Some(Listening::Button(action)), Some(binding), _) => {
842 ctx.rebind(action, vec![binding]);
843 self.listening = None;
844 }
845 (Some(Listening::Move(action)), _, Some(binding)) => {
846 ctx.rebind(action, vec![binding]);
847 self.listening = None;
848 }
849 _ => {}
850 }
851 match reset {
852 Some(Listening::Button(action)) => ctx.rebind(action, action.bindings()),
853 Some(Listening::Move(action)) => ctx.rebind(action, action.bindings()),
854 None => {}
855 }
856
857 if clicked {
858 ctx.play(Sound::Click);
859 }
860 if quit {
861 ctx.close();
862 }
863 }More examples
238 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239 // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240 // is taken first, and `rebind` is applied once the closure returns.
241 let buttons: Vec<_> = ButtonAction::all()
242 .into_iter()
243 .map(|action| {
244 (
245 action,
246 bindings_text(ctx.bindings(action)),
247 ctx.down(action),
248 ctx.pressed(action),
249 ctx.released(action),
250 ctx.clicks(action),
251 )
252 })
253 .collect();
254 let axes: Vec<_> = AxisAction::all()
255 .into_iter()
256 .map(|action| {
257 (
258 action,
259 bindings_text(ctx.bindings(action)),
260 ctx.axis(action),
261 )
262 })
263 .collect();
264 let axes2: Vec<_> = Axis2Action::all()
265 .into_iter()
266 .map(|action| {
267 (
268 action,
269 bindings_text(ctx.bindings(action)),
270 ctx.axis2(action),
271 )
272 })
273 .collect();
274
275 let capturing = !ctx.ui_wants_keyboard();
276 let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277 let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278 let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279 if actuated_button.is_some() {
280 self.last_button = actuated_button;
281 }
282 if actuated_axis.is_some() {
283 self.last_axis = actuated_axis;
284 }
285 if actuated_axis2.is_some() {
286 self.last_axis2 = actuated_axis2;
287 }
288 let pointer = ctx.pointer();
289 let mut edits = RowEdits {
290 listening: self.listening,
291 start_listening: None,
292 cancel: false,
293 reset: None,
294 };
295
296 ctx.ui(|ui| {
297 egui::CentralPanel::default().show(ui, |ui| {
298 ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299 ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300 ui.label("rebinds persist across runs");
301 ui.label(format!(
302 "last captured: button {}, pad axis {}, pad stick {}",
303 text_of(self.last_button),
304 text_of(self.last_axis),
305 text_of(self.last_axis2),
306 ));
307 ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308 ui.separator();
309
310 ui.horizontal(|ui| {
311 ui.vertical(|ui| {
312 ui.heading("buttons");
313 egui::Grid::new("buttons-grid")
314 .num_columns(5)
315 .spacing([6.0, 2.0])
316 .show(ui, |ui| {
317 for (action, bindings, down, pressed, released, clicks) in &buttons
318 {
319 let control = Control::Button(*action);
320 ui.label(action.name());
321 ui.label(bindings);
322 ui.horizontal(|ui| {
323 mark(ui, "down", *down);
324 mark(ui, "pressed", *pressed);
325 mark(ui, "released", *released);
326 ui.label(format!("clicks {clicks}"));
327 });
328 rebind_cell(ui, control, &mut edits);
329 reset_cell(ui, control, &mut edits);
330 ui.end_row();
331 }
332 });
333 });
334
335 ui.separator();
336
337 ui.vertical(|ui| {
338 egui::Grid::new("axes-grid")
339 .num_columns(5)
340 .spacing([6.0, 2.0])
341 .show(ui, |ui| {
342 ui.heading("axes");
343 ui.end_row();
344 for (action, bindings, value) in &axes {
345 let control = Control::Axis(*action);
346 ui.label(action.name());
347 ui.label(bindings);
348 axis_bar(ui, *value);
349 rebind_cell(ui, control, &mut edits);
350 reset_cell(ui, control, &mut edits);
351 ui.end_row();
352 }
353
354 ui.heading("vectors");
355 ui.end_row();
356 for (action, bindings, value) in &axes2 {
357 let control = Control::Axis2(*action);
358 ui.label(action.name());
359 ui.label(bindings);
360 axis2_dot(ui, *value);
361 rebind_cell(ui, control, &mut edits);
362 reset_cell(ui, control, &mut edits);
363 ui.end_row();
364 }
365 });
366 });
367 });
368 });
369 });
370
371 if edits.cancel {
372 self.listening = None;
373 }
374 if let Some(control) = edits.start_listening {
375 self.listening = Some(control);
376 }
377 if let Some(control) = edits.reset {
378 match control {
379 Control::Button(action) => ctx.rebind(action, action.bindings()),
380 Control::Axis(action) => ctx.rebind(action, action.bindings()),
381 Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382 }
383 }
384 match (
385 self.listening,
386 actuated_button,
387 actuated_axis,
388 actuated_axis2,
389 ) {
390 (Some(Control::Button(action)), Some(binding), _, _) => {
391 ctx.rebind(action, vec![binding]);
392 self.listening = None;
393 }
394 (Some(Control::Axis(action)), _, Some(binding), _) => {
395 ctx.rebind(action, vec![binding]);
396 self.listening = None;
397 }
398 (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399 ctx.rebind(action, vec![binding]);
400 self.listening = None;
401 }
402 _ => {}
403 }
404 }Sourcepub fn actuated_axis2(&self) -> Option<Axis2Binding>
pub fn actuated_axis2(&self) -> Option<Axis2Binding>
The stick the player pushed this frame, past the deadzone a binding starts with.
Examples found in repository?
238 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239 // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240 // is taken first, and `rebind` is applied once the closure returns.
241 let buttons: Vec<_> = ButtonAction::all()
242 .into_iter()
243 .map(|action| {
244 (
245 action,
246 bindings_text(ctx.bindings(action)),
247 ctx.down(action),
248 ctx.pressed(action),
249 ctx.released(action),
250 ctx.clicks(action),
251 )
252 })
253 .collect();
254 let axes: Vec<_> = AxisAction::all()
255 .into_iter()
256 .map(|action| {
257 (
258 action,
259 bindings_text(ctx.bindings(action)),
260 ctx.axis(action),
261 )
262 })
263 .collect();
264 let axes2: Vec<_> = Axis2Action::all()
265 .into_iter()
266 .map(|action| {
267 (
268 action,
269 bindings_text(ctx.bindings(action)),
270 ctx.axis2(action),
271 )
272 })
273 .collect();
274
275 let capturing = !ctx.ui_wants_keyboard();
276 let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277 let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278 let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279 if actuated_button.is_some() {
280 self.last_button = actuated_button;
281 }
282 if actuated_axis.is_some() {
283 self.last_axis = actuated_axis;
284 }
285 if actuated_axis2.is_some() {
286 self.last_axis2 = actuated_axis2;
287 }
288 let pointer = ctx.pointer();
289 let mut edits = RowEdits {
290 listening: self.listening,
291 start_listening: None,
292 cancel: false,
293 reset: None,
294 };
295
296 ctx.ui(|ui| {
297 egui::CentralPanel::default().show(ui, |ui| {
298 ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299 ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300 ui.label("rebinds persist across runs");
301 ui.label(format!(
302 "last captured: button {}, pad axis {}, pad stick {}",
303 text_of(self.last_button),
304 text_of(self.last_axis),
305 text_of(self.last_axis2),
306 ));
307 ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308 ui.separator();
309
310 ui.horizontal(|ui| {
311 ui.vertical(|ui| {
312 ui.heading("buttons");
313 egui::Grid::new("buttons-grid")
314 .num_columns(5)
315 .spacing([6.0, 2.0])
316 .show(ui, |ui| {
317 for (action, bindings, down, pressed, released, clicks) in &buttons
318 {
319 let control = Control::Button(*action);
320 ui.label(action.name());
321 ui.label(bindings);
322 ui.horizontal(|ui| {
323 mark(ui, "down", *down);
324 mark(ui, "pressed", *pressed);
325 mark(ui, "released", *released);
326 ui.label(format!("clicks {clicks}"));
327 });
328 rebind_cell(ui, control, &mut edits);
329 reset_cell(ui, control, &mut edits);
330 ui.end_row();
331 }
332 });
333 });
334
335 ui.separator();
336
337 ui.vertical(|ui| {
338 egui::Grid::new("axes-grid")
339 .num_columns(5)
340 .spacing([6.0, 2.0])
341 .show(ui, |ui| {
342 ui.heading("axes");
343 ui.end_row();
344 for (action, bindings, value) in &axes {
345 let control = Control::Axis(*action);
346 ui.label(action.name());
347 ui.label(bindings);
348 axis_bar(ui, *value);
349 rebind_cell(ui, control, &mut edits);
350 reset_cell(ui, control, &mut edits);
351 ui.end_row();
352 }
353
354 ui.heading("vectors");
355 ui.end_row();
356 for (action, bindings, value) in &axes2 {
357 let control = Control::Axis2(*action);
358 ui.label(action.name());
359 ui.label(bindings);
360 axis2_dot(ui, *value);
361 rebind_cell(ui, control, &mut edits);
362 reset_cell(ui, control, &mut edits);
363 ui.end_row();
364 }
365 });
366 });
367 });
368 });
369 });
370
371 if edits.cancel {
372 self.listening = None;
373 }
374 if let Some(control) = edits.start_listening {
375 self.listening = Some(control);
376 }
377 if let Some(control) = edits.reset {
378 match control {
379 Control::Button(action) => ctx.rebind(action, action.bindings()),
380 Control::Axis(action) => ctx.rebind(action, action.bindings()),
381 Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382 }
383 }
384 match (
385 self.listening,
386 actuated_button,
387 actuated_axis,
388 actuated_axis2,
389 ) {
390 (Some(Control::Button(action)), Some(binding), _, _) => {
391 ctx.rebind(action, vec![binding]);
392 self.listening = None;
393 }
394 (Some(Control::Axis(action)), _, Some(binding), _) => {
395 ctx.rebind(action, vec![binding]);
396 self.listening = None;
397 }
398 (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399 ctx.rebind(action, vec![binding]);
400 self.listening = None;
401 }
402 _ => {}
403 }
404 }Sourcepub fn dt(&self) -> Duration
pub fn dt(&self) -> Duration
Duration of the previous frame: this frame’s own variable time
step, distinct from the fixed one tick runs at.
Examples found in repository?
359 fn handle_camera(&mut self, ctx: &mut FrameContext<'_, Self>, elapsed: f32) {
360 if ctx.ui_wants_pointer() || ctx.ui_wants_keyboard() {
361 return;
362 }
363 let pan = ctx.axis2(Motion::Pan);
364 let wheel = ctx.axis(Height::Wheel);
365 let look = if ctx.down(Drag::Turn) {
366 ctx.axis2(Motion::Look)
367 } else {
368 Vec2::ZERO
369 };
370 if pan == Vec2::ZERO && wheel == 0.0 && look == Vec2::ZERO {
371 return;
372 }
373
374 let player = self.player.get_or_insert_with(|| {
375 let eye = Self::orbit_eye(elapsed);
376 let forward = (Vec3::ZERO - eye).normalize();
377 Player {
378 eye,
379 yaw: (-forward.x).atan2(-forward.z),
380 pitch: forward.y.asin(),
381 }
382 });
383
384 player.yaw -= look.x;
385 player.pitch = (player.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
386
387 let forward = Vec3::new(-player.yaw.sin(), 0.0, -player.yaw.cos());
388 let right = Vec3::new(player.yaw.cos(), 0.0, -player.yaw.sin());
389 player.eye += (forward * pan.y + right * pan.x) * PAN_SPEED * ctx.dt().as_secs_f32();
390 player.eye.y =
391 (player.eye.y + wheel * WHEEL_STEP).clamp(MIN_CAMERA_HEIGHT, MAX_CAMERA_HEIGHT);
392 }
393
394 fn draw_ground(ctx: &mut FrameContext<'_, Self>) {
395 let side = (FIELD_RADIUS + FIELD_INNER_RADIUS) * 2.2;
396 ctx.draw(
397 Plane
398 .at(Transform::from_scale(Vec3::new(side, 1.0, side)))
399 .material(Material::lit(GROUND_COLOR).roughness(0.9)),
400 );
401 }
402
403 fn draw_field(&self, ctx: &mut FrameContext<'_, Self>, elapsed: f32) {
404 for entry in &self.field {
405 let yaw = if self.settings.moving && entry.moving {
406 entry.phase + elapsed * MOVING_SPEED
407 } else {
408 entry.phase
409 };
410 ctx.draw(
411 Rock { seed: entry.seed }.at(Transform::from_scale_rotation_translation(
412 Vec3::ONE,
413 Quat::from_rotation_y(yaw),
414 entry.position,
415 )),
416 );
417 }
418 }
419
420 /// How many field items lie in the camera's view at `window_size`:
421 /// every item where the field holds at most [`MAX_IN_VIEW_SAMPLES`],
422 /// otherwise one item stepped at a time and the count scaled back up
423 /// to the whole field; `true` in the second place where the count
424 /// came from such a step.
425 ///
426 /// Each item is tested on the engine's own workers: a parallel iterator
427 /// reaches them with nothing configured for it.
428 fn count_in_view(&self, camera: &Camera, window_size: UVec2) -> (usize, bool) {
429 let stride = (self.field.len() as u32 / MAX_IN_VIEW_SAMPLES).max(1) as usize;
430 let tested = self.field.par_iter().step_by(stride);
431 let tested_count = self.field.len().div_ceil(stride);
432 let in_view = tested
433 .filter(|entry| Self::in_view(camera, entry.position, window_size))
434 .count();
435 let estimate = in_view
436 .checked_mul(self.field.len())
437 .and_then(|scaled| scaled.checked_div(tested_count))
438 .unwrap_or(in_view);
439 (estimate, stride > 1)
440 }
441
442 /// Whether `position` draws inside `window_size`, the frame's own
443 /// bound of what the camera's view holds.
444 fn in_view(camera: &Camera, position: Vec3, window_size: UVec2) -> bool {
445 camera.pixel_of(position, window_size).is_some_and(|pixel| {
446 pixel.x >= 0.0
447 && pixel.y >= 0.0
448 && pixel.x < window_size.x as f32
449 && pixel.y < window_size.y as f32
450 })
451 }
452
453 /// The load controls, and this frame's own cost, reported below them.
454 fn controls(&mut self, ctx: &mut FrameContext<'_, Self>, camera: &Camera) {
455 let submitted = self.field.len();
456 let seeds = self.applied_seed_count;
457 let average_ms = self.frame_times.average_ms();
458 let fps = if average_ms > 0.0 {
459 1000.0 / average_ms
460 } else {
461 0.0
462 };
463 let elapsed = ctx.elapsed().as_secs_f32();
464 let (in_view, sampled) = self.count_in_view(camera, ctx.window_size());
465
466 ctx.ui(|ui| {
467 egui::Frame::new()
468 .fill(egui::Color32::from_gray(24))
469 .inner_margin(PANEL_PADDING)
470 .corner_radius(f32::from(PANEL_PADDING))
471 .show(ui, |ui| {
472 ui.add(
473 egui::Slider::new(
474 &mut self.settings.instance_count,
475 MIN_INSTANCE_COUNT..=MAX_INSTANCE_COUNT,
476 )
477 .text("instance count"),
478 );
479 ui.add(
480 egui::Slider::new(
481 &mut self.settings.seed_count,
482 MIN_SEED_COUNT..=MAX_SEED_COUNT,
483 )
484 .text("distinct seeds"),
485 );
486 ui.checkbox(&mut self.settings.sun_shadow, "sun shadow");
487 ui.checkbox(&mut self.settings.moving, "moving fraction");
488 ui.separator();
489 ui.label(format!("instances submitted {submitted}"));
490 if sampled {
491 ui.label(format!("in view, sampled {in_view}"));
492 } else {
493 ui.label(format!("instances in view {in_view}"));
494 }
495 ui.label(format!("distinct seeds {seeds}"));
496 ui.label(format!("frame time {average_ms:.2}ms, {fps:.0} fps"));
497 ui.label(format!("elapsed {elapsed:.1}s"));
498 });
499 });
500 }
501}
502
503/// `instance_count` field values, each drawing one of `seed_count`
504/// distinct seed values in a cycle, and scattered from
505/// [`FIELD_INNER_RADIUS`] out to [`FIELD_RADIUS`]; each built from an
506/// integer-hash of its own index.
507fn build_field(instance_count: u32, seed_count: u32) -> Vec<FieldEntry> {
508 (0..instance_count)
509 .map(|index| {
510 let angle = hash_unit(index, 0) * core::f32::consts::TAU;
511 let spread = hash_unit(index, 1).sqrt();
512 let distance = FIELD_INNER_RADIUS + spread * (FIELD_RADIUS - FIELD_INNER_RADIUS);
513 FieldEntry {
514 seed: index % seed_count,
515 position: Vec3::new(angle.cos() * distance, 0.0, angle.sin() * distance),
516 phase: hash_unit(index, 2) * core::f32::consts::TAU,
517 moving: index % MOVING_STRIDE == 0,
518 }
519 })
520 .collect()
521}
522
523/// A rock built from `seed`: a cone of [`ROCK_SIDES`] sides, each base
524/// corner and the apex height displaced by an integer-hash of `seed`.
525fn build_rock(seed: u32) -> MeshData {
526 let height = ROCK_HEIGHT * (1.0 + hash_signed(seed, ROCK_SIDES) * ROCK_HEIGHT_DISPLACEMENT);
527 let apex = Vec3::Y * height;
528 let base: Vec<Vec3> = (0..ROCK_SIDES)
529 .map(|corner| {
530 let angle = core::f32::consts::TAU * corner as f32 / ROCK_SIDES as f32;
531 let radius =
532 ROCK_BASE_RADIUS * (1.0 + hash_signed(seed, corner) * ROCK_RADIAL_DISPLACEMENT);
533 Vec3::new(angle.cos() * radius, 0.0, angle.sin() * radius)
534 })
535 .collect();
536
537 let mut vertices = Vec::with_capacity(base.len() * 6);
538 let mut indices = Vec::with_capacity(base.len() * 6);
539 for corner in 0..base.len() {
540 let next = (corner + 1) % base.len();
541 push_face(&mut vertices, &mut indices, base[corner], apex, base[next]);
542 push_face(
543 &mut vertices,
544 &mut indices,
545 base[corner],
546 base[next],
547 Vec3::ZERO,
548 );
549 }
550
551 MeshData::new(vertices, indices).with_material(Material::lit(ROCK_COLOR))
552}
553
554/// One triangle, shaded flat, over `a`, `b`, `c`, in the order that faces
555/// outward: counter-clockwise as seen from the side its own normal points
556/// to.
557fn push_face(vertices: &mut Vec<Vertex>, indices: &mut Vec<u32>, a: Vec3, b: Vec3, c: Vec3) {
558 let normal = (b - a).cross(c - a).normalize();
559 let uvs = [
560 Vec2::new(0.0, 1.0),
561 Vec2::new(0.5, 0.0),
562 Vec2::new(1.0, 1.0),
563 ];
564 let base = vertices.len() as u32;
565 for (point, uv) in [a, b, c].into_iter().zip(uvs) {
566 vertices.push(Vertex::new(point, normal, uv));
567 }
568 indices.extend([base, base + 1, base + 2]);
569}
570
571/// An integer-hash of `seed` and `salt`.
572fn hash(seed: u32, salt: u32) -> u32 {
573 let mut x = seed ^ salt.wrapping_mul(0x9E37_79B9);
574 x ^= x >> 16;
575 x = x.wrapping_mul(0x7FEB_352D);
576 x ^= x >> 15;
577 x = x.wrapping_mul(0x846C_A68B);
578 x ^= x >> 16;
579 x
580}
581
582/// `hash`, scaled to `0.0..1.0`.
583fn hash_unit(seed: u32, salt: u32) -> f32 {
584 hash(seed, salt) as f32 / u32::MAX as f32
585}
586
587/// `hash`, scaled to `-1.0..1.0`.
588fn hash_signed(seed: u32, salt: u32) -> f32 {
589 hash_unit(seed, salt) * 2.0 - 1.0
590}
591
592impl Game for StressPreview {
593 type Meshes = Shape;
594 type Sounds = NoSounds;
595 type InputActions = Controls;
596 type Skyboxes = Sky;
597 type SurfaceStyles = NoSurfaceStyles;
598 type PostEffects = NoPostEffects;
599
600 fn tick(&mut self, _ctx: &mut TickContext<'_, Self>) {}
601
602 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
603 self.apply_settings();
604 self.frame_times.record(ctx.dt());
605
606 let elapsed = ctx.elapsed().as_secs_f32();
607 self.handle_camera(ctx, elapsed);
608 let camera = self.camera(elapsed);
609 ctx.set_camera(camera);
610 ctx.set_skybox(Sky::Day);
611
612 let sun = Light::directional(SUN_DIRECTION, SUN_COLOR);
613 ctx.light(if self.settings.sun_shadow {
614 sun.shadow()
615 } else {
616 sun
617 });
618
619 Self::draw_ground(ctx);
620 self.draw_field(ctx, elapsed);
621 self.controls(ctx, &camera);
622 }More examples
802 fn fly_camera(&mut self, ctx: &mut FrameContext<'_, Self>) {
803 if !ctx.ui_wants_pointer() && ctx.down(Move::Look) {
804 let look = ctx.axis2(Turn::Look);
805 self.yaw -= look.x;
806 self.pitch = (self.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
807 }
808
809 let wheel = ctx.axis(Speed::Wheel);
810 if !ctx.ui_wants_pointer() && wheel != 0.0 {
811 self.speed_scale =
812 (self.speed_scale * SPEED_STEP.powf(wheel)).clamp(MIN_SPEED_SCALE, MAX_SPEED_SCALE);
813 }
814
815 let forward = self.forward();
816 let right = Vec3::new(self.yaw.cos(), 0.0, -self.yaw.sin());
817 let mut move_by = Vec3::ZERO;
818 if ctx.down(Move::Forward) {
819 move_by += forward;
820 }
821 if ctx.down(Move::Back) {
822 move_by -= forward;
823 }
824 if ctx.down(Move::Right) {
825 move_by += right;
826 }
827 if ctx.down(Move::Left) {
828 move_by -= right;
829 }
830 if ctx.down(Move::Up) {
831 move_by += Vec3::Y;
832 }
833 if ctx.down(Move::Down) {
834 move_by -= Vec3::Y;
835 }
836 if move_by.length_squared() > 1.0 {
837 move_by = move_by.normalize();
838 }
839
840 self.eye += move_by * MOVE_SPEED * self.speed_scale * ctx.dt().as_secs_f32();
841 self.eye.y = self.eye.y.max(MIN_EYE_HEIGHT);
842 }Sourcepub fn elapsed(&self) -> Duration
pub fn elapsed(&self) -> Duration
Duration the game has been running.
Examples found in repository?
741 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
742 self.apply_settings();
743
744 let center = Vec3::from(self.butterflies.center());
745 let elapsed = ctx.elapsed().as_secs_f32();
746 ctx.set_camera(Self::camera(center, self.world, elapsed));
747 ctx.set_skybox(Sky::Day);
748 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
749
750 Self::draw_ground(ctx);
751 self.draw_butterflies(ctx);
752 self.panel(ctx);
753 }More examples
951 fn draw_outpost(&self, ctx: &mut FrameContext<'_, Self>) {
952 let clock = ctx.elapsed().as_secs_f32();
953
954 for &(position, scale) in &PILLARS {
955 ctx.draw(
956 Cube.at(Transform::from_scale_rotation_translation(
957 scale,
958 Quat::IDENTITY,
959 OUTPOST + position,
960 ))
961 .material(Material::lit(Color::rgb(0.55, 0.5, 0.45))),
962 );
963 }
964
965 ctx.draw(
966 Cube.at(Transform::from_scale_rotation_translation(
967 POLE_SCALE,
968 Quat::IDENTITY,
969 OUTPOST + POLE_POSITION,
970 ))
971 .material(Material::lit(Color::rgb(0.3, 0.24, 0.18))),
972 );
973
974 ctx.set_surface_style(Banner { time: clock });
975 ctx.draw(
976 BannerCloth
977 .at(Transform::from_translation(OUTPOST + BANNER_MOUNT))
978 .material(Material::lit(Color::rgb(0.75, 0.12, 0.12)))
979 .surface_style::<Banner>(),
980 );
981
982 ctx.set_surface_style(Field {
983 tint: Color::rgb(0.25, 0.75, 1.0),
984 time: clock,
985 });
986 ctx.draw(
987 Sphere { subdivisions: 2 }
988 .at(Transform::from_scale_rotation_translation(
989 Vec3::splat(FIELD_ORB_SCALE),
990 Quat::IDENTITY,
991 OUTPOST + FIELD_ORB_POSITION,
992 ))
993 .material(Material::color(Color::BLACK))
994 .surface_style::<Field>(),
995 );
996 }454 fn controls(&mut self, ctx: &mut FrameContext<'_, Self>, camera: &Camera) {
455 let submitted = self.field.len();
456 let seeds = self.applied_seed_count;
457 let average_ms = self.frame_times.average_ms();
458 let fps = if average_ms > 0.0 {
459 1000.0 / average_ms
460 } else {
461 0.0
462 };
463 let elapsed = ctx.elapsed().as_secs_f32();
464 let (in_view, sampled) = self.count_in_view(camera, ctx.window_size());
465
466 ctx.ui(|ui| {
467 egui::Frame::new()
468 .fill(egui::Color32::from_gray(24))
469 .inner_margin(PANEL_PADDING)
470 .corner_radius(f32::from(PANEL_PADDING))
471 .show(ui, |ui| {
472 ui.add(
473 egui::Slider::new(
474 &mut self.settings.instance_count,
475 MIN_INSTANCE_COUNT..=MAX_INSTANCE_COUNT,
476 )
477 .text("instance count"),
478 );
479 ui.add(
480 egui::Slider::new(
481 &mut self.settings.seed_count,
482 MIN_SEED_COUNT..=MAX_SEED_COUNT,
483 )
484 .text("distinct seeds"),
485 );
486 ui.checkbox(&mut self.settings.sun_shadow, "sun shadow");
487 ui.checkbox(&mut self.settings.moving, "moving fraction");
488 ui.separator();
489 ui.label(format!("instances submitted {submitted}"));
490 if sampled {
491 ui.label(format!("in view, sampled {in_view}"));
492 } else {
493 ui.label(format!("instances in view {in_view}"));
494 }
495 ui.label(format!("distinct seeds {seeds}"));
496 ui.label(format!("frame time {average_ms:.2}ms, {fps:.0} fps"));
497 ui.label(format!("elapsed {elapsed:.1}s"));
498 });
499 });
500 }
501}
502
503/// `instance_count` field values, each drawing one of `seed_count`
504/// distinct seed values in a cycle, and scattered from
505/// [`FIELD_INNER_RADIUS`] out to [`FIELD_RADIUS`]; each built from an
506/// integer-hash of its own index.
507fn build_field(instance_count: u32, seed_count: u32) -> Vec<FieldEntry> {
508 (0..instance_count)
509 .map(|index| {
510 let angle = hash_unit(index, 0) * core::f32::consts::TAU;
511 let spread = hash_unit(index, 1).sqrt();
512 let distance = FIELD_INNER_RADIUS + spread * (FIELD_RADIUS - FIELD_INNER_RADIUS);
513 FieldEntry {
514 seed: index % seed_count,
515 position: Vec3::new(angle.cos() * distance, 0.0, angle.sin() * distance),
516 phase: hash_unit(index, 2) * core::f32::consts::TAU,
517 moving: index % MOVING_STRIDE == 0,
518 }
519 })
520 .collect()
521}
522
523/// A rock built from `seed`: a cone of [`ROCK_SIDES`] sides, each base
524/// corner and the apex height displaced by an integer-hash of `seed`.
525fn build_rock(seed: u32) -> MeshData {
526 let height = ROCK_HEIGHT * (1.0 + hash_signed(seed, ROCK_SIDES) * ROCK_HEIGHT_DISPLACEMENT);
527 let apex = Vec3::Y * height;
528 let base: Vec<Vec3> = (0..ROCK_SIDES)
529 .map(|corner| {
530 let angle = core::f32::consts::TAU * corner as f32 / ROCK_SIDES as f32;
531 let radius =
532 ROCK_BASE_RADIUS * (1.0 + hash_signed(seed, corner) * ROCK_RADIAL_DISPLACEMENT);
533 Vec3::new(angle.cos() * radius, 0.0, angle.sin() * radius)
534 })
535 .collect();
536
537 let mut vertices = Vec::with_capacity(base.len() * 6);
538 let mut indices = Vec::with_capacity(base.len() * 6);
539 for corner in 0..base.len() {
540 let next = (corner + 1) % base.len();
541 push_face(&mut vertices, &mut indices, base[corner], apex, base[next]);
542 push_face(
543 &mut vertices,
544 &mut indices,
545 base[corner],
546 base[next],
547 Vec3::ZERO,
548 );
549 }
550
551 MeshData::new(vertices, indices).with_material(Material::lit(ROCK_COLOR))
552}
553
554/// One triangle, shaded flat, over `a`, `b`, `c`, in the order that faces
555/// outward: counter-clockwise as seen from the side its own normal points
556/// to.
557fn push_face(vertices: &mut Vec<Vertex>, indices: &mut Vec<u32>, a: Vec3, b: Vec3, c: Vec3) {
558 let normal = (b - a).cross(c - a).normalize();
559 let uvs = [
560 Vec2::new(0.0, 1.0),
561 Vec2::new(0.5, 0.0),
562 Vec2::new(1.0, 1.0),
563 ];
564 let base = vertices.len() as u32;
565 for (point, uv) in [a, b, c].into_iter().zip(uvs) {
566 vertices.push(Vertex::new(point, normal, uv));
567 }
568 indices.extend([base, base + 1, base + 2]);
569}
570
571/// An integer-hash of `seed` and `salt`.
572fn hash(seed: u32, salt: u32) -> u32 {
573 let mut x = seed ^ salt.wrapping_mul(0x9E37_79B9);
574 x ^= x >> 16;
575 x = x.wrapping_mul(0x7FEB_352D);
576 x ^= x >> 15;
577 x = x.wrapping_mul(0x846C_A68B);
578 x ^= x >> 16;
579 x
580}
581
582/// `hash`, scaled to `0.0..1.0`.
583fn hash_unit(seed: u32, salt: u32) -> f32 {
584 hash(seed, salt) as f32 / u32::MAX as f32
585}
586
587/// `hash`, scaled to `-1.0..1.0`.
588fn hash_signed(seed: u32, salt: u32) -> f32 {
589 hash_unit(seed, salt) * 2.0 - 1.0
590}
591
592impl Game for StressPreview {
593 type Meshes = Shape;
594 type Sounds = NoSounds;
595 type InputActions = Controls;
596 type Skyboxes = Sky;
597 type SurfaceStyles = NoSurfaceStyles;
598 type PostEffects = NoPostEffects;
599
600 fn tick(&mut self, _ctx: &mut TickContext<'_, Self>) {}
601
602 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
603 self.apply_settings();
604 self.frame_times.record(ctx.dt());
605
606 let elapsed = ctx.elapsed().as_secs_f32();
607 self.handle_camera(ctx, elapsed);
608 let camera = self.camera(elapsed);
609 ctx.set_camera(camera);
610 ctx.set_skybox(Sky::Day);
611
612 let sun = Light::directional(SUN_DIRECTION, SUN_COLOR);
613 ctx.light(if self.settings.sun_shadow {
614 sun.shadow()
615 } else {
616 sun
617 });
618
619 Self::draw_ground(ctx);
620 self.draw_field(ctx, elapsed);
621 self.controls(ctx, &camera);
622 }920 fn frame(&mut self, ctx: &mut FrameContext<'_, Scene>) {
921 self.steer_camera(ctx);
922
923 let alpha = ctx.alpha();
924 let elf_pos = self.elf_prev.lerp(self.elf_pos, alpha);
925 let elf_height = self.elf_height_prev + (self.elf_height - self.elf_height_prev) * alpha;
926 let (butterfly_pos, butterfly_yaw) = butterfly_pose(ctx.elapsed().as_secs_f32());
927
928 let camera = orbit_camera(elf_pos, self.camera_yaw, self.camera_pitch);
929 ctx.set_camera(camera);
930 ctx.set_cursor(if self.holding {
931 Cursor::Held
932 } else {
933 Cursor::Arrow
934 });
935 ctx.set_skybox(Sky::Day);
936 ctx.set_exposure(3.0);
937 ctx.set_bloom(0.2);
938 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
939 ctx.light(
940 Light::point(
941 LAMP_POST_POSITION + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP * 0.5),
942 LAMP_LIGHT_COLOR,
943 LAMP_LIGHT_RANGE,
944 )
945 .shadow(),
946 );
947 ctx.light(
948 Light::spot(Spot {
949 position: SPOT_POSITION,
950 direction: SPOT_DIRECTION,
951 color: SPOT_COLOR,
952 range: SPOT_RANGE,
953 angle: SPOT_ANGLE,
954 })
955 .shadow(),
956 );
957 ctx.light(
958 Light::point(butterfly_pos, BUTTERFLY_LIGHT_COLOR, BUTTERFLY_LIGHT_RANGE).shadow(),
959 );
960
961 ctx.draw(
962 Plane
963 .at(Transform::from_scale(Vec3::new(
964 GROUND_SIZE,
965 1.0,
966 GROUND_SIZE,
967 )))
968 .material(Material::lit(GROUND_COLOR)),
969 );
970 for patch in HURT_PATCHES {
971 ctx.draw(
972 Plane
973 .at(Transform::from_scale_rotation_translation(
974 Vec3::splat(HURT_RADIUS * 2.0),
975 Quat::IDENTITY,
976 patch,
977 ))
978 .material(Material::lit(HURT_COLOR)),
979 );
980 }
981 ctx.draw(
982 Cube.at(Transform::from_scale_rotation_translation(
983 Vec3::new(SEAT_FOOTPRINT, SEAT_HEIGHT, SEAT_FOOTPRINT),
984 Quat::IDENTITY,
985 SEAT_POSITION + Vec3::Y * SEAT_HEIGHT * 0.5,
986 ))
987 .material(Material::lit(SEAT_COLOR)),
988 );
989 ctx.draw(
990 Cube.at(Transform::from_scale_rotation_translation(
991 Vec3::new(LAMP_POST_THICKNESS, LAMP_POST_HEIGHT, LAMP_POST_THICKNESS),
992 Quat::IDENTITY,
993 LAMP_POST_POSITION + Vec3::Y * LAMP_POST_HEIGHT * 0.5,
994 ))
995 .material(Material::lit(LAMP_POST_COLOR)),
996 );
997 ctx.draw(
998 Cube.at(Transform::from_scale_rotation_translation(
999 Vec3::splat(LAMP_HEAD_SIZE),
1000 Quat::IDENTITY,
1001 LAMP_POST_POSITION
1002 + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP + LAMP_HEAD_SIZE * 0.5),
1003 ))
1004 .material(Material::color(Color::BLACK).emissive(LAMP_LIGHT_COLOR)),
1005 );
1006 ctx.draw(
1007 Cube.at(Transform::from_scale_rotation_translation(
1008 Vec3::splat(SPOT_FIXTURE_SIZE),
1009 Quat::IDENTITY,
1010 SPOT_POSITION + Vec3::Y * SPOT_FIXTURE_SIZE * 0.5,
1011 ))
1012 .material(Material::lit(SPOT_FIXTURE_COLOR)),
1013 );
1014
1015 ctx.draw(
1016 Elf.at(Transform::from_rotation_translation(
1017 Quat::from_rotation_y(self.elf_yaw),
1018 elf_pos + Vec3::Y * elf_height,
1019 ))
1020 .posed(&self.elf_animator),
1021 );
1022 ctx.draw(
1023 Elf.at(Transform::from_rotation_translation(
1024 Quat::from_rotation_y(core::f32::consts::PI),
1025 SCRUBBED_ELF_POSITION,
1026 ))
1027 .posed(&self.scrubbed_animator),
1028 );
1029 ctx.draw(
1030 Butterfly
1031 .at(Transform::from_rotation_translation(
1032 Quat::from_rotation_y(butterfly_yaw),
1033 butterfly_pos,
1034 ))
1035 .posed(&self.butterfly_animator)
1036 .material(Material::lit(Color::WHITE).emissive(BUTTERFLY_EMISSIVE)),
1037 );
1038
1039 self.draw_prompts(ctx, camera);
1040 self.panel(ctx);
1041 }Sourcepub fn alpha(&self) -> f32
pub fn alpha(&self) -> f32
This frame’s position into the next simulation step, a fraction in
0.0..1.0; used to draw between two tick states.
Examples found in repository?
1722 fn frame_overworld(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1723 let drawn_at = self.previous.lerp(self.position, ctx.alpha());
1724 ctx.set_camera(Self::camera(drawn_at, OVERWORLD_CAMERA_OFFSET));
1725 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
1726
1727 self.draw_ground(ctx);
1728 self.draw_hedgerow(ctx);
1729 self.draw_pond(ctx);
1730 self.draw_crates(ctx);
1731 self.draw_well(ctx);
1732 self.draw_flora(ctx);
1733 Self::draw_mouth(ctx, ENTRANCE);
1734 self.draw_walker(ctx, drawn_at);
1735 }
1736
1737 fn frame_cave(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1738 let drawn_at = self.previous.lerp(self.position, ctx.alpha());
1739 let camera = Self::camera(drawn_at, CAVE_CAMERA_OFFSET);
1740 ctx.set_camera(camera);
1741
1742 self.draw_cave_floor(ctx);
1743 self.draw_cave_walls(ctx);
1744 Self::draw_door_wall(ctx, (self.ghost > 0.0).then_some(drawn_at.x), self.ghost);
1745 Self::draw_mouth(ctx, EXIT);
1746 self.draw_torches(ctx);
1747 self.draw_door(ctx, self.ghost);
1748 self.draw_door_frame(ctx, self.ghost);
1749 if !self.gem_taken {
1750 self.draw_gem(ctx);
1751 }
1752 self.draw_walker(ctx, drawn_at);
1753 self.draw_door_prompt(ctx, camera);
1754 }More examples
583 fn draw_sprite(&self, ctx: &mut FrameContext<'_, Board>, hover: Hover) {
584 let position = self.sprite.previous.lerp(self.sprite.position, ctx.alpha());
585 let current = self.turn == Turn::Sprite;
586 let (tint, glow) = if current && self.selected {
587 (SELECTED_TINT, SELECTED_GLOW)
588 } else if current && hover == Hover::CurrentUnit {
589 (HOVER_TINT, HOVER_GLOW)
590 } else if current {
591 (TURN_TINT, TURN_GLOW)
592 } else {
593 (Color::WHITE, Color::BLACK)
594 };
595 ctx.draw(
596 Sprite
597 .at(Transform::from_scale_rotation_translation(
598 Vec3::new(SPRITE_WIDTH, SPRITE_HEIGHT, 1.0),
599 Quat::IDENTITY,
600 position,
601 ))
602 .upright()
603 .frame(sprite_frame(self.sprite.facing_right))
604 .material(Material::lit(tint).cutout().emissive(glow)),
605 );
606 }
607
608 fn draw_block(&self, ctx: &mut FrameContext<'_, Board>, hover: Hover) {
609 let position = self.block.previous.lerp(self.block.position, ctx.alpha());
610 let current = self.turn == Turn::Block;
611 let (color, glow) = if current && self.selected {
612 (SELECTED_TINT, SELECTED_GLOW)
613 } else if current && hover == Hover::CurrentUnit {
614 (HOVER_TINT, HOVER_GLOW)
615 } else if current {
616 (BLOCK_TURN, TURN_GLOW)
617 } else {
618 (BLOCK_IDLE, Color::BLACK)
619 };
620 ctx.draw(
621 Cube.at(Transform::from_scale_rotation_translation(
622 Vec3::splat(BLOCK_SIZE),
623 Quat::IDENTITY,
624 position,
625 ))
626 .material(Material::lit(color).emissive(glow)),
627 );
628 }978 fn frame(&mut self, ctx: &mut FrameContext<'_, SoundCheck>) {
979 ctx.set_volume(self.master_volume);
980
981 let player = self.player_prev.lerp(self.player, ctx.alpha());
982 let ear = Vec3::new(player.x, EYE_HEIGHT, player.y);
983 let listener = View::look_at(ear, ear + Vec3::NEG_Z);
984 ctx.set_listener(listener);
985
986 ctx.set_camera(Self::camera(player));
987 ctx.set_skybox(Sky::Room);
988 ctx.set_bloom(0.2);
989 ctx.light(Light::directional(Vec3::new(-0.4, -1.0, -0.5), SUN_COLOR).shadow());
990
991 self.draw_room(ctx);
992 self.draw_sources(ctx);
993 self.draw_listener(ctx, listener);
994 self.draw_merge_markers(ctx);
995 self.draw_ring(ctx);
996
997 self.sustain_cues(ctx);
998 for (index, source) in self.sources.iter().enumerate() {
999 if source.enabled {
1000 ctx.sustain(source.cue().instance(index as u32));
1001 }
1002 }
1003 if self.merge_demo {
1004 ctx.sustain(Sound::Click.at(MERGE_POS_A).gain(MERGE_GAIN));
1005 ctx.sustain(Sound::Click.at(MERGE_POS_B).gain(MERGE_GAIN));
1006 }
1007 self.sustain_ring(ctx);
1008
1009 self.side_panel(ctx);
1010 let (play_once, play_many) = self.one_shot_panel(ctx);
1011
1012 if play_once {
1013 ctx.play(self.one_shot_cue());
1014 }
1015 if play_many {
1016 for _ in 0..32 {
1017 ctx.play(self.one_shot_cue());
1018 }
1019 }
1020 }995 fn frame(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
996 if matches!(self.phase, Phase::Serving | Phase::Playing) && ctx.pressed(Button::Pause) {
997 self.paused = !self.paused;
998 }
999
1000 ctx.set_volume(self.master_volume);
1001 self.sustain_music(ctx);
1002
1003 ctx.set_camera(Self::camera());
1004
1005 let brick_pulse = (self.brick_flash / BRICK_FLASH).clamp(0.0, 1.0);
1006 ctx.set_bloom((BLOOM_BASE + brick_pulse * BLOOM_PULSE_PEAK).clamp(0.0, 1.0));
1007
1008 let life_lost_t = (self.life_lost_flash / LIFE_LOST_FLASH).clamp(0.0, 1.0);
1009 ctx.set_exposure((1.0 - life_lost_t * EXPOSURE_DIP_DEPTH).clamp(0.0, 1.0));
1010
1011 // The tick moves nothing behind a menu, so a frame there draws the last
1012 // step whole rather than interpolating from the one before.
1013 let alpha = match self.phase {
1014 Phase::Serving | Phase::Playing if !self.paused => ctx.alpha(),
1015 _ => 1.0,
1016 };
1017 let paddle_x = self.paddle_prev_x.lerp(self.paddle_x, alpha);
1018 let ball_pos = self.ball_prev.lerp(self.ball_pos, alpha);
1019
1020 ctx.light(Light::point(ball_pos, BALL_GLOW, BALL_LIGHT_RANGE).shadow());
1021
1022 self.draw_court(ctx);
1023 self.draw_bricks(ctx);
1024 self.draw_sparks(ctx);
1025 self.draw_lives(ctx);
1026
1027 ctx.draw(
1028 Paddle
1029 .at(Transform::from_translation(Vec3::new(
1030 paddle_x,
1031 PADDLE_HALF_HEIGHT,
1032 PADDLE_Z,
1033 )))
1034 .material_of(PaddlePart::Face, self.paddle_face_material()),
1035 );
1036
1037 self.draw_trail(ctx, alpha);
1038 ctx.draw(
1039 Sphere { subdivisions: 2 }
1040 .at(Transform::from_scale_rotation_translation(
1041 Vec3::splat(BALL_RADIUS * 2.0),
1042 Quat::IDENTITY,
1043 ball_pos,
1044 ))
1045 .material(
1046 Material::color(BALL_GLOW)
1047 .emissive(BALL_EMISSIVE)
1048 .additive(),
1049 ),
1050 );
1051
1052 self.overlay(ctx);
1053 }920 fn frame(&mut self, ctx: &mut FrameContext<'_, Scene>) {
921 self.steer_camera(ctx);
922
923 let alpha = ctx.alpha();
924 let elf_pos = self.elf_prev.lerp(self.elf_pos, alpha);
925 let elf_height = self.elf_height_prev + (self.elf_height - self.elf_height_prev) * alpha;
926 let (butterfly_pos, butterfly_yaw) = butterfly_pose(ctx.elapsed().as_secs_f32());
927
928 let camera = orbit_camera(elf_pos, self.camera_yaw, self.camera_pitch);
929 ctx.set_camera(camera);
930 ctx.set_cursor(if self.holding {
931 Cursor::Held
932 } else {
933 Cursor::Arrow
934 });
935 ctx.set_skybox(Sky::Day);
936 ctx.set_exposure(3.0);
937 ctx.set_bloom(0.2);
938 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
939 ctx.light(
940 Light::point(
941 LAMP_POST_POSITION + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP * 0.5),
942 LAMP_LIGHT_COLOR,
943 LAMP_LIGHT_RANGE,
944 )
945 .shadow(),
946 );
947 ctx.light(
948 Light::spot(Spot {
949 position: SPOT_POSITION,
950 direction: SPOT_DIRECTION,
951 color: SPOT_COLOR,
952 range: SPOT_RANGE,
953 angle: SPOT_ANGLE,
954 })
955 .shadow(),
956 );
957 ctx.light(
958 Light::point(butterfly_pos, BUTTERFLY_LIGHT_COLOR, BUTTERFLY_LIGHT_RANGE).shadow(),
959 );
960
961 ctx.draw(
962 Plane
963 .at(Transform::from_scale(Vec3::new(
964 GROUND_SIZE,
965 1.0,
966 GROUND_SIZE,
967 )))
968 .material(Material::lit(GROUND_COLOR)),
969 );
970 for patch in HURT_PATCHES {
971 ctx.draw(
972 Plane
973 .at(Transform::from_scale_rotation_translation(
974 Vec3::splat(HURT_RADIUS * 2.0),
975 Quat::IDENTITY,
976 patch,
977 ))
978 .material(Material::lit(HURT_COLOR)),
979 );
980 }
981 ctx.draw(
982 Cube.at(Transform::from_scale_rotation_translation(
983 Vec3::new(SEAT_FOOTPRINT, SEAT_HEIGHT, SEAT_FOOTPRINT),
984 Quat::IDENTITY,
985 SEAT_POSITION + Vec3::Y * SEAT_HEIGHT * 0.5,
986 ))
987 .material(Material::lit(SEAT_COLOR)),
988 );
989 ctx.draw(
990 Cube.at(Transform::from_scale_rotation_translation(
991 Vec3::new(LAMP_POST_THICKNESS, LAMP_POST_HEIGHT, LAMP_POST_THICKNESS),
992 Quat::IDENTITY,
993 LAMP_POST_POSITION + Vec3::Y * LAMP_POST_HEIGHT * 0.5,
994 ))
995 .material(Material::lit(LAMP_POST_COLOR)),
996 );
997 ctx.draw(
998 Cube.at(Transform::from_scale_rotation_translation(
999 Vec3::splat(LAMP_HEAD_SIZE),
1000 Quat::IDENTITY,
1001 LAMP_POST_POSITION
1002 + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP + LAMP_HEAD_SIZE * 0.5),
1003 ))
1004 .material(Material::color(Color::BLACK).emissive(LAMP_LIGHT_COLOR)),
1005 );
1006 ctx.draw(
1007 Cube.at(Transform::from_scale_rotation_translation(
1008 Vec3::splat(SPOT_FIXTURE_SIZE),
1009 Quat::IDENTITY,
1010 SPOT_POSITION + Vec3::Y * SPOT_FIXTURE_SIZE * 0.5,
1011 ))
1012 .material(Material::lit(SPOT_FIXTURE_COLOR)),
1013 );
1014
1015 ctx.draw(
1016 Elf.at(Transform::from_rotation_translation(
1017 Quat::from_rotation_y(self.elf_yaw),
1018 elf_pos + Vec3::Y * elf_height,
1019 ))
1020 .posed(&self.elf_animator),
1021 );
1022 ctx.draw(
1023 Elf.at(Transform::from_rotation_translation(
1024 Quat::from_rotation_y(core::f32::consts::PI),
1025 SCRUBBED_ELF_POSITION,
1026 ))
1027 .posed(&self.scrubbed_animator),
1028 );
1029 ctx.draw(
1030 Butterfly
1031 .at(Transform::from_rotation_translation(
1032 Quat::from_rotation_y(butterfly_yaw),
1033 butterfly_pos,
1034 ))
1035 .posed(&self.butterfly_animator)
1036 .material(Material::lit(Color::WHITE).emissive(BUTTERFLY_EMISSIVE)),
1037 );
1038
1039 self.draw_prompts(ctx, camera);
1040 self.panel(ctx);
1041 }Sourcepub fn set_tick_interval(&mut self, interval: Duration)
pub fn set_tick_interval(&mut self, interval: Duration)
Sets the simulated time every later tick covers, from the next frame
on; held to at least Duration::from_micros(1).
Required if you want to pace the simulation against something outside the engine, such as a program on another machine. The ticks a frame already runs keep the step they started with.
Sourcepub fn close(&mut self)
pub fn close(&mut self)
Ends the run once this frame is drawn: what the frame saved is written, and no tick or frame runs after it.
On the desktop the window closes and run returns. In
the browser there is no program to end: the loop stops, and the
canvas the engine created is dropped from the page, while one the
game named through Config::with_canvas_id stays as the page left
it.
Examples found in repository?
765 fn menu(&mut self, ctx: &mut FrameContext<'_, Breakout>, title: &str, over: bool) {
766 let mut clicked = false;
767 let mut quit = false;
768
769 // `ctx.ui` cannot borrow `ctx`, so anything the controls list needs is
770 // read first and applied after.
771 let buttons: Vec<(Button, String)> = Button::all()
772 .into_iter()
773 .map(|action| (action, bindings_text(ctx.bindings(action))))
774 .collect();
775 let axes: Vec<(Move, String)> = Move::all()
776 .into_iter()
777 .map(|action| (action, bindings_text(ctx.bindings(action))))
778 .collect();
779 let listening = self.listening;
780 let actuated_button = (!ctx.ui_wants_keyboard())
781 .then(|| ctx.actuated_button())
782 .flatten();
783 let actuated_axis = (!ctx.ui_wants_keyboard())
784 .then(|| ctx.actuated_axis())
785 .flatten();
786 let mut reset = None;
787
788 ctx.ui(|ui| {
789 egui::Window::new(title)
790 .collapsible(false)
791 .resizable(false)
792 .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
793 .show(ui.ctx(), |ui| {
794 if over {
795 ui.label(format!("score {}", self.score));
796 }
797 if !over {
798 ui.add(
799 egui::Slider::new(&mut self.master_volume, 0.0..=1.0).text("volume"),
800 );
801 if ui.button("resume").clicked() {
802 self.paused = false;
803 clicked = true;
804 }
805 ui.separator();
806 ui.heading("controls");
807 for (action, text) in &buttons {
808 controls_row(
809 ui,
810 action.name(),
811 text,
812 listening == Some(Listening::Button(*action)),
813 &mut self.listening,
814 Listening::Button(*action),
815 &mut reset,
816 );
817 }
818 for (action, text) in &axes {
819 controls_row(
820 ui,
821 action.name(),
822 text,
823 listening == Some(Listening::Move(*action)),
824 &mut self.listening,
825 Listening::Move(*action),
826 &mut reset,
827 );
828 }
829 }
830 if ui.button("restart").clicked() {
831 self.restart();
832 clicked = true;
833 }
834 if ui.button("quit").clicked() {
835 quit = true;
836 }
837 });
838 });
839
840 match (self.listening, actuated_button, actuated_axis) {
841 (Some(Listening::Button(action)), Some(binding), _) => {
842 ctx.rebind(action, vec![binding]);
843 self.listening = None;
844 }
845 (Some(Listening::Move(action)), _, Some(binding)) => {
846 ctx.rebind(action, vec![binding]);
847 self.listening = None;
848 }
849 _ => {}
850 }
851 match reset {
852 Some(Listening::Button(action)) => ctx.rebind(action, action.bindings()),
853 Some(Listening::Move(action)) => ctx.rebind(action, action.bindings()),
854 None => {}
855 }
856
857 if clicked {
858 ctx.play(Sound::Click);
859 }
860 if quit {
861 ctx.close();
862 }
863 }Sourcepub fn set_camera(&mut self, camera: Camera)
pub fn set_camera(&mut self, camera: Camera)
Views the rest of the frame from camera. A later call in the same
frame replaces this one; without any, Camera::default is used.
Examples found in repository?
More examples
1151 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
1152 self.fly_camera(ctx);
1153 ctx.set_camera(self.camera());
1154 ctx.set_skybox(self.sky);
1155 for light in self.lights() {
1156 ctx.light(light);
1157 }
1158 ctx.set_exposure(self.exposure);
1159 ctx.set_bloom(self.bloom);
1160
1161 self.draw_scene(ctx);
1162 self.controls(ctx);
1163 }741 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
742 self.apply_settings();
743
744 let center = Vec3::from(self.butterflies.center());
745 let elapsed = ctx.elapsed().as_secs_f32();
746 ctx.set_camera(Self::camera(center, self.world, elapsed));
747 ctx.set_skybox(Sky::Day);
748 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
749
750 Self::draw_ground(ctx);
751 self.draw_butterflies(ctx);
752 self.panel(ctx);
753 }1722 fn frame_overworld(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1723 let drawn_at = self.previous.lerp(self.position, ctx.alpha());
1724 ctx.set_camera(Self::camera(drawn_at, OVERWORLD_CAMERA_OFFSET));
1725 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
1726
1727 self.draw_ground(ctx);
1728 self.draw_hedgerow(ctx);
1729 self.draw_pond(ctx);
1730 self.draw_crates(ctx);
1731 self.draw_well(ctx);
1732 self.draw_flora(ctx);
1733 Self::draw_mouth(ctx, ENTRANCE);
1734 self.draw_walker(ctx, drawn_at);
1735 }
1736
1737 fn frame_cave(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1738 let drawn_at = self.previous.lerp(self.position, ctx.alpha());
1739 let camera = Self::camera(drawn_at, CAVE_CAMERA_OFFSET);
1740 ctx.set_camera(camera);
1741
1742 self.draw_cave_floor(ctx);
1743 self.draw_cave_walls(ctx);
1744 Self::draw_door_wall(ctx, (self.ghost > 0.0).then_some(drawn_at.x), self.ghost);
1745 Self::draw_mouth(ctx, EXIT);
1746 self.draw_torches(ctx);
1747 self.draw_door(ctx, self.ghost);
1748 self.draw_door_frame(ctx, self.ghost);
1749 if !self.gem_taken {
1750 self.draw_gem(ctx);
1751 }
1752 self.draw_walker(ctx, drawn_at);
1753 self.draw_door_prompt(ctx, camera);
1754 }819 fn frame(&mut self, ctx: &mut FrameContext<'_, Board>) {
820 let camera = Self::camera();
821 ctx.set_camera(camera);
822 ctx.set_skybox(Sky::Day);
823 ctx.light(Light::directional(Vec3::new(-0.35, -1.0, -0.5), SUN_COLOR).shadow());
824 ctx.set_bloom(BLOOM);
825
826 let hover = self.hovered(ctx);
827 self.draw_ground(ctx);
828 self.draw_board(ctx, hover);
829 self.draw_current_mark(ctx);
830 self.draw_rocks(ctx);
831 self.draw_sprite(ctx, hover);
832 self.draw_block(ctx, hover);
833 self.draw_prompt(ctx, camera, hover);
834
835 self.overlay(ctx);
836 }602 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
603 self.apply_settings();
604 self.frame_times.record(ctx.dt());
605
606 let elapsed = ctx.elapsed().as_secs_f32();
607 self.handle_camera(ctx, elapsed);
608 let camera = self.camera(elapsed);
609 ctx.set_camera(camera);
610 ctx.set_skybox(Sky::Day);
611
612 let sun = Light::directional(SUN_DIRECTION, SUN_COLOR);
613 ctx.light(if self.settings.sun_shadow {
614 sun.shadow()
615 } else {
616 sun
617 });
618
619 Self::draw_ground(ctx);
620 self.draw_field(ctx, elapsed);
621 self.controls(ctx, &camera);
622 }Sourcepub fn draw<M>(&mut self, instance: Instance<M, G::SurfaceStyles>)
pub fn draw<M>(&mut self, instance: Instance<M, G::SurfaceStyles>)
Draws instance this frame. The engine decides the order and which
draws share GPU work.
Takes a draw of a mesh of Game::Meshes and
no other.
Examples found in repository?
394 fn draw_ground(ctx: &mut FrameContext<'_, Self>) {
395 let side = (FIELD_RADIUS + FIELD_INNER_RADIUS) * 2.2;
396 ctx.draw(
397 Plane
398 .at(Transform::from_scale(Vec3::new(side, 1.0, side)))
399 .material(Material::lit(GROUND_COLOR).roughness(0.9)),
400 );
401 }
402
403 fn draw_field(&self, ctx: &mut FrameContext<'_, Self>, elapsed: f32) {
404 for entry in &self.field {
405 let yaw = if self.settings.moving && entry.moving {
406 entry.phase + elapsed * MOVING_SPEED
407 } else {
408 entry.phase
409 };
410 ctx.draw(
411 Rock { seed: entry.seed }.at(Transform::from_scale_rotation_translation(
412 Vec3::ONE,
413 Quat::from_rotation_y(yaw),
414 entry.position,
415 )),
416 );
417 }
418 }More examples
660 fn draw_ground(ctx: &mut FrameContext<'_, Self>) {
661 ctx.draw(
662 Plane
663 .at(Transform::from_scale(Vec3::new(
664 GROUND_SIZE,
665 1.0,
666 GROUND_SIZE,
667 )))
668 .material(Material::lit(GROUND_COLOR).roughness(0.9)),
669 );
670 }
671
672 /// Every butterfly at its own scale, turned to face its velocity, posed
673 /// by the flap machine of its own group and tinted its own color.
674 fn draw_butterflies(&self, ctx: &mut FrameContext<'_, Self>) {
675 for (position, velocity, kind) in self.butterflies.each() {
676 let rotation = Quat::from_rotation_arc(Vec3::Z, Vec3::from(velocity).normalize());
677 ctx.draw(
678 Butterfly
679 .at(Transform::from_scale_rotation_translation(
680 Vec3::splat(BUTTERFLY_SCALE),
681 rotation,
682 Vec3::from(position),
683 ))
684 .posed(&self.flaps[usize::from(kind.flap)])
685 .material(Material::lit(TINTS[usize::from(kind.tint)])),
686 );
687 }
688 }1236 fn draw_ground(&self, ctx: &mut FrameContext<'_, Keep>) {
1237 for col in -GROUND_DRAW_HALF..=GROUND_DRAW_HALF {
1238 for row in -GROUND_DRAW_HALF..=GROUND_DRAW_HALF {
1239 ctx.draw(
1240 Ground
1241 .at(Vec3::new(
1242 col as f32 * TILE_SIZE,
1243 0.0,
1244 row as f32 * TILE_SIZE,
1245 ))
1246 .frame(ground_cell(col, row)),
1247 );
1248 }
1249 }
1250 }
1251
1252 /// Two staggered rows of bushes around the clearing, open where the path
1253 /// leaves it, drawn between the camera and the ground's edge. The rows
1254 /// running along `Z` skip their two ends, which the rows running along
1255 /// `X` already cover.
1256 fn draw_hedgerow(&self, ctx: &mut FrameContext<'_, Keep>) {
1257 for (row, half) in [HEDGE_INNER_HALF, HEDGE_OUTER_HALF].into_iter().enumerate() {
1258 let row = row as i32;
1259 // The inner row covers both corners; the outer one is half a
1260 // span in from each, backing the gaps the inner row leaves.
1261 let spans = ((2.0 * half / HEDGE_STEP).round() as i32).max(1);
1262 let span = 2.0 * half / spans as f32;
1263 let steps = spans - row;
1264 for step in 0..=steps {
1265 let along = -half + (step as f32 + 0.5 * row as f32) * span;
1266 let scale = if (step + row) % 2 == 0 { 1.0 } else { 0.8 };
1267 let (width, height) = (BUSH_WIDTH * scale, BUSH_HEIGHT * scale);
1268 // The path leaves through the rows running along `X`, so only
1269 // those two open around it.
1270 let gated = along.abs() < HEDGE_GATE_HALF;
1271 let corner = step == 0 || step == steps;
1272 let places = [
1273 (along, -half, gated),
1274 (along, half, gated),
1275 (-half, along, corner),
1276 (half, along, corner),
1277 ];
1278 for (x, z, skip) in places {
1279 if skip {
1280 continue;
1281 }
1282 ctx.draw(
1283 Bush.at(Transform::from_scale_rotation_translation(
1284 Vec3::new(width, height, width),
1285 Quat::IDENTITY,
1286 Vec3::new(x, height * 0.5, z),
1287 ))
1288 .upright(),
1289 );
1290 }
1291 }
1292 }
1293 }
1294
1295 /// The pond: a square of styled water, and the shoreline sprite laid over
1296 /// it, which rings the open middle and hides the water's own edges.
1297 fn draw_pond(&self, ctx: &mut FrameContext<'_, Keep>) {
1298 ctx.draw(
1299 Plane
1300 .at(Transform::from_scale_rotation_translation(
1301 Vec3::splat(POND_WATER_HALF * 2.0),
1302 Quat::IDENTITY,
1303 POND_CENTER,
1304 ))
1305 .material(Material::shaded(WATER_COLOR, WATER_LITNESS))
1306 .surface_style::<Water>(),
1307 );
1308 ctx.draw(
1309 Shore
1310 .at(Transform::from_scale_rotation_translation(
1311 Vec3::splat(POND_HALF * 2.0),
1312 Quat::IDENTITY,
1313 Vec3::new(POND_CENTER.x, 0.0, POND_CENTER.z),
1314 ))
1315 .frame(Sheet::new(UVec2::new(POND_CELLS, 1)).cell(POND_SHORE_CELL)),
1316 );
1317 }
1318
1319 fn draw_crates(&self, ctx: &mut FrameContext<'_, Keep>) {
1320 for &(x, z, turn) in &CRATE_POSITIONS {
1321 ctx.draw(Crate.at(Transform::from_scale_rotation_translation(
1322 Vec3::splat(CRATE_SIZE),
1323 Quat::from_rotation_y(turn),
1324 Vec3::new(x, CRATE_SIZE * 0.5, z),
1325 )));
1326 }
1327 }
1328
1329 /// The well: its rim in grey masonry, and the mouth cell laid over the
1330 /// rim's top face.
1331 fn draw_well(&self, ctx: &mut FrameContext<'_, Keep>) {
1332 let cells = Sheet::new(UVec2::new(WELL_CELLS, 1));
1333 ctx.draw(
1334 Well.at(Transform::from_scale_rotation_translation(
1335 WELL_SIZE,
1336 Quat::IDENTITY,
1337 WELL_POSITION + Vec3::Y * (WELL_SIZE.y * 0.5),
1338 ))
1339 .frame(cells.cell(WELL_RIM_CELL)),
1340 );
1341 ctx.draw(
1342 WellMouth
1343 .at(Transform::from_scale_rotation_translation(
1344 Vec3::new(WELL_SIZE.x, 1.0, WELL_SIZE.z),
1345 Quat::IDENTITY,
1346 WELL_POSITION + Vec3::Y * (WELL_SIZE.y + WELL_MOUTH_LIFT),
1347 ))
1348 .frame(cells.cell(WELL_MOUTH_CELL)),
1349 );
1350 }
1351
1352 fn draw_flora(&self, ctx: &mut FrameContext<'_, Keep>) {
1353 for &(x, z, rock) in &FLORA {
1354 let (width, height) = if rock {
1355 (ROCK_WIDTH, ROCK_HEIGHT)
1356 } else {
1357 (BUSH_WIDTH, BUSH_HEIGHT)
1358 };
1359 let standing = Transform::from_scale_rotation_translation(
1360 Vec3::new(width, height, width),
1361 Quat::IDENTITY,
1362 Vec3::new(x, height * 0.5, z),
1363 );
1364 let flora: Instance<Shape, _> = if rock {
1365 Rock.at(standing).into_set()
1366 } else {
1367 Bush.at(standing).into_set()
1368 };
1369 ctx.draw(flora.upright());
1370 }
1371 }
1372
1373 /// One stone box drawn on the ground at `at`, `size` across, sampling
1374 /// the part of the sheet `frame` covers.
1375 fn draw_stone(ctx: &mut FrameContext<'_, Keep>, at: Vec3, size: Vec3, frame: Frame) {
1376 ctx.draw(
1377 Stone
1378 .at(Transform::from_scale_rotation_translation(
1379 size,
1380 Quat::IDENTITY,
1381 at + Vec3::Y * (size.y * 0.5),
1382 ))
1383 .frame(frame),
1384 );
1385 }
1386
1387 /// Two stone pillars drawn where `mouth` blocks the player, each a
1388 /// capital over its own course of masonry, and, on the one the camera
1389 /// looks into, the lintel across their tops and the dark filling
1390 /// the opening under it.
1391 fn draw_mouth(ctx: &mut FrameContext<'_, Keep>, mouth: Mouth) {
1392 for at in mouth.pillars() {
1393 Self::draw_stone(ctx, at, MOUTH_PILLAR_SIZE, Frame::default());
1394 }
1395 if !mouth.looked_into() {
1396 return;
1397 }
1398
1399 Self::draw_stone(
1400 ctx,
1401 mouth.at + Vec3::Y * MOUTH_PILLAR_SIZE.y,
1402 MOUTH_LINTEL_SIZE,
1403 masonry(MOUTH_LINTEL_TILES),
1404 );
1405 ctx.draw(
1406 Quad.at(Transform::from_scale_rotation_translation(
1407 Vec3::new(MOUTH_PILLAR_OFFSET * 2.0, MOUTH_DARK_HEIGHT, 1.0),
1408 Quat::IDENTITY,
1409 mouth.at + Vec3::Y * (MOUTH_DARK_HEIGHT * 0.5),
1410 ))
1411 .material(Material::color(Color::BLACK)),
1412 );
1413 }
1414
1415 fn draw_cave_floor(&self, ctx: &mut FrameContext<'_, Keep>) {
1416 let half = CAVE_HALF_WIDTH as i32;
1417 let near = CAVE_NEAR_Z as i32;
1418 let far = CAVE_FAR_Z as i32;
1419 for col in -half..=half {
1420 for row in far..=near {
1421 let variant = (col * 13 + row * 7).rem_euclid(CAVE_COLUMNS as i32) as u32;
1422 ctx.draw(
1423 CaveFloor
1424 .at(Vec3::new(
1425 col as f32 * TILE_SIZE,
1426 0.0,
1427 row as f32 * TILE_SIZE,
1428 ))
1429 .frame(
1430 Sheet::new(UVec2::new(CAVE_COLUMNS, CAVE_ROWS))
1431 .cell_at(UVec2::new(variant, CAVE_FLOOR_ROW)),
1432 ),
1433 );
1434 }
1435 }
1436 }
1437
1438 /// The wall drawn at `at` over the meters `standing`, in courses
1439 /// [`WALL_HEIGHT`] tall from the floor up, each cut to the part of it the
1440 /// span leaves; its faces are picked by `seed` and its stone faded to
1441 /// `fade`, which is `1.0` wherever it is solid.
1442 fn draw_wall(
1443 ctx: &mut FrameContext<'_, Keep>,
1444 at: Vec2,
1445 standing: Range<f32>,
1446 seed: i32,
1447 fade: f32,
1448 ) {
1449 for course in 0..WALL_COURSES {
1450 let base = course as f32 * WALL_HEIGHT;
1451 let low = (standing.start - base).max(0.0);
1452 let high = (standing.end - base).min(WALL_HEIGHT);
1453 if high <= low {
1454 continue;
1455 }
1456
1457 let variant = (seed + course).rem_euclid(CAVE_COLUMNS as i32) as u32;
1458 ctx.draw(
1459 CaveWall
1460 .at(Transform::from_scale_rotation_translation(
1461 Vec3::new(TILE_SIZE, high - low, TILE_SIZE),
1462 Quat::IDENTITY,
1463 Vec3::new(at.x, base + (low + high) * 0.5, at.y),
1464 ))
1465 .frame(cave_wall_face(variant, low..high))
1466 .faded(fade),
1467 );
1468 }
1469 }
1470
1471 /// The room's two side walls and its back wall, full height, and the low
1472 /// wall closing its near end between the side walls and the mouth. The
1473 /// back wall stops short of the corners the side walls already fill, and
1474 /// the near one leaves the mouth's own tile open.
1475 fn draw_cave_walls(&self, ctx: &mut FrameContext<'_, Keep>) {
1476 let half = CAVE_HALF_WIDTH as i32 + 1;
1477 let near = CAVE_NEAR_Z as i32;
1478 let far = CAVE_FAR_Z as i32;
1479
1480 for row in far..=near {
1481 let z = row as f32 * TILE_SIZE;
1482 let west = Vec2::new(-half as f32 * TILE_SIZE, z);
1483 let east = Vec2::new(half as f32 * TILE_SIZE, z);
1484 Self::draw_wall(ctx, west, 0.0..WALL_TOP, row * 5, SOLID);
1485 Self::draw_wall(ctx, east, 0.0..WALL_TOP, row * 5 + 1, SOLID);
1486 }
1487 for col in (-half + 1)..half {
1488 let x = col as f32 * TILE_SIZE;
1489 let back = Vec2::new(x, far as f32 * TILE_SIZE);
1490 Self::draw_wall(ctx, back, 0.0..WALL_TOP, col * 5 + 2, SOLID);
1491 if col != 0 {
1492 let lip = Vec2::new(x, CAVE_LIP_Z);
1493 Self::draw_wall(ctx, lip, 0.0..CAVE_LIP_HEIGHT, col * 5 + 4, SOLID);
1494 }
1495 }
1496 }
1497
1498 /// The wall the door hangs in, run across the room between the side walls
1499 /// with one tile left open on the room's axis for the doorway and stone
1500 /// filling the column over the door. A player behind the wall is drawn
1501 /// through the stacks between them and the camera, at `seen_through`,
1502 /// faded by `ghost`; the rest of it stays solid, and keeps casting.
1503 fn draw_door_wall(ctx: &mut FrameContext<'_, Keep>, seen_through: Option<f32>, ghost: f32) {
1504 let stone = |x: f32| match seen_through {
1505 Some(at) if (x - at).abs() < GHOST_CORRIDOR_HALF => ghost_alpha(ghost),
1506 _ => SOLID,
1507 };
1508 let half = CAVE_HALF_WIDTH as i32;
1509
1510 for col in (-half..=half).filter(|&col| col != 0) {
1511 let x = col as f32 * TILE_SIZE;
1512 Self::draw_wall(
1513 ctx,
1514 Vec2::new(x, DOOR_Z),
1515 0.0..WALL_TOP,
1516 col * 5 + 3,
1517 stone(x),
1518 );
1519 }
1520 Self::draw_wall(
1521 ctx,
1522 Vec2::new(0.0, DOOR_Z),
1523 DOOR_HEIGHT..WALL_TOP,
1524 3,
1525 stone(0.0),
1526 );
1527 }
1528
1529 /// The two torches: an upright cutout post apiece, the flame's loop
1530 /// burning over its binding, and the light that flame casts.
1531 fn draw_torches(&self, ctx: &mut FrameContext<'_, Keep>) {
1532 let elapsed = self.simulated.as_secs_f32();
1533 let loop_cells = Sheet::new(UVec2::new(FLAME_CELLS, 1));
1534
1535 for (index, &(x, z)) in TORCH_POSITIONS.iter().enumerate() {
1536 let base = Vec3::new(x, 0.0, z);
1537 ctx.draw(
1538 Torch
1539 .at(Transform::from_scale_rotation_translation(
1540 Vec3::new(TORCH_SPRITE_WIDTH, TORCH_STAND_HEIGHT, 1.0),
1541 Quat::IDENTITY,
1542 base + Vec3::Y * (TORCH_STAND_HEIGHT * 0.5),
1543 ))
1544 .upright(),
1545 );
1546
1547 let phase = index as f32 * 2.1;
1548 let flicker = (elapsed * FLAME_FLICKER_SPEED + phase).sin();
1549 let flame_pos =
1550 base + Vec3::Y * (TORCH_STAND_HEIGHT + FLAME_LIFT + flicker * FLAME_BOB);
1551
1552 let light_pos = flame_pos + Vec3::new(0.0, TORCH_LIGHT_LIFT, TORCH_LIGHT_STANDOFF);
1553 ctx.light(Light::point(light_pos, TORCH_LIGHT_COLOR, TORCH_LIGHT_RANGE).shadow());
1554 // The pair burn an even share of the loop apart.
1555 let offset = index as u32 * FLAME_CELLS / TORCH_POSITIONS.len() as u32;
1556 ctx.draw(
1557 Flame
1558 .at(Transform::from_scale_rotation_translation(
1559 Vec3::splat(FLAME_SIZE),
1560 Quat::IDENTITY,
1561 flame_pos,
1562 ))
1563 .billboard()
1564 .roll(flicker * FLAME_ROLL)
1565 .frame(loop_cells.cell((elapsed * FLAME_RATE) as u32 + offset)),
1566 );
1567 }
1568 }
1569
1570 /// The door at its hinge — swung back against the wall once opened —
1571 /// drawn through alongside its wall, faded by `ghost`.
1572 fn draw_door(&self, ctx: &mut FrameContext<'_, Keep>, ghost: f32) {
1573 let fade = ghost_alpha(ghost);
1574 let swung = if self.door_opening {
1575 Quat::from_rotation_y(core::f32::consts::FRAC_PI_2)
1576 } else {
1577 Quat::IDENTITY
1578 };
1579
1580 ctx.draw(
1581 Door.at(Transform::from_rotation_translation(swung, DOOR_HINGE))
1582 .material(Material::shaded(DOOR_COLOR, DOOR_LITNESS))
1583 .faded(fade),
1584 );
1585 }
1586
1587 /// The posts and lintel framing the doorway, in a color the stone never
1588 /// is, standing clear of the wall so the opening reads as a door from
1589 /// across the chamber. Glowing of their own while the door is closed and
1590 /// within [`INTERACT_RADIUS`], the cue that it opens.
1591 fn draw_door_frame(&self, ctx: &mut FrameContext<'_, Keep>, ghost: f32) {
1592 let reachable =
1593 !self.door_opening && self.position.distance(INTERACT_POINT) < INTERACT_RADIUS;
1594 let material =
1595 Material::shaded(DOOR_FRAME_COLOR, DOOR_FRAME_LITNESS).emissive(if reachable {
1596 DOOR_FRAME_GLOW
1597 } else {
1598 Color::BLACK
1599 });
1600 let fade = ghost_alpha(ghost);
1601 let z = DOOR_WALL_NEAR_Z + DOOR_FRAME_STANDOFF;
1602 let jamb_height = DOOR_HEIGHT + DOOR_FRAME_THICKNESS;
1603
1604 for side in SIDES {
1605 ctx.draw(
1606 Cube.at(Transform::from_scale_rotation_translation(
1607 Vec3::new(DOOR_FRAME_THICKNESS, jamb_height, DOOR_FRAME_THICKNESS),
1608 Quat::IDENTITY,
1609 Vec3::new(
1610 side * (DOORWAY_HALF + DOOR_FRAME_THICKNESS * 0.5),
1611 jamb_height * 0.5,
1612 z,
1613 ),
1614 ))
1615 .material(material)
1616 .faded(fade),
1617 );
1618 }
1619 ctx.draw(
1620 Cube.at(Transform::from_scale_rotation_translation(
1621 Vec3::new(
1622 DOOR_WIDTH + DOOR_FRAME_THICKNESS * 2.0,
1623 DOOR_FRAME_THICKNESS,
1624 DOOR_FRAME_THICKNESS,
1625 ),
1626 Quat::IDENTITY,
1627 Vec3::new(0.0, DOOR_HEIGHT + DOOR_FRAME_THICKNESS * 0.5, z),
1628 ))
1629 .material(material)
1630 .faded(fade),
1631 );
1632 }
1633
1634 /// A world prompt over the door: what opens it while the player is
1635 /// within [`INTERACT_RADIUS`] and it is closed, and that it swings while
1636 /// it does; gone once it has swung [`DOOR_SWING_TICKS`]. Laid out and
1637 /// placed like `examples/animation.rs`'s own prompt.
1638 fn draw_door_prompt(&self, ctx: &mut FrameContext<'_, Keep>, camera: Camera) {
1639 let near = self.position.distance(INTERACT_POINT) < INTERACT_RADIUS;
1640 let swinging = self.door_opening && self.swing_ticks < DOOR_SWING_TICKS;
1641 let text = if swinging {
1642 "opening"
1643 } else if near && !self.door_opening {
1644 "e opens the door"
1645 } else {
1646 return;
1647 };
1648
1649 let galley = ctx.text_layout(text, egui::FontId::proportional(DOOR_PROMPT_SIZE));
1650 let point = INTERACT_POINT + Vec3::Y * (DOOR_HEIGHT + DOOR_PROMPT_LIFT);
1651 let window_size = ctx.window_size();
1652 let pixels_per_point = ctx.pixels_per_point();
1653 let Some(pixel) = camera.pixel_of(point, window_size) else {
1654 return;
1655 };
1656
1657 ctx.ui(|ui| {
1658 let painter = ui.painter();
1659 let at = logical(pixel, pixels_per_point);
1660 let ink = galley.mesh_bounds;
1661 let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
1662 let backdrop = egui::Rect::from_center_size(
1663 at,
1664 ink.size() + egui::Vec2::splat(DOOR_PROMPT_PADDING * 2.0),
1665 );
1666 painter.rect_filled(
1667 backdrop,
1668 DOOR_PROMPT_PADDING,
1669 egui::Color32::from_black_alpha(DOOR_PROMPT_BACKDROP),
1670 );
1671 painter.galley(pos, galley, DOOR_PROMPT_COLOR);
1672 });
1673 }
1674
1675 /// The gem, spinning and bobbing over the chamber's floor, and the light
1676 /// it casts over it.
1677 fn draw_gem(&self, ctx: &mut FrameContext<'_, Keep>) {
1678 let t = self.simulated.as_secs_f32();
1679 let bob = (t * 2.0).sin() * GEM_BOB_HEIGHT;
1680 ctx.light(
1681 Light::point(
1682 GEM_POSITION + Vec3::Y * (bob + GEM_LIGHT_LIFT),
1683 GEM_LIGHT_COLOR,
1684 GEM_LIGHT_RANGE,
1685 )
1686 .shadow(),
1687 );
1688 ctx.draw(
1689 Gem.at(Transform::from_scale_rotation_translation(
1690 Vec3::ONE,
1691 Quat::from_rotation_y(t * GEM_SPIN_SPEED),
1692 GEM_POSITION + Vec3::Y * bob,
1693 ))
1694 .material(Material::shaded(GEM_COLOR, 0.7).emissive(GEM_COLOR.dimmed(1.6))),
1695 );
1696 }
1697
1698 /// The player: upright so it always faces the camera about `+Y`,
1699 /// windowed to its facing's row and the walk cycle's current frame.
1700 fn draw_walker(&self, ctx: &mut FrameContext<'_, Keep>, ground: Vec3) {
1701 let step = if self.walk_ticks > 0 {
1702 (self.walk_ticks / TICKS_PER_WALK_FRAME) % WALKER_COLUMNS
1703 } else {
1704 0
1705 };
1706 let cell = Sheet::new(UVec2::new(WALKER_COLUMNS, WALKER_ROWS))
1707 .cell_at(UVec2::new(step, self.facing as u32));
1708 let size = Vec2::new(WALKER_WIDTH, WALKER_HEIGHT);
1709
1710 ctx.draw(
1711 Walker
1712 .at(Transform::from_scale_rotation_translation(
1713 size.extend(1.0),
1714 Quat::IDENTITY,
1715 ground + Vec3::Y * (WALKER_HEIGHT * 0.5),
1716 ))
1717 .upright()
1718 .frame(cell),
1719 );
1720 }657 fn draw_station(&self, ctx: &mut FrameContext<'_, Self>, station: StationKind) {
658 let look = station.look();
659 let center = station.center();
660 let front_offset =
661 STATION_SIZE.z * 0.5 - STATION_FRONT_SIZE.z * 0.5 + STATION_FRONT_OUTWARD;
662 let front = center - Vec3::new(0.0, 0.0, front_offset);
663 for (size, position, material) in [
664 (STATION_SIZE, center, Material::lit(look.color)),
665 (
666 STATION_FRONT_SIZE,
667 front,
668 Material::color(Color::BLACK).emissive(look.glow),
669 ),
670 ] {
671 ctx.draw(
672 Cube.at(Transform::from_scale_rotation_translation(
673 size,
674 Quat::IDENTITY,
675 position,
676 ))
677 .material(material),
678 );
679 }
680 }
681
682 fn draw_bracket(&self, ctx: &mut FrameContext<'_, Self>, camera: Camera, station: StationKind) {
683 let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
684 let window_size = ctx.window_size();
685 let Some(pixel) = camera.pixel_of(top, window_size) else {
686 return;
687 };
688 let at = logical(pixel, ctx.pixels_per_point());
689
690 let name = ctx.text_layout(station.look().name, egui::FontId::proportional(BODY_SIZE));
691 let (reading_text, number_text) = station.reading(self.elapsed.as_secs_f32());
692 let reading = ctx.text_layout(&reading_text, egui::FontId::monospace(BODY_SIZE));
693 let number = ctx.text_layout(
694 &number_text,
695 egui::FontId::new(NUMBER_SIZE, egui::FontFamily::Name(DISPLAY_FAMILY.into())),
696 );
697
698 ctx.ui(|ui| bracket(ui.painter(), at, name, reading, number));
699 }
700
701 /// A `Prompt` for `Trigger::Hail`, above every `StationKind` but
702 /// `hovered`: what a player presses to reach one, apart from a hover.
703 fn draw_prompts(
704 &self,
705 ctx: &mut FrameContext<'_, Self>,
706 camera: Camera,
707 hovered: Option<StationKind>,
708 ) {
709 let Some(binding) = ctx.bindings(Trigger::Hail).into_iter().next() else {
710 return;
711 };
712 let hint = prompt(&binding);
713 let glyph = ctx.text_layout(&hint.text(), egui::FontId::new(PROMPT_SIZE, hint.family()));
714 let window_size = ctx.window_size();
715 let pixels_per_point = ctx.pixels_per_point();
716
717 ctx.ui(|ui| {
718 let painter = ui.painter();
719 for station in StationKind::ALL {
720 if Some(station) == hovered {
721 continue;
722 }
723 let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
724 let Some(pixel) = camera.pixel_of(top, window_size) else {
725 continue;
726 };
727 let at = logical(pixel, pixels_per_point);
728 let at = egui::pos2(at.x, at.y - PROMPT_LIFT);
729 prompt_at(painter, at, glyph.clone());
730 }
731 });
732 }
733
734 /// The title, a line and the reading, each in a font this game loaded
735 /// rather than egui's own.
736 fn panel(&self, ctx: &mut FrameContext<'_, Self>) {
737 ctx.ui(|ui| {
738 ui.label(styled(
739 "a game's own fonts",
740 egui::FontId::proportional(HEADING_SIZE),
741 ));
742 ui.label(styled(
743 "drawn in Pixel Operator, the game's proportional font",
744 egui::FontId::proportional(BODY_SIZE),
745 ));
746 ui.label(styled(
747 "the readings above each station in Pixel Operator Mono",
748 egui::FontId::monospace(BODY_SIZE),
749 ));
750 });
751 }
752
753 fn draw_dialogue(&self, ctx: &mut FrameContext<'_, Self>) {
754 let Some(dialogue) = &self.dialogue else {
755 return;
756 };
757 let whole = ctx.text_layout(
758 dialogue.current_line(),
759 egui::FontId::proportional(BODY_SIZE),
760 );
761 let size = whole.size();
762 ctx.ui(|ui| dialogue.draw(ui, size));
763 }
764
765 /// The `StationKind` under the pointer, `None` while the UI holds it.
766 fn hovered(ctx: &FrameContext<'_, Self>) -> Option<StationKind> {
767 if ctx.ui_wants_pointer() {
768 return None;
769 }
770 hit_station(
771 ctx.last_camera()
772 .ray_through(ctx.pointer(), ctx.window_size()),
773 )
774 }
775
776 /// A held [`Trigger::Hail`] turns the camera by the pointer's own
777 /// motion; the wheel zooms it.
778 fn steer(&mut self, ctx: &mut FrameContext<'_, Self>) {
779 if !ctx.ui_wants_pointer() && ctx.down(Trigger::Hail) {
780 self.orbit.turn(ctx.axis2(Turn::Look));
781 }
782 let wheel = ctx.axis(Zoom::Wheel);
783 if !ctx.ui_wants_pointer() && wheel != 0.0 {
784 self.orbit.zoom(ZOOM_STEP.powf(wheel));
785 }
786 }
787}
788
789impl Game for WatchRoom {
790 type Meshes = Shape;
791 type Sounds = NoSounds;
792 type InputActions = Controls;
793 type Skyboxes = Sky;
794 type SurfaceStyles = NoSurfaceStyles;
795 type PostEffects = NoPostEffects;
796
797 fn tick(&mut self, ctx: &mut TickContext<'_, Self>) {
798 self.elapsed += ctx.dt();
799 self.orbit.yaw += AUTO_TURN_RATE * ctx.dt().as_secs_f32();
800
801 if let Some(dialogue) = &mut self.dialogue {
802 dialogue.tick();
803 }
804 if ctx.pressed(Trigger::Close) {
805 self.dialogue = None;
806 self.hailed = None;
807 }
808 if ctx.pressed(Trigger::Sheet) {
809 self.sheet_open = !self.sheet_open;
810 }
811 if ctx.pressed(Trigger::Hail) && !ctx.ui_wants_pointer() {
812 self.handle_hail(ctx);
813 }
814 }
815
816 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
817 self.steer(ctx);
818
819 let camera = self.orbit.camera();
820 ctx.set_camera(camera);
821 ctx.set_skybox(Sky::Dusk);
822 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
823
824 ctx.draw(
825 Plane
826 .at(Transform::from_scale(Vec3::new(
827 PLATFORM_SIZE,
828 1.0,
829 PLATFORM_SIZE,
830 )))
831 .material(Material::lit(PLATFORM_COLOR)),
832 );
833 for station in StationKind::ALL {
834 self.draw_station(ctx, station);
835 }
836
837 let hovered = Self::hovered(ctx);
838 if !self.sheet_open {
839 if let Some(station) = hovered {
840 ctx.set_cursor(Cursor::Pointer);
841 self.draw_bracket(ctx, camera, station);
842 }
843 self.draw_prompts(ctx, camera, hovered);
844 }
845 if self.dialogue.is_some() {
846 self.draw_dialogue(ctx);
847 }
848 if self.sheet_open {
849 ctx.ui(sheet);
850 }
851 self.panel(ctx);
852 }90 fn draw_scene(&self, ctx: &mut FrameContext<'_, Self>) {
91 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
92
93 ctx.draw(
94 Plane
95 .at(Transform::from_scale(Vec3::new(
96 GROUND_SIZE,
97 1.0,
98 GROUND_SIZE,
99 )))
100 .material(Material::lit(GROUND_COLOR)),
101 );
102 ctx.draw(
103 Cube.at(Transform::from_scale_rotation_translation(
104 Vec3::splat(GLOW_SIZE),
105 Quat::IDENTITY,
106 GLOW_POSITION,
107 ))
108 .material(Material::color(Color::BLACK).emissive(GLOW_COLOR)),
109 );
110 for position in SPHERE_POSITIONS {
111 ctx.draw(
112 Sphere {
113 subdivisions: SPHERE_SUBDIVISIONS,
114 }
115 .at(position)
116 .material(Material::lit(SPHERE_COLOR)),
117 );
118 }
119 }604 fn draw_court(&self, ctx: &mut FrameContext<'_, Breakout>) {
605 ctx.draw(
606 Plane
607 .at(Transform::from_scale(Vec3::new(
608 COURT_HALF_WIDTH * 2.0,
609 1.0,
610 COURT_HALF_DEPTH * 2.0,
611 )))
612 .material(Material::lit(FLOOR_COLOR)),
613 );
614
615 let side_half = Vec3::new(WALL_THICKNESS * 0.5, WALL_HEIGHT * 0.5, COURT_HALF_DEPTH);
616 for side in [-1.0, 1.0] {
617 let x = side * (COURT_HALF_WIDTH - WALL_THICKNESS * 0.5);
618 ctx.draw(
619 Cube.at(Transform::from_scale_rotation_translation(
620 side_half * 2.0,
621 Quat::IDENTITY,
622 Vec3::new(x, side_half.y, 0.0),
623 ))
624 .material(Material::lit(WALL_COLOR)),
625 );
626 }
627
628 let top_half = Vec3::new(COURT_HALF_WIDTH, WALL_HEIGHT * 0.5, WALL_THICKNESS * 0.5);
629 ctx.draw(
630 Cube.at(Transform::from_scale_rotation_translation(
631 top_half * 2.0,
632 Quat::IDENTITY,
633 Vec3::new(0.0, top_half.y, -COURT_HALF_DEPTH + WALL_THICKNESS * 0.5),
634 ))
635 .material(Material::lit(WALL_COLOR)),
636 );
637 }
638
639 fn draw_bricks(&self, ctx: &mut FrameContext<'_, Breakout>) {
640 let scale = Vec3::new(
641 BRICK_HALF_WIDTH * 2.0,
642 BRICK_HALF_HEIGHT * 2.0,
643 BRICK_HALF_DEPTH * 2.0,
644 );
645 for brick in self.bricks.iter().filter(|brick| brick.hits_remaining > 0) {
646 let health = f32::from(brick.hits_remaining) / f32::from(BRICK_HITS);
647 let color = BRICK_ROW_COLORS[brick.row].dimmed(0.4 + 0.6 * health);
648 ctx.draw(
649 Cube.at(Transform::from_scale_rotation_translation(
650 scale,
651 Quat::IDENTITY,
652 brick.position,
653 ))
654 .material(Material::shaded(color, health)),
655 );
656 }
657 }
658
659 /// Draws the live spark burst: additive, tumbling by roll as they age,
660 /// shrinking and fading out over their lifetime.
661 fn draw_sparks(&self, ctx: &mut FrameContext<'_, Breakout>) {
662 for spark in &self.sparks {
663 let age = (spark.age / SPARK_LIFETIME).clamp(0.0, 1.0);
664 let fade = 1.0 - age;
665 let size = SPARK_SIZE_START.lerp(SPARK_SIZE_END, age);
666 ctx.draw(
667 Quad.at(Transform::from_scale_rotation_translation(
668 Vec3::splat(size),
669 Quat::IDENTITY,
670 spark.position,
671 ))
672 .billboard()
673 .roll(spark.roll + spark.age * SPARK_SPIN_SPEED)
674 .material(
675 Material::color(spark.color.with_alpha(fade))
676 .emissive(spark.color.dimmed(SPARK_EMISSIVE_PEAK))
677 .additive(),
678 ),
679 );
680 }
681 }
682
683 /// Draws the ball's ghost trail, each ghost smaller and more transparent
684 /// than the one ahead of it; each ghost's position interpolates between
685 /// its own last two resolved ticks by the same `alpha` the ball itself
686 /// draws at, and its radius clamps to what the ball's own radius has
687 /// left over its distance from the head, so a ghost still close to the
688 /// ball never draws past its edge.
689 fn draw_trail(&self, ctx: &mut FrameContext<'_, Breakout>, alpha: f32) {
690 let head = self.ball_trail[1].lerp(self.ball_trail[0], alpha);
691 for i in 0..TRAIL_LEN {
692 let position = self.ball_trail[i + 1].lerp(self.ball_trail[i], alpha);
693 let age = (i + 1) as f32 / TRAIL_LEN as f32;
694 let fade = (1.0 - age).max(TRAIL_ALPHA_FLOOR);
695 let radius = (BALL_RADIUS * TRAIL_SCALE_MIN.lerp(TRAIL_SCALE_MAX, fade))
696 .min((BALL_RADIUS - head.distance(position)).max(0.0));
697 let scale = Vec3::splat(radius * 2.0);
698 ctx.draw(
699 Sphere { subdivisions: 2 }
700 .at(Transform::from_scale_rotation_translation(
701 scale,
702 Quat::IDENTITY,
703 position,
704 ))
705 .material(
706 Material::color(BALL_GLOW.with_alpha(fade))
707 .emissive(BALL_EMISSIVE.dimmed(TRAIL_EMISSIVE_PEAK)),
708 ),
709 );
710 }
711 }
712
713 /// Draws one held ball for every life past the one in play, set in a
714 /// row alongside the paddle's own path.
715 fn draw_lives(&self, ctx: &mut FrameContext<'_, Breakout>) {
716 let held_lives = self.lives.saturating_sub(1);
717 for slot in 0..held_lives {
718 let z = PADDLE_Z + (slot + 1) as f32 * LIFE_ROW_SPACING;
719 ctx.draw(
720 Sphere { subdivisions: 2 }
721 .at(Transform::from_scale_rotation_translation(
722 Vec3::splat(BALL_RADIUS * 2.0),
723 Quat::IDENTITY,
724 Vec3::new(LIFE_ROW_X, BALL_RADIUS, z),
725 ))
726 .material(
727 Material::color(BALL_GLOW)
728 .emissive(BALL_EMISSIVE)
729 .additive(),
730 ),
731 );
732 }
733 }
734
735 fn overlay(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
736 let bricks_left = self
737 .bricks
738 .iter()
739 .filter(|brick| brick.hits_remaining > 0)
740 .count();
741 // Read before `ctx.ui` so a rebind changes what the hint reads this
742 // frame too.
743 let move_hint = bindings_text(ctx.bindings(Move::Paddle));
744 let pause_hint = bindings_text(ctx.bindings(Button::Pause));
745 let serve_hint = bindings_text(ctx.bindings(Button::Serve));
746 ctx.ui(|ui| {
747 ui.horizontal(|ui| {
748 ui.label(egui::RichText::new(format!("score {}", self.score)).size(32.0));
749 ui.label(format!("{bricks_left} bricks left"));
750 });
751 ui.label(format!("move: {move_hint} · {pause_hint} to pause"));
752 if self.phase == Phase::Serving {
753 ui.label(format!("{serve_hint} to serve"));
754 }
755 });
756
757 match self.phase {
758 Phase::Serving | Phase::Playing if self.paused => self.menu(ctx, "paused", false),
759 Phase::Won => self.menu(ctx, "you win", true),
760 Phase::Lost => self.menu(ctx, "game over", true),
761 _ => {}
762 }
763 }
764
765 fn menu(&mut self, ctx: &mut FrameContext<'_, Breakout>, title: &str, over: bool) {
766 let mut clicked = false;
767 let mut quit = false;
768
769 // `ctx.ui` cannot borrow `ctx`, so anything the controls list needs is
770 // read first and applied after.
771 let buttons: Vec<(Button, String)> = Button::all()
772 .into_iter()
773 .map(|action| (action, bindings_text(ctx.bindings(action))))
774 .collect();
775 let axes: Vec<(Move, String)> = Move::all()
776 .into_iter()
777 .map(|action| (action, bindings_text(ctx.bindings(action))))
778 .collect();
779 let listening = self.listening;
780 let actuated_button = (!ctx.ui_wants_keyboard())
781 .then(|| ctx.actuated_button())
782 .flatten();
783 let actuated_axis = (!ctx.ui_wants_keyboard())
784 .then(|| ctx.actuated_axis())
785 .flatten();
786 let mut reset = None;
787
788 ctx.ui(|ui| {
789 egui::Window::new(title)
790 .collapsible(false)
791 .resizable(false)
792 .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
793 .show(ui.ctx(), |ui| {
794 if over {
795 ui.label(format!("score {}", self.score));
796 }
797 if !over {
798 ui.add(
799 egui::Slider::new(&mut self.master_volume, 0.0..=1.0).text("volume"),
800 );
801 if ui.button("resume").clicked() {
802 self.paused = false;
803 clicked = true;
804 }
805 ui.separator();
806 ui.heading("controls");
807 for (action, text) in &buttons {
808 controls_row(
809 ui,
810 action.name(),
811 text,
812 listening == Some(Listening::Button(*action)),
813 &mut self.listening,
814 Listening::Button(*action),
815 &mut reset,
816 );
817 }
818 for (action, text) in &axes {
819 controls_row(
820 ui,
821 action.name(),
822 text,
823 listening == Some(Listening::Move(*action)),
824 &mut self.listening,
825 Listening::Move(*action),
826 &mut reset,
827 );
828 }
829 }
830 if ui.button("restart").clicked() {
831 self.restart();
832 clicked = true;
833 }
834 if ui.button("quit").clicked() {
835 quit = true;
836 }
837 });
838 });
839
840 match (self.listening, actuated_button, actuated_axis) {
841 (Some(Listening::Button(action)), Some(binding), _) => {
842 ctx.rebind(action, vec![binding]);
843 self.listening = None;
844 }
845 (Some(Listening::Move(action)), _, Some(binding)) => {
846 ctx.rebind(action, vec![binding]);
847 self.listening = None;
848 }
849 _ => {}
850 }
851 match reset {
852 Some(Listening::Button(action)) => ctx.rebind(action, action.bindings()),
853 Some(Listening::Move(action)) => ctx.rebind(action, action.bindings()),
854 None => {}
855 }
856
857 if clicked {
858 ctx.play(Sound::Click);
859 }
860 if quit {
861 ctx.close();
862 }
863 }
864
865 /// Sustains both tracks every frame, and the gain goes to whichever the
866 /// game calls for: gameplay music while a round is live, serving
867 /// included, and menu music whenever a menu covers it.
868 ///
869 /// Each fades in over [`MUSIC_CROSSFADE`] and slides every later gain
870 /// over it, which is the crossfade itself; the one at no gain costs no
871 /// voice while its playback goes on under the other.
872 fn sustain_music(&self, ctx: &mut FrameContext<'_, Breakout>) {
873 let playing = !self.paused && matches!(self.phase, Phase::Serving | Phase::Playing);
874 let gain = |wanted: bool| match wanted {
875 true => MUSIC_GAIN,
876 false => 0.0,
877 };
878
879 ctx.sustain(
880 Sound::Music
881 .gain(gain(playing))
882 .fade(MUSIC_CROSSFADE)
883 .glide(MUSIC_CROSSFADE)
884 .loop_from(MUSIC_LOOP_FROM),
885 );
886 ctx.sustain(
887 Sound::MenuMusic
888 .gain(gain(!playing))
889 .fade(MUSIC_CROSSFADE)
890 .glide(MUSIC_CROSSFADE)
891 .loop_from(MENU_MUSIC_LOOP_FROM),
892 );
893 }
894}
895
896/// One action's name, its live bindings, a rebind control that starts
897/// listening for a new one, and a reset to its defaults; cancel is a
898/// button rather than Escape, since Escape is itself a binding a listen
899/// could capture.
900fn controls_row(
901 ui: &mut egui::Ui,
902 name: &str,
903 bindings: &str,
904 listening: bool,
905 target: &mut Option<Listening>,
906 action: Listening,
907 reset: &mut Option<Listening>,
908) {
909 ui.horizontal(|ui| {
910 ui.label(format!("{name}: {bindings}"));
911 if listening {
912 ui.label("listening");
913 if ui.button("cancel").clicked() {
914 *target = None;
915 }
916 } else if ui.button("rebind").clicked() {
917 *target = Some(action);
918 }
919 if ui.button("reset").clicked() {
920 *reset = Some(action);
921 }
922 });
923}
924
925/// The controls-menu text for a live binding list: each alternative,
926/// separated, in the order the player can use them.
927fn bindings_text<B: Display>(bindings: Vec<B>) -> String {
928 bindings
929 .iter()
930 .map(ToString::to_string)
931 .collect::<Vec<_>>()
932 .join(", ")
933}
934
935fn spawn_bricks() -> Vec<Brick> {
936 let cell = BRICK_HALF_WIDTH * 2.0 + BRICK_GAP;
937 let row_span = BRICK_HALF_DEPTH * 2.0 + BRICK_ROW_GAP;
938 let grid_width = cell * BRICK_COLUMNS as f32 - BRICK_GAP;
939 let start_x = -grid_width * 0.5 + BRICK_HALF_WIDTH;
940 let start_z = -COURT_HALF_DEPTH + WALL_THICKNESS + BRICK_HALF_DEPTH + 0.6;
941
942 (0..BRICK_ROWS)
943 .flat_map(|row| {
944 (0..BRICK_COLUMNS).map(move |column| Brick {
945 row,
946 position: Vec3::new(
947 start_x + column as f32 * cell,
948 BRICK_HALF_HEIGHT,
949 start_z + row as f32 * row_span,
950 ),
951 hits_remaining: BRICK_HITS,
952 })
953 })
954 .collect()
955}
956
957impl Game for Breakout {
958 type Meshes = Shape;
959 type Sounds = Sound;
960 type InputActions = Controls;
961 type Skyboxes = NoSkyboxes;
962 type SurfaceStyles = NoSurfaceStyles;
963 type PostEffects = NoPostEffects;
964
965 fn tick(&mut self, ctx: &mut TickContext<'_, Breakout>) {
966 if self.paused {
967 return;
968 }
969
970 let dt = ctx.dt().as_secs_f32();
971 self.paddle_flash = (self.paddle_flash - dt).max(0.0);
972 self.brick_flash = (self.brick_flash - dt).max(0.0);
973 self.life_lost_flash = (self.life_lost_flash - dt).max(0.0);
974 self.step_sparks(dt);
975
976 // Decay runs before the end-screen return below, so the last pulse and
977 // burst do not stay on screen.
978 if matches!(self.phase, Phase::Won | Phase::Lost) {
979 return;
980 }
981
982 let axis = if ctx.ui_wants_keyboard() {
983 0.0
984 } else {
985 ctx.axis(Move::Paddle)
986 };
987 self.step_paddle(axis, dt);
988
989 match self.phase {
990 Phase::Serving => self.hold_ball(ctx),
991 _ => self.step_ball(ctx, dt),
992 }
993 }
994
995 fn frame(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
996 if matches!(self.phase, Phase::Serving | Phase::Playing) && ctx.pressed(Button::Pause) {
997 self.paused = !self.paused;
998 }
999
1000 ctx.set_volume(self.master_volume);
1001 self.sustain_music(ctx);
1002
1003 ctx.set_camera(Self::camera());
1004
1005 let brick_pulse = (self.brick_flash / BRICK_FLASH).clamp(0.0, 1.0);
1006 ctx.set_bloom((BLOOM_BASE + brick_pulse * BLOOM_PULSE_PEAK).clamp(0.0, 1.0));
1007
1008 let life_lost_t = (self.life_lost_flash / LIFE_LOST_FLASH).clamp(0.0, 1.0);
1009 ctx.set_exposure((1.0 - life_lost_t * EXPOSURE_DIP_DEPTH).clamp(0.0, 1.0));
1010
1011 // The tick moves nothing behind a menu, so a frame there draws the last
1012 // step whole rather than interpolating from the one before.
1013 let alpha = match self.phase {
1014 Phase::Serving | Phase::Playing if !self.paused => ctx.alpha(),
1015 _ => 1.0,
1016 };
1017 let paddle_x = self.paddle_prev_x.lerp(self.paddle_x, alpha);
1018 let ball_pos = self.ball_prev.lerp(self.ball_pos, alpha);
1019
1020 ctx.light(Light::point(ball_pos, BALL_GLOW, BALL_LIGHT_RANGE).shadow());
1021
1022 self.draw_court(ctx);
1023 self.draw_bricks(ctx);
1024 self.draw_sparks(ctx);
1025 self.draw_lives(ctx);
1026
1027 ctx.draw(
1028 Paddle
1029 .at(Transform::from_translation(Vec3::new(
1030 paddle_x,
1031 PADDLE_HALF_HEIGHT,
1032 PADDLE_Z,
1033 )))
1034 .material_of(PaddlePart::Face, self.paddle_face_material()),
1035 );
1036
1037 self.draw_trail(ctx, alpha);
1038 ctx.draw(
1039 Sphere { subdivisions: 2 }
1040 .at(Transform::from_scale_rotation_translation(
1041 Vec3::splat(BALL_RADIUS * 2.0),
1042 Quat::IDENTITY,
1043 ball_pos,
1044 ))
1045 .material(
1046 Material::color(BALL_GLOW)
1047 .emissive(BALL_EMISSIVE)
1048 .additive(),
1049 ),
1050 );
1051
1052 self.overlay(ctx);
1053 }Sourcepub fn set_surface_style<T: SurfaceStyle>(&mut self, style: T)where
G::SurfaceStyles: Holds<T>,
pub fn set_surface_style<T: SurfaceStyle>(&mut self, style: T)where
G::SurfaceStyles: Holds<T>,
Passes the style T the values its WGSL reads this frame; the last
call for a style is the one it reads.
A frame that never calls this for a style leaves it reading the
default value of every field. Takes a style of
Game::SurfaceStyles and no other.
Examples found in repository?
1832 fn frame(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1833 ctx.set_surface_style(Water {
1834 time: self.simulated.as_secs_f32(),
1835 });
1836
1837 match self.area {
1838 Area::Overworld => self.frame_overworld(ctx),
1839 Area::Cave => self.frame_cave(ctx),
1840 }
1841
1842 self.overlay(ctx);
1843 }More examples
951 fn draw_outpost(&self, ctx: &mut FrameContext<'_, Self>) {
952 let clock = ctx.elapsed().as_secs_f32();
953
954 for &(position, scale) in &PILLARS {
955 ctx.draw(
956 Cube.at(Transform::from_scale_rotation_translation(
957 scale,
958 Quat::IDENTITY,
959 OUTPOST + position,
960 ))
961 .material(Material::lit(Color::rgb(0.55, 0.5, 0.45))),
962 );
963 }
964
965 ctx.draw(
966 Cube.at(Transform::from_scale_rotation_translation(
967 POLE_SCALE,
968 Quat::IDENTITY,
969 OUTPOST + POLE_POSITION,
970 ))
971 .material(Material::lit(Color::rgb(0.3, 0.24, 0.18))),
972 );
973
974 ctx.set_surface_style(Banner { time: clock });
975 ctx.draw(
976 BannerCloth
977 .at(Transform::from_translation(OUTPOST + BANNER_MOUNT))
978 .material(Material::lit(Color::rgb(0.75, 0.12, 0.12)))
979 .surface_style::<Banner>(),
980 );
981
982 ctx.set_surface_style(Field {
983 tint: Color::rgb(0.25, 0.75, 1.0),
984 time: clock,
985 });
986 ctx.draw(
987 Sphere { subdivisions: 2 }
988 .at(Transform::from_scale_rotation_translation(
989 Vec3::splat(FIELD_ORB_SCALE),
990 Quat::IDENTITY,
991 OUTPOST + FIELD_ORB_POSITION,
992 ))
993 .material(Material::color(Color::BLACK))
994 .surface_style::<Field>(),
995 );
996 }Sourcepub fn set_post_effect<T: PostEffect>(&mut self, effect: T)where
G::PostEffects: Holds<T>,
pub fn set_post_effect<T: PostEffect>(&mut self, effect: T)where
G::PostEffects: Holds<T>,
Runs the post effect T over this frame with the values its WGSL
reads; the last call for an effect is the one it runs with.
A frame that never calls this for an effect runs no pass for it.
Takes an effect of
Game::PostEffects and no other.
Examples found in repository?
124 fn panel(&mut self, ctx: &mut FrameContext<'_, Self>) {
125 ctx.ui(|ui| {
126 ui.add(egui::Slider::new(&mut self.vignette, 0.0..=1.0).text("vignette"));
127 ui.add(egui::Slider::new(&mut self.grain, 0.0..=1.0).text("grain"));
128 ui.add(egui::Slider::new(&mut self.scanlines, 0.0..=1.0).text("scanlines"));
129 });
130
131 ctx.set_post_effect(Vignette {
132 strength: self.vignette,
133 });
134 ctx.set_post_effect(Grain {
135 strength: self.grain,
136 seed: self.ticks,
137 });
138 ctx.set_post_effect(Scanlines {
139 strength: self.scanlines,
140 });
141 }Sourcepub fn set_skybox(&mut self, sky: G::Skyboxes)
pub fn set_skybox(&mut self, sky: G::Skyboxes)
Draws and lights this frame by sky: what it draws where nothing
else was drawn, and the ambient — the light every surface takes from
every direction.
Takes a value of Game::Skyboxes and no
other; startup built every one of them. The last call in a frame is
the one it draws, and a frame that never calls this draws and is lit
by the default sky.
Examples found in repository?
1151 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
1152 self.fly_camera(ctx);
1153 ctx.set_camera(self.camera());
1154 ctx.set_skybox(self.sky);
1155 for light in self.lights() {
1156 ctx.light(light);
1157 }
1158 ctx.set_exposure(self.exposure);
1159 ctx.set_bloom(self.bloom);
1160
1161 self.draw_scene(ctx);
1162 self.controls(ctx);
1163 }More examples
741 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
742 self.apply_settings();
743
744 let center = Vec3::from(self.butterflies.center());
745 let elapsed = ctx.elapsed().as_secs_f32();
746 ctx.set_camera(Self::camera(center, self.world, elapsed));
747 ctx.set_skybox(Sky::Day);
748 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
749
750 Self::draw_ground(ctx);
751 self.draw_butterflies(ctx);
752 self.panel(ctx);
753 }819 fn frame(&mut self, ctx: &mut FrameContext<'_, Board>) {
820 let camera = Self::camera();
821 ctx.set_camera(camera);
822 ctx.set_skybox(Sky::Day);
823 ctx.light(Light::directional(Vec3::new(-0.35, -1.0, -0.5), SUN_COLOR).shadow());
824 ctx.set_bloom(BLOOM);
825
826 let hover = self.hovered(ctx);
827 self.draw_ground(ctx);
828 self.draw_board(ctx, hover);
829 self.draw_current_mark(ctx);
830 self.draw_rocks(ctx);
831 self.draw_sprite(ctx, hover);
832 self.draw_block(ctx, hover);
833 self.draw_prompt(ctx, camera, hover);
834
835 self.overlay(ctx);
836 }602 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
603 self.apply_settings();
604 self.frame_times.record(ctx.dt());
605
606 let elapsed = ctx.elapsed().as_secs_f32();
607 self.handle_camera(ctx, elapsed);
608 let camera = self.camera(elapsed);
609 ctx.set_camera(camera);
610 ctx.set_skybox(Sky::Day);
611
612 let sun = Light::directional(SUN_DIRECTION, SUN_COLOR);
613 ctx.light(if self.settings.sun_shadow {
614 sun.shadow()
615 } else {
616 sun
617 });
618
619 Self::draw_ground(ctx);
620 self.draw_field(ctx, elapsed);
621 self.controls(ctx, &camera);
622 }816 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
817 self.steer(ctx);
818
819 let camera = self.orbit.camera();
820 ctx.set_camera(camera);
821 ctx.set_skybox(Sky::Dusk);
822 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
823
824 ctx.draw(
825 Plane
826 .at(Transform::from_scale(Vec3::new(
827 PLATFORM_SIZE,
828 1.0,
829 PLATFORM_SIZE,
830 )))
831 .material(Material::lit(PLATFORM_COLOR)),
832 );
833 for station in StationKind::ALL {
834 self.draw_station(ctx, station);
835 }
836
837 let hovered = Self::hovered(ctx);
838 if !self.sheet_open {
839 if let Some(station) = hovered {
840 ctx.set_cursor(Cursor::Pointer);
841 self.draw_bracket(ctx, camera, station);
842 }
843 self.draw_prompts(ctx, camera, hovered);
844 }
845 if self.dialogue.is_some() {
846 self.draw_dialogue(ctx);
847 }
848 if self.sheet_open {
849 ctx.ui(sheet);
850 }
851 self.panel(ctx);
852 }978 fn frame(&mut self, ctx: &mut FrameContext<'_, SoundCheck>) {
979 ctx.set_volume(self.master_volume);
980
981 let player = self.player_prev.lerp(self.player, ctx.alpha());
982 let ear = Vec3::new(player.x, EYE_HEIGHT, player.y);
983 let listener = View::look_at(ear, ear + Vec3::NEG_Z);
984 ctx.set_listener(listener);
985
986 ctx.set_camera(Self::camera(player));
987 ctx.set_skybox(Sky::Room);
988 ctx.set_bloom(0.2);
989 ctx.light(Light::directional(Vec3::new(-0.4, -1.0, -0.5), SUN_COLOR).shadow());
990
991 self.draw_room(ctx);
992 self.draw_sources(ctx);
993 self.draw_listener(ctx, listener);
994 self.draw_merge_markers(ctx);
995 self.draw_ring(ctx);
996
997 self.sustain_cues(ctx);
998 for (index, source) in self.sources.iter().enumerate() {
999 if source.enabled {
1000 ctx.sustain(source.cue().instance(index as u32));
1001 }
1002 }
1003 if self.merge_demo {
1004 ctx.sustain(Sound::Click.at(MERGE_POS_A).gain(MERGE_GAIN));
1005 ctx.sustain(Sound::Click.at(MERGE_POS_B).gain(MERGE_GAIN));
1006 }
1007 self.sustain_ring(ctx);
1008
1009 self.side_panel(ctx);
1010 let (play_once, play_many) = self.one_shot_panel(ctx);
1011
1012 if play_once {
1013 ctx.play(self.one_shot_cue());
1014 }
1015 if play_many {
1016 for _ in 0..32 {
1017 ctx.play(self.one_shot_cue());
1018 }
1019 }
1020 }Sourcepub fn light(&mut self, light: Light)
pub fn light(&mut self, light: Light)
Lights the frame with light, in addition to any already submitted.
The first call replaces the default environment’s light; past
MAX_LIGHTS, excess is ignored — warned the
first frame, a debug log after.
Examples found in repository?
1151 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
1152 self.fly_camera(ctx);
1153 ctx.set_camera(self.camera());
1154 ctx.set_skybox(self.sky);
1155 for light in self.lights() {
1156 ctx.light(light);
1157 }
1158 ctx.set_exposure(self.exposure);
1159 ctx.set_bloom(self.bloom);
1160
1161 self.draw_scene(ctx);
1162 self.controls(ctx);
1163 }More examples
741 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
742 self.apply_settings();
743
744 let center = Vec3::from(self.butterflies.center());
745 let elapsed = ctx.elapsed().as_secs_f32();
746 ctx.set_camera(Self::camera(center, self.world, elapsed));
747 ctx.set_skybox(Sky::Day);
748 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
749
750 Self::draw_ground(ctx);
751 self.draw_butterflies(ctx);
752 self.panel(ctx);
753 }819 fn frame(&mut self, ctx: &mut FrameContext<'_, Board>) {
820 let camera = Self::camera();
821 ctx.set_camera(camera);
822 ctx.set_skybox(Sky::Day);
823 ctx.light(Light::directional(Vec3::new(-0.35, -1.0, -0.5), SUN_COLOR).shadow());
824 ctx.set_bloom(BLOOM);
825
826 let hover = self.hovered(ctx);
827 self.draw_ground(ctx);
828 self.draw_board(ctx, hover);
829 self.draw_current_mark(ctx);
830 self.draw_rocks(ctx);
831 self.draw_sprite(ctx, hover);
832 self.draw_block(ctx, hover);
833 self.draw_prompt(ctx, camera, hover);
834
835 self.overlay(ctx);
836 }602 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
603 self.apply_settings();
604 self.frame_times.record(ctx.dt());
605
606 let elapsed = ctx.elapsed().as_secs_f32();
607 self.handle_camera(ctx, elapsed);
608 let camera = self.camera(elapsed);
609 ctx.set_camera(camera);
610 ctx.set_skybox(Sky::Day);
611
612 let sun = Light::directional(SUN_DIRECTION, SUN_COLOR);
613 ctx.light(if self.settings.sun_shadow {
614 sun.shadow()
615 } else {
616 sun
617 });
618
619 Self::draw_ground(ctx);
620 self.draw_field(ctx, elapsed);
621 self.controls(ctx, &camera);
622 }90 fn draw_scene(&self, ctx: &mut FrameContext<'_, Self>) {
91 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
92
93 ctx.draw(
94 Plane
95 .at(Transform::from_scale(Vec3::new(
96 GROUND_SIZE,
97 1.0,
98 GROUND_SIZE,
99 )))
100 .material(Material::lit(GROUND_COLOR)),
101 );
102 ctx.draw(
103 Cube.at(Transform::from_scale_rotation_translation(
104 Vec3::splat(GLOW_SIZE),
105 Quat::IDENTITY,
106 GLOW_POSITION,
107 ))
108 .material(Material::color(Color::BLACK).emissive(GLOW_COLOR)),
109 );
110 for position in SPHERE_POSITIONS {
111 ctx.draw(
112 Sphere {
113 subdivisions: SPHERE_SUBDIVISIONS,
114 }
115 .at(position)
116 .material(Material::lit(SPHERE_COLOR)),
117 );
118 }
119 }816 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
817 self.steer(ctx);
818
819 let camera = self.orbit.camera();
820 ctx.set_camera(camera);
821 ctx.set_skybox(Sky::Dusk);
822 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
823
824 ctx.draw(
825 Plane
826 .at(Transform::from_scale(Vec3::new(
827 PLATFORM_SIZE,
828 1.0,
829 PLATFORM_SIZE,
830 )))
831 .material(Material::lit(PLATFORM_COLOR)),
832 );
833 for station in StationKind::ALL {
834 self.draw_station(ctx, station);
835 }
836
837 let hovered = Self::hovered(ctx);
838 if !self.sheet_open {
839 if let Some(station) = hovered {
840 ctx.set_cursor(Cursor::Pointer);
841 self.draw_bracket(ctx, camera, station);
842 }
843 self.draw_prompts(ctx, camera, hovered);
844 }
845 if self.dialogue.is_some() {
846 self.draw_dialogue(ctx);
847 }
848 if self.sheet_open {
849 ctx.ui(sheet);
850 }
851 self.panel(ctx);
852 }Sourcepub fn set_exposure(&mut self, exposure: f32)
pub fn set_exposure(&mut self, exposure: f32)
Scales the frame’s light before the curve, as a fraction of it; a
value under 0.0 is clamped to it, and 1.0 is used where a frame
never calls this.
The last call in a frame replaces the rest.
Examples found in repository?
1151 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
1152 self.fly_camera(ctx);
1153 ctx.set_camera(self.camera());
1154 ctx.set_skybox(self.sky);
1155 for light in self.lights() {
1156 ctx.light(light);
1157 }
1158 ctx.set_exposure(self.exposure);
1159 ctx.set_bloom(self.bloom);
1160
1161 self.draw_scene(ctx);
1162 self.controls(ctx);
1163 }More examples
995 fn frame(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
996 if matches!(self.phase, Phase::Serving | Phase::Playing) && ctx.pressed(Button::Pause) {
997 self.paused = !self.paused;
998 }
999
1000 ctx.set_volume(self.master_volume);
1001 self.sustain_music(ctx);
1002
1003 ctx.set_camera(Self::camera());
1004
1005 let brick_pulse = (self.brick_flash / BRICK_FLASH).clamp(0.0, 1.0);
1006 ctx.set_bloom((BLOOM_BASE + brick_pulse * BLOOM_PULSE_PEAK).clamp(0.0, 1.0));
1007
1008 let life_lost_t = (self.life_lost_flash / LIFE_LOST_FLASH).clamp(0.0, 1.0);
1009 ctx.set_exposure((1.0 - life_lost_t * EXPOSURE_DIP_DEPTH).clamp(0.0, 1.0));
1010
1011 // The tick moves nothing behind a menu, so a frame there draws the last
1012 // step whole rather than interpolating from the one before.
1013 let alpha = match self.phase {
1014 Phase::Serving | Phase::Playing if !self.paused => ctx.alpha(),
1015 _ => 1.0,
1016 };
1017 let paddle_x = self.paddle_prev_x.lerp(self.paddle_x, alpha);
1018 let ball_pos = self.ball_prev.lerp(self.ball_pos, alpha);
1019
1020 ctx.light(Light::point(ball_pos, BALL_GLOW, BALL_LIGHT_RANGE).shadow());
1021
1022 self.draw_court(ctx);
1023 self.draw_bricks(ctx);
1024 self.draw_sparks(ctx);
1025 self.draw_lives(ctx);
1026
1027 ctx.draw(
1028 Paddle
1029 .at(Transform::from_translation(Vec3::new(
1030 paddle_x,
1031 PADDLE_HALF_HEIGHT,
1032 PADDLE_Z,
1033 )))
1034 .material_of(PaddlePart::Face, self.paddle_face_material()),
1035 );
1036
1037 self.draw_trail(ctx, alpha);
1038 ctx.draw(
1039 Sphere { subdivisions: 2 }
1040 .at(Transform::from_scale_rotation_translation(
1041 Vec3::splat(BALL_RADIUS * 2.0),
1042 Quat::IDENTITY,
1043 ball_pos,
1044 ))
1045 .material(
1046 Material::color(BALL_GLOW)
1047 .emissive(BALL_EMISSIVE)
1048 .additive(),
1049 ),
1050 );
1051
1052 self.overlay(ctx);
1053 }920 fn frame(&mut self, ctx: &mut FrameContext<'_, Scene>) {
921 self.steer_camera(ctx);
922
923 let alpha = ctx.alpha();
924 let elf_pos = self.elf_prev.lerp(self.elf_pos, alpha);
925 let elf_height = self.elf_height_prev + (self.elf_height - self.elf_height_prev) * alpha;
926 let (butterfly_pos, butterfly_yaw) = butterfly_pose(ctx.elapsed().as_secs_f32());
927
928 let camera = orbit_camera(elf_pos, self.camera_yaw, self.camera_pitch);
929 ctx.set_camera(camera);
930 ctx.set_cursor(if self.holding {
931 Cursor::Held
932 } else {
933 Cursor::Arrow
934 });
935 ctx.set_skybox(Sky::Day);
936 ctx.set_exposure(3.0);
937 ctx.set_bloom(0.2);
938 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
939 ctx.light(
940 Light::point(
941 LAMP_POST_POSITION + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP * 0.5),
942 LAMP_LIGHT_COLOR,
943 LAMP_LIGHT_RANGE,
944 )
945 .shadow(),
946 );
947 ctx.light(
948 Light::spot(Spot {
949 position: SPOT_POSITION,
950 direction: SPOT_DIRECTION,
951 color: SPOT_COLOR,
952 range: SPOT_RANGE,
953 angle: SPOT_ANGLE,
954 })
955 .shadow(),
956 );
957 ctx.light(
958 Light::point(butterfly_pos, BUTTERFLY_LIGHT_COLOR, BUTTERFLY_LIGHT_RANGE).shadow(),
959 );
960
961 ctx.draw(
962 Plane
963 .at(Transform::from_scale(Vec3::new(
964 GROUND_SIZE,
965 1.0,
966 GROUND_SIZE,
967 )))
968 .material(Material::lit(GROUND_COLOR)),
969 );
970 for patch in HURT_PATCHES {
971 ctx.draw(
972 Plane
973 .at(Transform::from_scale_rotation_translation(
974 Vec3::splat(HURT_RADIUS * 2.0),
975 Quat::IDENTITY,
976 patch,
977 ))
978 .material(Material::lit(HURT_COLOR)),
979 );
980 }
981 ctx.draw(
982 Cube.at(Transform::from_scale_rotation_translation(
983 Vec3::new(SEAT_FOOTPRINT, SEAT_HEIGHT, SEAT_FOOTPRINT),
984 Quat::IDENTITY,
985 SEAT_POSITION + Vec3::Y * SEAT_HEIGHT * 0.5,
986 ))
987 .material(Material::lit(SEAT_COLOR)),
988 );
989 ctx.draw(
990 Cube.at(Transform::from_scale_rotation_translation(
991 Vec3::new(LAMP_POST_THICKNESS, LAMP_POST_HEIGHT, LAMP_POST_THICKNESS),
992 Quat::IDENTITY,
993 LAMP_POST_POSITION + Vec3::Y * LAMP_POST_HEIGHT * 0.5,
994 ))
995 .material(Material::lit(LAMP_POST_COLOR)),
996 );
997 ctx.draw(
998 Cube.at(Transform::from_scale_rotation_translation(
999 Vec3::splat(LAMP_HEAD_SIZE),
1000 Quat::IDENTITY,
1001 LAMP_POST_POSITION
1002 + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP + LAMP_HEAD_SIZE * 0.5),
1003 ))
1004 .material(Material::color(Color::BLACK).emissive(LAMP_LIGHT_COLOR)),
1005 );
1006 ctx.draw(
1007 Cube.at(Transform::from_scale_rotation_translation(
1008 Vec3::splat(SPOT_FIXTURE_SIZE),
1009 Quat::IDENTITY,
1010 SPOT_POSITION + Vec3::Y * SPOT_FIXTURE_SIZE * 0.5,
1011 ))
1012 .material(Material::lit(SPOT_FIXTURE_COLOR)),
1013 );
1014
1015 ctx.draw(
1016 Elf.at(Transform::from_rotation_translation(
1017 Quat::from_rotation_y(self.elf_yaw),
1018 elf_pos + Vec3::Y * elf_height,
1019 ))
1020 .posed(&self.elf_animator),
1021 );
1022 ctx.draw(
1023 Elf.at(Transform::from_rotation_translation(
1024 Quat::from_rotation_y(core::f32::consts::PI),
1025 SCRUBBED_ELF_POSITION,
1026 ))
1027 .posed(&self.scrubbed_animator),
1028 );
1029 ctx.draw(
1030 Butterfly
1031 .at(Transform::from_rotation_translation(
1032 Quat::from_rotation_y(butterfly_yaw),
1033 butterfly_pos,
1034 ))
1035 .posed(&self.butterfly_animator)
1036 .material(Material::lit(Color::WHITE).emissive(BUTTERFLY_EMISSIVE)),
1037 );
1038
1039 self.draw_prompts(ctx, camera);
1040 self.panel(ctx);
1041 }Sourcepub fn set_bloom(&mut self, amount: f32)
pub fn set_bloom(&mut self, amount: f32)
Spreads the frame’s brightest light over what is around it; the
value is clamped into 0.0..=1.0, and 0.0 is used where a frame
never calls this.
The value is the fraction of the frame the spread replaces, and 0.0
is no work at all. The last call in a frame replaces the rest.
Examples found in repository?
More examples
1151 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
1152 self.fly_camera(ctx);
1153 ctx.set_camera(self.camera());
1154 ctx.set_skybox(self.sky);
1155 for light in self.lights() {
1156 ctx.light(light);
1157 }
1158 ctx.set_exposure(self.exposure);
1159 ctx.set_bloom(self.bloom);
1160
1161 self.draw_scene(ctx);
1162 self.controls(ctx);
1163 }819 fn frame(&mut self, ctx: &mut FrameContext<'_, Board>) {
820 let camera = Self::camera();
821 ctx.set_camera(camera);
822 ctx.set_skybox(Sky::Day);
823 ctx.light(Light::directional(Vec3::new(-0.35, -1.0, -0.5), SUN_COLOR).shadow());
824 ctx.set_bloom(BLOOM);
825
826 let hover = self.hovered(ctx);
827 self.draw_ground(ctx);
828 self.draw_board(ctx, hover);
829 self.draw_current_mark(ctx);
830 self.draw_rocks(ctx);
831 self.draw_sprite(ctx, hover);
832 self.draw_block(ctx, hover);
833 self.draw_prompt(ctx, camera, hover);
834
835 self.overlay(ctx);
836 }978 fn frame(&mut self, ctx: &mut FrameContext<'_, SoundCheck>) {
979 ctx.set_volume(self.master_volume);
980
981 let player = self.player_prev.lerp(self.player, ctx.alpha());
982 let ear = Vec3::new(player.x, EYE_HEIGHT, player.y);
983 let listener = View::look_at(ear, ear + Vec3::NEG_Z);
984 ctx.set_listener(listener);
985
986 ctx.set_camera(Self::camera(player));
987 ctx.set_skybox(Sky::Room);
988 ctx.set_bloom(0.2);
989 ctx.light(Light::directional(Vec3::new(-0.4, -1.0, -0.5), SUN_COLOR).shadow());
990
991 self.draw_room(ctx);
992 self.draw_sources(ctx);
993 self.draw_listener(ctx, listener);
994 self.draw_merge_markers(ctx);
995 self.draw_ring(ctx);
996
997 self.sustain_cues(ctx);
998 for (index, source) in self.sources.iter().enumerate() {
999 if source.enabled {
1000 ctx.sustain(source.cue().instance(index as u32));
1001 }
1002 }
1003 if self.merge_demo {
1004 ctx.sustain(Sound::Click.at(MERGE_POS_A).gain(MERGE_GAIN));
1005 ctx.sustain(Sound::Click.at(MERGE_POS_B).gain(MERGE_GAIN));
1006 }
1007 self.sustain_ring(ctx);
1008
1009 self.side_panel(ctx);
1010 let (play_once, play_many) = self.one_shot_panel(ctx);
1011
1012 if play_once {
1013 ctx.play(self.one_shot_cue());
1014 }
1015 if play_many {
1016 for _ in 0..32 {
1017 ctx.play(self.one_shot_cue());
1018 }
1019 }
1020 }995 fn frame(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
996 if matches!(self.phase, Phase::Serving | Phase::Playing) && ctx.pressed(Button::Pause) {
997 self.paused = !self.paused;
998 }
999
1000 ctx.set_volume(self.master_volume);
1001 self.sustain_music(ctx);
1002
1003 ctx.set_camera(Self::camera());
1004
1005 let brick_pulse = (self.brick_flash / BRICK_FLASH).clamp(0.0, 1.0);
1006 ctx.set_bloom((BLOOM_BASE + brick_pulse * BLOOM_PULSE_PEAK).clamp(0.0, 1.0));
1007
1008 let life_lost_t = (self.life_lost_flash / LIFE_LOST_FLASH).clamp(0.0, 1.0);
1009 ctx.set_exposure((1.0 - life_lost_t * EXPOSURE_DIP_DEPTH).clamp(0.0, 1.0));
1010
1011 // The tick moves nothing behind a menu, so a frame there draws the last
1012 // step whole rather than interpolating from the one before.
1013 let alpha = match self.phase {
1014 Phase::Serving | Phase::Playing if !self.paused => ctx.alpha(),
1015 _ => 1.0,
1016 };
1017 let paddle_x = self.paddle_prev_x.lerp(self.paddle_x, alpha);
1018 let ball_pos = self.ball_prev.lerp(self.ball_pos, alpha);
1019
1020 ctx.light(Light::point(ball_pos, BALL_GLOW, BALL_LIGHT_RANGE).shadow());
1021
1022 self.draw_court(ctx);
1023 self.draw_bricks(ctx);
1024 self.draw_sparks(ctx);
1025 self.draw_lives(ctx);
1026
1027 ctx.draw(
1028 Paddle
1029 .at(Transform::from_translation(Vec3::new(
1030 paddle_x,
1031 PADDLE_HALF_HEIGHT,
1032 PADDLE_Z,
1033 )))
1034 .material_of(PaddlePart::Face, self.paddle_face_material()),
1035 );
1036
1037 self.draw_trail(ctx, alpha);
1038 ctx.draw(
1039 Sphere { subdivisions: 2 }
1040 .at(Transform::from_scale_rotation_translation(
1041 Vec3::splat(BALL_RADIUS * 2.0),
1042 Quat::IDENTITY,
1043 ball_pos,
1044 ))
1045 .material(
1046 Material::color(BALL_GLOW)
1047 .emissive(BALL_EMISSIVE)
1048 .additive(),
1049 ),
1050 );
1051
1052 self.overlay(ctx);
1053 }920 fn frame(&mut self, ctx: &mut FrameContext<'_, Scene>) {
921 self.steer_camera(ctx);
922
923 let alpha = ctx.alpha();
924 let elf_pos = self.elf_prev.lerp(self.elf_pos, alpha);
925 let elf_height = self.elf_height_prev + (self.elf_height - self.elf_height_prev) * alpha;
926 let (butterfly_pos, butterfly_yaw) = butterfly_pose(ctx.elapsed().as_secs_f32());
927
928 let camera = orbit_camera(elf_pos, self.camera_yaw, self.camera_pitch);
929 ctx.set_camera(camera);
930 ctx.set_cursor(if self.holding {
931 Cursor::Held
932 } else {
933 Cursor::Arrow
934 });
935 ctx.set_skybox(Sky::Day);
936 ctx.set_exposure(3.0);
937 ctx.set_bloom(0.2);
938 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
939 ctx.light(
940 Light::point(
941 LAMP_POST_POSITION + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP * 0.5),
942 LAMP_LIGHT_COLOR,
943 LAMP_LIGHT_RANGE,
944 )
945 .shadow(),
946 );
947 ctx.light(
948 Light::spot(Spot {
949 position: SPOT_POSITION,
950 direction: SPOT_DIRECTION,
951 color: SPOT_COLOR,
952 range: SPOT_RANGE,
953 angle: SPOT_ANGLE,
954 })
955 .shadow(),
956 );
957 ctx.light(
958 Light::point(butterfly_pos, BUTTERFLY_LIGHT_COLOR, BUTTERFLY_LIGHT_RANGE).shadow(),
959 );
960
961 ctx.draw(
962 Plane
963 .at(Transform::from_scale(Vec3::new(
964 GROUND_SIZE,
965 1.0,
966 GROUND_SIZE,
967 )))
968 .material(Material::lit(GROUND_COLOR)),
969 );
970 for patch in HURT_PATCHES {
971 ctx.draw(
972 Plane
973 .at(Transform::from_scale_rotation_translation(
974 Vec3::splat(HURT_RADIUS * 2.0),
975 Quat::IDENTITY,
976 patch,
977 ))
978 .material(Material::lit(HURT_COLOR)),
979 );
980 }
981 ctx.draw(
982 Cube.at(Transform::from_scale_rotation_translation(
983 Vec3::new(SEAT_FOOTPRINT, SEAT_HEIGHT, SEAT_FOOTPRINT),
984 Quat::IDENTITY,
985 SEAT_POSITION + Vec3::Y * SEAT_HEIGHT * 0.5,
986 ))
987 .material(Material::lit(SEAT_COLOR)),
988 );
989 ctx.draw(
990 Cube.at(Transform::from_scale_rotation_translation(
991 Vec3::new(LAMP_POST_THICKNESS, LAMP_POST_HEIGHT, LAMP_POST_THICKNESS),
992 Quat::IDENTITY,
993 LAMP_POST_POSITION + Vec3::Y * LAMP_POST_HEIGHT * 0.5,
994 ))
995 .material(Material::lit(LAMP_POST_COLOR)),
996 );
997 ctx.draw(
998 Cube.at(Transform::from_scale_rotation_translation(
999 Vec3::splat(LAMP_HEAD_SIZE),
1000 Quat::IDENTITY,
1001 LAMP_POST_POSITION
1002 + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP + LAMP_HEAD_SIZE * 0.5),
1003 ))
1004 .material(Material::color(Color::BLACK).emissive(LAMP_LIGHT_COLOR)),
1005 );
1006 ctx.draw(
1007 Cube.at(Transform::from_scale_rotation_translation(
1008 Vec3::splat(SPOT_FIXTURE_SIZE),
1009 Quat::IDENTITY,
1010 SPOT_POSITION + Vec3::Y * SPOT_FIXTURE_SIZE * 0.5,
1011 ))
1012 .material(Material::lit(SPOT_FIXTURE_COLOR)),
1013 );
1014
1015 ctx.draw(
1016 Elf.at(Transform::from_rotation_translation(
1017 Quat::from_rotation_y(self.elf_yaw),
1018 elf_pos + Vec3::Y * elf_height,
1019 ))
1020 .posed(&self.elf_animator),
1021 );
1022 ctx.draw(
1023 Elf.at(Transform::from_rotation_translation(
1024 Quat::from_rotation_y(core::f32::consts::PI),
1025 SCRUBBED_ELF_POSITION,
1026 ))
1027 .posed(&self.scrubbed_animator),
1028 );
1029 ctx.draw(
1030 Butterfly
1031 .at(Transform::from_rotation_translation(
1032 Quat::from_rotation_y(butterfly_yaw),
1033 butterfly_pos,
1034 ))
1035 .posed(&self.butterfly_animator)
1036 .material(Material::lit(Color::WHITE).emissive(BUTTERFLY_EMISSIVE)),
1037 );
1038
1039 self.draw_prompts(ctx, camera);
1040 self.panel(ctx);
1041 }Sourcepub fn play(&mut self, sound: impl Into<SoundCue<G::Sounds>>)
pub fn play(&mut self, sound: impl Into<SoundCue<G::Sounds>>)
Plays sound once, keeping wherever it is placed as of this frame.
Every call is a voice of its own, so the same sound twice over is heard twice.
Examples found in repository?
978 fn frame(&mut self, ctx: &mut FrameContext<'_, SoundCheck>) {
979 ctx.set_volume(self.master_volume);
980
981 let player = self.player_prev.lerp(self.player, ctx.alpha());
982 let ear = Vec3::new(player.x, EYE_HEIGHT, player.y);
983 let listener = View::look_at(ear, ear + Vec3::NEG_Z);
984 ctx.set_listener(listener);
985
986 ctx.set_camera(Self::camera(player));
987 ctx.set_skybox(Sky::Room);
988 ctx.set_bloom(0.2);
989 ctx.light(Light::directional(Vec3::new(-0.4, -1.0, -0.5), SUN_COLOR).shadow());
990
991 self.draw_room(ctx);
992 self.draw_sources(ctx);
993 self.draw_listener(ctx, listener);
994 self.draw_merge_markers(ctx);
995 self.draw_ring(ctx);
996
997 self.sustain_cues(ctx);
998 for (index, source) in self.sources.iter().enumerate() {
999 if source.enabled {
1000 ctx.sustain(source.cue().instance(index as u32));
1001 }
1002 }
1003 if self.merge_demo {
1004 ctx.sustain(Sound::Click.at(MERGE_POS_A).gain(MERGE_GAIN));
1005 ctx.sustain(Sound::Click.at(MERGE_POS_B).gain(MERGE_GAIN));
1006 }
1007 self.sustain_ring(ctx);
1008
1009 self.side_panel(ctx);
1010 let (play_once, play_many) = self.one_shot_panel(ctx);
1011
1012 if play_once {
1013 ctx.play(self.one_shot_cue());
1014 }
1015 if play_many {
1016 for _ in 0..32 {
1017 ctx.play(self.one_shot_cue());
1018 }
1019 }
1020 }More examples
765 fn menu(&mut self, ctx: &mut FrameContext<'_, Breakout>, title: &str, over: bool) {
766 let mut clicked = false;
767 let mut quit = false;
768
769 // `ctx.ui` cannot borrow `ctx`, so anything the controls list needs is
770 // read first and applied after.
771 let buttons: Vec<(Button, String)> = Button::all()
772 .into_iter()
773 .map(|action| (action, bindings_text(ctx.bindings(action))))
774 .collect();
775 let axes: Vec<(Move, String)> = Move::all()
776 .into_iter()
777 .map(|action| (action, bindings_text(ctx.bindings(action))))
778 .collect();
779 let listening = self.listening;
780 let actuated_button = (!ctx.ui_wants_keyboard())
781 .then(|| ctx.actuated_button())
782 .flatten();
783 let actuated_axis = (!ctx.ui_wants_keyboard())
784 .then(|| ctx.actuated_axis())
785 .flatten();
786 let mut reset = None;
787
788 ctx.ui(|ui| {
789 egui::Window::new(title)
790 .collapsible(false)
791 .resizable(false)
792 .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
793 .show(ui.ctx(), |ui| {
794 if over {
795 ui.label(format!("score {}", self.score));
796 }
797 if !over {
798 ui.add(
799 egui::Slider::new(&mut self.master_volume, 0.0..=1.0).text("volume"),
800 );
801 if ui.button("resume").clicked() {
802 self.paused = false;
803 clicked = true;
804 }
805 ui.separator();
806 ui.heading("controls");
807 for (action, text) in &buttons {
808 controls_row(
809 ui,
810 action.name(),
811 text,
812 listening == Some(Listening::Button(*action)),
813 &mut self.listening,
814 Listening::Button(*action),
815 &mut reset,
816 );
817 }
818 for (action, text) in &axes {
819 controls_row(
820 ui,
821 action.name(),
822 text,
823 listening == Some(Listening::Move(*action)),
824 &mut self.listening,
825 Listening::Move(*action),
826 &mut reset,
827 );
828 }
829 }
830 if ui.button("restart").clicked() {
831 self.restart();
832 clicked = true;
833 }
834 if ui.button("quit").clicked() {
835 quit = true;
836 }
837 });
838 });
839
840 match (self.listening, actuated_button, actuated_axis) {
841 (Some(Listening::Button(action)), Some(binding), _) => {
842 ctx.rebind(action, vec![binding]);
843 self.listening = None;
844 }
845 (Some(Listening::Move(action)), _, Some(binding)) => {
846 ctx.rebind(action, vec![binding]);
847 self.listening = None;
848 }
849 _ => {}
850 }
851 match reset {
852 Some(Listening::Button(action)) => ctx.rebind(action, action.bindings()),
853 Some(Listening::Move(action)) => ctx.rebind(action, action.bindings()),
854 None => {}
855 }
856
857 if clicked {
858 ctx.play(Sound::Click);
859 }
860 if quit {
861 ctx.close();
862 }
863 }Sourcepub fn sustain(&mut self, sound: impl Into<SoundCue<G::Sounds>>)
pub fn sustain(&mut self, sound: impl Into<SoundCue<G::Sounds>>)
Keeps sound playing while frames go on declaring it, and fades it
out over its fade once one does not.
What a frame declares is the whole of what it wants sounding. One voice per value, whose knobs follow what each frame declares; the last call for a value in a frame is the one that counts. A value a frame stops declaring is dropped once it has faded, so declaring it again after that starts it at its window start.
Examples found in repository?
836 fn sustain_ring(&self, ctx: &mut FrameContext<'_, SoundCheck>) {
837 if !self.ring_demo {
838 return;
839 }
840 for nth in 0..RING_COUNT {
841 let gain = RING_GAIN * (1.0 - nth as f32 / RING_COUNT as f32);
842 ctx.sustain(
843 Sound::Pulse
844 .at(ring_place(nth))
845 .gain(gain)
846 .reference(RING_REFERENCE)
847 .range(RING_RADIUS * 3.0)
848 .instance(nth + 1),
849 );
850 }
851 }
852
853 fn sustain_cues(&self, ctx: &mut FrameContext<'_, SoundCheck>) {
854 let fade = Duration::from_secs_f32(self.cue_fade);
855 if self.theme_on {
856 ctx.sustain(Sound::Theme.gain(0.5).fade(fade));
857 }
858 if self.menu_on {
859 ctx.sustain(Sound::MenuTheme.gain(0.5).fade(fade));
860 }
861 if self.pulse_on {
862 ctx.sustain(Sound::Pulse.gain(0.3).fade(fade));
863 }
864 }
865}
866
867/// Where the `nth` sustain of the ring stands: around the room, starting
868/// behind the listener's back.
869fn ring_place(nth: u32) -> Vec3 {
870 let turn = TAU * nth as f32 / RING_COUNT as f32;
871
872 Vec3::new(
873 turn.sin() * RING_RADIUS,
874 SOURCE_HEIGHT,
875 turn.cos() * RING_RADIUS,
876 )
877}
878
879/// The direction the ear pair is offset along — the same side the
880/// engine's own pan reads.
881fn listener_right(view: View) -> Vec3 {
882 (view.target() - view.eye())
883 .normalize_or_zero()
884 .cross(view.up())
885}
886
887/// The clip's duration, with two trim handles and a loop marker, each moved
888/// by the pointer's own place rather than by a moving total.
889fn duration_bar(
890 ui: &mut egui::Ui,
891 duration: f32,
892 trim_start: &mut f32,
893 trim_end: &mut f32,
894 loop_from: &mut f32,
895) {
896 let size = egui::vec2(ui.available_width().min(420.0), 28.0);
897 let (rect, _response) = ui.allocate_exact_size(size, egui::Sense::hover());
898 let painter = ui.painter();
899 painter.rect_filled(rect, 3.0, egui::Color32::from_gray(35));
900
901 let x_of = |seconds: f32| rect.left() + (seconds / duration).clamp(0.0, 1.0) * rect.width();
902 let seconds_of = |x: f32| ((x - rect.left()) / rect.width()).clamp(0.0, 1.0) * duration;
903
904 let span = egui::Rect::from_min_max(
905 egui::pos2(x_of(*trim_start), rect.top()),
906 egui::pos2(x_of(*trim_end), rect.bottom()),
907 );
908 painter.rect_filled(span, 3.0, egui::Color32::from_rgb(70, 120, 95));
909
910 let start_x = x_of(*trim_start);
911 if let Some(x) = drag_handle(
912 ui,
913 rect,
914 "trim-start",
915 start_x,
916 egui::Color32::from_rgb(230, 200, 80),
917 ) {
918 *trim_start = seconds_of(x).min(*trim_end);
919 }
920 let end_x = x_of(*trim_end);
921 if let Some(x) = drag_handle(
922 ui,
923 rect,
924 "trim-end",
925 end_x,
926 egui::Color32::from_rgb(230, 200, 80),
927 ) {
928 *trim_end = seconds_of(x).max(*trim_start);
929 }
930 let loop_x = x_of(*loop_from);
931 if let Some(x) = drag_handle(
932 ui,
933 rect,
934 "loop-from",
935 loop_x,
936 egui::Color32::from_rgb(90, 170, 230),
937 ) {
938 *loop_from = seconds_of(x).clamp(*trim_start, *trim_end);
939 }
940}
941
942/// One round handle at `x`. Returns the pointer's `x` while a drag holds
943/// it.
944fn drag_handle(
945 ui: &mut egui::Ui,
946 bar: egui::Rect,
947 salt: &str,
948 x: f32,
949 color: egui::Color32,
950) -> Option<f32> {
951 let radius = 6.0;
952 let center = egui::pos2(x, bar.center().y);
953 let sense_rect = egui::Rect::from_center_size(center, egui::Vec2::splat(radius * 2.5));
954 let id = ui.id().with(salt);
955 let response = ui.interact(sense_rect, id, egui::Sense::drag());
956 ui.painter().circle_filled(center, radius, color);
957
958 response
959 .dragged()
960 .then(|| response.interact_pointer_pos())
961 .flatten()
962 .map(|pos| pos.x)
963}
964
965impl Game for SoundCheck {
966 type Meshes = Shape;
967 type Sounds = Sound;
968 type InputActions = Controls;
969 type Skyboxes = Sky;
970 type SurfaceStyles = NoSurfaceStyles;
971 type PostEffects = NoPostEffects;
972
973 fn tick(&mut self, ctx: &mut TickContext<'_, SoundCheck>) {
974 self.handle_walk(ctx);
975 self.handle_drag(ctx);
976 }
977
978 fn frame(&mut self, ctx: &mut FrameContext<'_, SoundCheck>) {
979 ctx.set_volume(self.master_volume);
980
981 let player = self.player_prev.lerp(self.player, ctx.alpha());
982 let ear = Vec3::new(player.x, EYE_HEIGHT, player.y);
983 let listener = View::look_at(ear, ear + Vec3::NEG_Z);
984 ctx.set_listener(listener);
985
986 ctx.set_camera(Self::camera(player));
987 ctx.set_skybox(Sky::Room);
988 ctx.set_bloom(0.2);
989 ctx.light(Light::directional(Vec3::new(-0.4, -1.0, -0.5), SUN_COLOR).shadow());
990
991 self.draw_room(ctx);
992 self.draw_sources(ctx);
993 self.draw_listener(ctx, listener);
994 self.draw_merge_markers(ctx);
995 self.draw_ring(ctx);
996
997 self.sustain_cues(ctx);
998 for (index, source) in self.sources.iter().enumerate() {
999 if source.enabled {
1000 ctx.sustain(source.cue().instance(index as u32));
1001 }
1002 }
1003 if self.merge_demo {
1004 ctx.sustain(Sound::Click.at(MERGE_POS_A).gain(MERGE_GAIN));
1005 ctx.sustain(Sound::Click.at(MERGE_POS_B).gain(MERGE_GAIN));
1006 }
1007 self.sustain_ring(ctx);
1008
1009 self.side_panel(ctx);
1010 let (play_once, play_many) = self.one_shot_panel(ctx);
1011
1012 if play_once {
1013 ctx.play(self.one_shot_cue());
1014 }
1015 if play_many {
1016 for _ in 0..32 {
1017 ctx.play(self.one_shot_cue());
1018 }
1019 }
1020 }More examples
872 fn sustain_music(&self, ctx: &mut FrameContext<'_, Breakout>) {
873 let playing = !self.paused && matches!(self.phase, Phase::Serving | Phase::Playing);
874 let gain = |wanted: bool| match wanted {
875 true => MUSIC_GAIN,
876 false => 0.0,
877 };
878
879 ctx.sustain(
880 Sound::Music
881 .gain(gain(playing))
882 .fade(MUSIC_CROSSFADE)
883 .glide(MUSIC_CROSSFADE)
884 .loop_from(MUSIC_LOOP_FROM),
885 );
886 ctx.sustain(
887 Sound::MenuMusic
888 .gain(gain(!playing))
889 .fade(MUSIC_CROSSFADE)
890 .glide(MUSIC_CROSSFADE)
891 .loop_from(MENU_MUSIC_LOOP_FROM),
892 );
893 }Sourcepub fn set_listener(&mut self, listener: View)
pub fn set_listener(&mut self, listener: View)
Hears the frame from listener, in place of the frame’s camera.
Examples found in repository?
978 fn frame(&mut self, ctx: &mut FrameContext<'_, SoundCheck>) {
979 ctx.set_volume(self.master_volume);
980
981 let player = self.player_prev.lerp(self.player, ctx.alpha());
982 let ear = Vec3::new(player.x, EYE_HEIGHT, player.y);
983 let listener = View::look_at(ear, ear + Vec3::NEG_Z);
984 ctx.set_listener(listener);
985
986 ctx.set_camera(Self::camera(player));
987 ctx.set_skybox(Sky::Room);
988 ctx.set_bloom(0.2);
989 ctx.light(Light::directional(Vec3::new(-0.4, -1.0, -0.5), SUN_COLOR).shadow());
990
991 self.draw_room(ctx);
992 self.draw_sources(ctx);
993 self.draw_listener(ctx, listener);
994 self.draw_merge_markers(ctx);
995 self.draw_ring(ctx);
996
997 self.sustain_cues(ctx);
998 for (index, source) in self.sources.iter().enumerate() {
999 if source.enabled {
1000 ctx.sustain(source.cue().instance(index as u32));
1001 }
1002 }
1003 if self.merge_demo {
1004 ctx.sustain(Sound::Click.at(MERGE_POS_A).gain(MERGE_GAIN));
1005 ctx.sustain(Sound::Click.at(MERGE_POS_B).gain(MERGE_GAIN));
1006 }
1007 self.sustain_ring(ctx);
1008
1009 self.side_panel(ctx);
1010 let (play_once, play_many) = self.one_shot_panel(ctx);
1011
1012 if play_once {
1013 ctx.play(self.one_shot_cue());
1014 }
1015 if play_many {
1016 for _ in 0..32 {
1017 ctx.play(self.one_shot_cue());
1018 }
1019 }
1020 }Sourcepub fn set_volume(&mut self, volume: f32)
pub fn set_volume(&mut self, volume: f32)
Plays everything this frame at volume, the fraction of each
sound’s own gain it multiplies; 1.0 where a frame never calls this.
The mix slides to it over SoundCue::DEFAULT_GLIDE, so a slider a
player moves never steps the sound.
Examples found in repository?
978 fn frame(&mut self, ctx: &mut FrameContext<'_, SoundCheck>) {
979 ctx.set_volume(self.master_volume);
980
981 let player = self.player_prev.lerp(self.player, ctx.alpha());
982 let ear = Vec3::new(player.x, EYE_HEIGHT, player.y);
983 let listener = View::look_at(ear, ear + Vec3::NEG_Z);
984 ctx.set_listener(listener);
985
986 ctx.set_camera(Self::camera(player));
987 ctx.set_skybox(Sky::Room);
988 ctx.set_bloom(0.2);
989 ctx.light(Light::directional(Vec3::new(-0.4, -1.0, -0.5), SUN_COLOR).shadow());
990
991 self.draw_room(ctx);
992 self.draw_sources(ctx);
993 self.draw_listener(ctx, listener);
994 self.draw_merge_markers(ctx);
995 self.draw_ring(ctx);
996
997 self.sustain_cues(ctx);
998 for (index, source) in self.sources.iter().enumerate() {
999 if source.enabled {
1000 ctx.sustain(source.cue().instance(index as u32));
1001 }
1002 }
1003 if self.merge_demo {
1004 ctx.sustain(Sound::Click.at(MERGE_POS_A).gain(MERGE_GAIN));
1005 ctx.sustain(Sound::Click.at(MERGE_POS_B).gain(MERGE_GAIN));
1006 }
1007 self.sustain_ring(ctx);
1008
1009 self.side_panel(ctx);
1010 let (play_once, play_many) = self.one_shot_panel(ctx);
1011
1012 if play_once {
1013 ctx.play(self.one_shot_cue());
1014 }
1015 if play_many {
1016 for _ in 0..32 {
1017 ctx.play(self.one_shot_cue());
1018 }
1019 }
1020 }More examples
995 fn frame(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
996 if matches!(self.phase, Phase::Serving | Phase::Playing) && ctx.pressed(Button::Pause) {
997 self.paused = !self.paused;
998 }
999
1000 ctx.set_volume(self.master_volume);
1001 self.sustain_music(ctx);
1002
1003 ctx.set_camera(Self::camera());
1004
1005 let brick_pulse = (self.brick_flash / BRICK_FLASH).clamp(0.0, 1.0);
1006 ctx.set_bloom((BLOOM_BASE + brick_pulse * BLOOM_PULSE_PEAK).clamp(0.0, 1.0));
1007
1008 let life_lost_t = (self.life_lost_flash / LIFE_LOST_FLASH).clamp(0.0, 1.0);
1009 ctx.set_exposure((1.0 - life_lost_t * EXPOSURE_DIP_DEPTH).clamp(0.0, 1.0));
1010
1011 // The tick moves nothing behind a menu, so a frame there draws the last
1012 // step whole rather than interpolating from the one before.
1013 let alpha = match self.phase {
1014 Phase::Serving | Phase::Playing if !self.paused => ctx.alpha(),
1015 _ => 1.0,
1016 };
1017 let paddle_x = self.paddle_prev_x.lerp(self.paddle_x, alpha);
1018 let ball_pos = self.ball_prev.lerp(self.ball_pos, alpha);
1019
1020 ctx.light(Light::point(ball_pos, BALL_GLOW, BALL_LIGHT_RANGE).shadow());
1021
1022 self.draw_court(ctx);
1023 self.draw_bricks(ctx);
1024 self.draw_sparks(ctx);
1025 self.draw_lives(ctx);
1026
1027 ctx.draw(
1028 Paddle
1029 .at(Transform::from_translation(Vec3::new(
1030 paddle_x,
1031 PADDLE_HALF_HEIGHT,
1032 PADDLE_Z,
1033 )))
1034 .material_of(PaddlePart::Face, self.paddle_face_material()),
1035 );
1036
1037 self.draw_trail(ctx, alpha);
1038 ctx.draw(
1039 Sphere { subdivisions: 2 }
1040 .at(Transform::from_scale_rotation_translation(
1041 Vec3::splat(BALL_RADIUS * 2.0),
1042 Quat::IDENTITY,
1043 ball_pos,
1044 ))
1045 .material(
1046 Material::color(BALL_GLOW)
1047 .emissive(BALL_EMISSIVE)
1048 .additive(),
1049 ),
1050 );
1051
1052 self.overlay(ctx);
1053 }Sourcepub fn sound_unlocked(&self) -> bool
pub fn sound_unlocked(&self) -> bool
Whether the platform allows sound to start right now.
True from the first frame on the desktop, with or without an audio device: what holds it false is the browser, which plays nothing until the player has done something. A one-shot played while it is false is dropped; a sustain declared then starts when it turns true.
Sourcepub fn saved<K: SaveKey>(&self, key: K) -> K::Value
pub fn saved<K: SaveKey>(&self, key: K) -> K::Value
The value the last run to save key kept, or its fallback where
none did, or where what was kept no longer reads as the key’s own
value, with a debug log.
Sourcepub fn save<K: SaveKey>(&mut self, key: K, value: K::Value)
pub fn save<K: SaveKey>(&mut self, key: K, value: K::Value)
Keeps value under key, for the rest of this run and the runs
after it.
The store is written once the frame is drawn, and only where a value changed, so saving every frame costs nothing.
Sourcepub fn ui(&mut self, build: impl FnOnce(&mut Ui))
pub fn ui(&mut self, build: impl FnOnce(&mut Ui))
Builds this frame’s UI, drawn over the scene.
Calls append to one layer, which is placed 8 points clear of the
window’s edges; floating windows go through ui.ctx(), and
egui::Panel::left and the three beside it hold a panel against one
side of that layer, shown in the ui this call takes.
Examples found in repository?
643 fn overlay(&self, ctx: &mut FrameContext<'_, Board>) {
644 ctx.ui(|ui| {
645 ui.label(match self.turn {
646 Turn::Sprite => "the sprite unit's turn",
647 Turn::Block => "the block unit's turn",
648 });
649 ui.label(if self.selected {
650 "click a marked tile to order the move"
651 } else {
652 "click the glowing unit to select it"
653 });
654 });
655 }
656
657 /// What a click at `hover` does, named for the player; `None` where a
658 /// click has no effect.
659 fn click_effect(&self, hover: Hover) -> Option<&'static str> {
660 match hover {
661 Hover::CurrentUnit if self.selected => Some("deselect"),
662 Hover::CurrentUnit => Some("select"),
663 Hover::Tile(tile) if self.selected && self.reachable(tile) => Some("move here"),
664 Hover::Tile(_) if self.selected => Some("occupied"),
665 _ => None,
666 }
667 }
668
669 /// A prompt beside the pointer's target, naming what its click does;
670 /// absent where [`Self::click_effect`] reads no effect.
671 fn draw_prompt(&self, ctx: &mut FrameContext<'_, Board>, camera: Camera, hover: Hover) {
672 let Some(text) = self.click_effect(hover) else {
673 return;
674 };
675 let point = match hover {
676 Hover::CurrentUnit => {
677 let (lift, _) = unit_geometry(self.turn);
678 self.current().position + Vec3::Y * (lift * 2.0 + PROMPT_UNIT_LIFT)
679 }
680 Hover::Tile(tile) => tile_center(tile) + Vec3::Y * PROMPT_TILE_LIFT,
681 Hover::None => return,
682 };
683 let galley = ctx.text_layout(text, egui::FontId::proportional(PROMPT_SIZE));
684 let window_size = ctx.window_size();
685 let pixels_per_point = ctx.pixels_per_point();
686 let Some(pixel) = camera.pixel_of(point, window_size) else {
687 return;
688 };
689 let at = logical(pixel, pixels_per_point);
690 ctx.ui(|ui| {
691 let painter = ui.painter();
692 let ink = galley.mesh_bounds;
693 let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
694 let backdrop = egui::Rect::from_center_size(
695 at,
696 ink.size() + egui::Vec2::splat(PROMPT_PADDING * 2.0),
697 );
698 painter.rect_filled(
699 backdrop,
700 PROMPT_PADDING,
701 egui::Color32::from_black_alpha(PROMPT_BACKDROP),
702 );
703 painter.galley(pos, galley, PROMPT_TEXT_COLOR);
704 });
705 }More examples
124 fn panel(&mut self, ctx: &mut FrameContext<'_, Self>) {
125 ctx.ui(|ui| {
126 ui.add(egui::Slider::new(&mut self.vignette, 0.0..=1.0).text("vignette"));
127 ui.add(egui::Slider::new(&mut self.grain, 0.0..=1.0).text("grain"));
128 ui.add(egui::Slider::new(&mut self.scanlines, 0.0..=1.0).text("scanlines"));
129 });
130
131 ctx.set_post_effect(Vignette {
132 strength: self.vignette,
133 });
134 ctx.set_post_effect(Grain {
135 strength: self.grain,
136 seed: self.ticks,
137 });
138 ctx.set_post_effect(Scanlines {
139 strength: self.scanlines,
140 });
141 }682 fn draw_bracket(&self, ctx: &mut FrameContext<'_, Self>, camera: Camera, station: StationKind) {
683 let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
684 let window_size = ctx.window_size();
685 let Some(pixel) = camera.pixel_of(top, window_size) else {
686 return;
687 };
688 let at = logical(pixel, ctx.pixels_per_point());
689
690 let name = ctx.text_layout(station.look().name, egui::FontId::proportional(BODY_SIZE));
691 let (reading_text, number_text) = station.reading(self.elapsed.as_secs_f32());
692 let reading = ctx.text_layout(&reading_text, egui::FontId::monospace(BODY_SIZE));
693 let number = ctx.text_layout(
694 &number_text,
695 egui::FontId::new(NUMBER_SIZE, egui::FontFamily::Name(DISPLAY_FAMILY.into())),
696 );
697
698 ctx.ui(|ui| bracket(ui.painter(), at, name, reading, number));
699 }
700
701 /// A `Prompt` for `Trigger::Hail`, above every `StationKind` but
702 /// `hovered`: what a player presses to reach one, apart from a hover.
703 fn draw_prompts(
704 &self,
705 ctx: &mut FrameContext<'_, Self>,
706 camera: Camera,
707 hovered: Option<StationKind>,
708 ) {
709 let Some(binding) = ctx.bindings(Trigger::Hail).into_iter().next() else {
710 return;
711 };
712 let hint = prompt(&binding);
713 let glyph = ctx.text_layout(&hint.text(), egui::FontId::new(PROMPT_SIZE, hint.family()));
714 let window_size = ctx.window_size();
715 let pixels_per_point = ctx.pixels_per_point();
716
717 ctx.ui(|ui| {
718 let painter = ui.painter();
719 for station in StationKind::ALL {
720 if Some(station) == hovered {
721 continue;
722 }
723 let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
724 let Some(pixel) = camera.pixel_of(top, window_size) else {
725 continue;
726 };
727 let at = logical(pixel, pixels_per_point);
728 let at = egui::pos2(at.x, at.y - PROMPT_LIFT);
729 prompt_at(painter, at, glyph.clone());
730 }
731 });
732 }
733
734 /// The title, a line and the reading, each in a font this game loaded
735 /// rather than egui's own.
736 fn panel(&self, ctx: &mut FrameContext<'_, Self>) {
737 ctx.ui(|ui| {
738 ui.label(styled(
739 "a game's own fonts",
740 egui::FontId::proportional(HEADING_SIZE),
741 ));
742 ui.label(styled(
743 "drawn in Pixel Operator, the game's proportional font",
744 egui::FontId::proportional(BODY_SIZE),
745 ));
746 ui.label(styled(
747 "the readings above each station in Pixel Operator Mono",
748 egui::FontId::monospace(BODY_SIZE),
749 ));
750 });
751 }
752
753 fn draw_dialogue(&self, ctx: &mut FrameContext<'_, Self>) {
754 let Some(dialogue) = &self.dialogue else {
755 return;
756 };
757 let whole = ctx.text_layout(
758 dialogue.current_line(),
759 egui::FontId::proportional(BODY_SIZE),
760 );
761 let size = whole.size();
762 ctx.ui(|ui| dialogue.draw(ui, size));
763 }
764
765 /// The `StationKind` under the pointer, `None` while the UI holds it.
766 fn hovered(ctx: &FrameContext<'_, Self>) -> Option<StationKind> {
767 if ctx.ui_wants_pointer() {
768 return None;
769 }
770 hit_station(
771 ctx.last_camera()
772 .ray_through(ctx.pointer(), ctx.window_size()),
773 )
774 }
775
776 /// A held [`Trigger::Hail`] turns the camera by the pointer's own
777 /// motion; the wheel zooms it.
778 fn steer(&mut self, ctx: &mut FrameContext<'_, Self>) {
779 if !ctx.ui_wants_pointer() && ctx.down(Trigger::Hail) {
780 self.orbit.turn(ctx.axis2(Turn::Look));
781 }
782 let wheel = ctx.axis(Zoom::Wheel);
783 if !ctx.ui_wants_pointer() && wheel != 0.0 {
784 self.orbit.zoom(ZOOM_STEP.powf(wheel));
785 }
786 }
787}
788
789impl Game for WatchRoom {
790 type Meshes = Shape;
791 type Sounds = NoSounds;
792 type InputActions = Controls;
793 type Skyboxes = Sky;
794 type SurfaceStyles = NoSurfaceStyles;
795 type PostEffects = NoPostEffects;
796
797 fn tick(&mut self, ctx: &mut TickContext<'_, Self>) {
798 self.elapsed += ctx.dt();
799 self.orbit.yaw += AUTO_TURN_RATE * ctx.dt().as_secs_f32();
800
801 if let Some(dialogue) = &mut self.dialogue {
802 dialogue.tick();
803 }
804 if ctx.pressed(Trigger::Close) {
805 self.dialogue = None;
806 self.hailed = None;
807 }
808 if ctx.pressed(Trigger::Sheet) {
809 self.sheet_open = !self.sheet_open;
810 }
811 if ctx.pressed(Trigger::Hail) && !ctx.ui_wants_pointer() {
812 self.handle_hail(ctx);
813 }
814 }
815
816 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
817 self.steer(ctx);
818
819 let camera = self.orbit.camera();
820 ctx.set_camera(camera);
821 ctx.set_skybox(Sky::Dusk);
822 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
823
824 ctx.draw(
825 Plane
826 .at(Transform::from_scale(Vec3::new(
827 PLATFORM_SIZE,
828 1.0,
829 PLATFORM_SIZE,
830 )))
831 .material(Material::lit(PLATFORM_COLOR)),
832 );
833 for station in StationKind::ALL {
834 self.draw_station(ctx, station);
835 }
836
837 let hovered = Self::hovered(ctx);
838 if !self.sheet_open {
839 if let Some(station) = hovered {
840 ctx.set_cursor(Cursor::Pointer);
841 self.draw_bracket(ctx, camera, station);
842 }
843 self.draw_prompts(ctx, camera, hovered);
844 }
845 if self.dialogue.is_some() {
846 self.draw_dialogue(ctx);
847 }
848 if self.sheet_open {
849 ctx.ui(sheet);
850 }
851 self.panel(ctx);
852 }692 fn panel(&mut self, ctx: &mut FrameContext<'_, Self>) {
693 let workers = rayon::current_num_threads();
694 let tick_ms = self.last_tick_ms;
695
696 ctx.ui(|ui| {
697 egui::Frame::new()
698 .fill(egui::Color32::from_gray(24))
699 .inner_margin(PANEL_PADDING)
700 .corner_radius(f32::from(PANEL_PADDING))
701 .show(ui, |ui| {
702 ui.label(format!("workers {workers}"));
703 ui.horizontal(|ui| {
704 for size in FLOCK_SIZES {
705 ui.radio_value(
706 &mut self.settings.flock_size,
707 size,
708 format!("{size} butterflies"),
709 );
710 }
711 });
712 ui.checkbox(&mut self.settings.sequential, "sequential update");
713 ui.separator();
714 ui.label(format!("tick time {tick_ms:.2}ms"));
715 });
716 });
717 }735 fn overlay(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
736 let bricks_left = self
737 .bricks
738 .iter()
739 .filter(|brick| brick.hits_remaining > 0)
740 .count();
741 // Read before `ctx.ui` so a rebind changes what the hint reads this
742 // frame too.
743 let move_hint = bindings_text(ctx.bindings(Move::Paddle));
744 let pause_hint = bindings_text(ctx.bindings(Button::Pause));
745 let serve_hint = bindings_text(ctx.bindings(Button::Serve));
746 ctx.ui(|ui| {
747 ui.horizontal(|ui| {
748 ui.label(egui::RichText::new(format!("score {}", self.score)).size(32.0));
749 ui.label(format!("{bricks_left} bricks left"));
750 });
751 ui.label(format!("move: {move_hint} · {pause_hint} to pause"));
752 if self.phase == Phase::Serving {
753 ui.label(format!("{serve_hint} to serve"));
754 }
755 });
756
757 match self.phase {
758 Phase::Serving | Phase::Playing if self.paused => self.menu(ctx, "paused", false),
759 Phase::Won => self.menu(ctx, "you win", true),
760 Phase::Lost => self.menu(ctx, "game over", true),
761 _ => {}
762 }
763 }
764
765 fn menu(&mut self, ctx: &mut FrameContext<'_, Breakout>, title: &str, over: bool) {
766 let mut clicked = false;
767 let mut quit = false;
768
769 // `ctx.ui` cannot borrow `ctx`, so anything the controls list needs is
770 // read first and applied after.
771 let buttons: Vec<(Button, String)> = Button::all()
772 .into_iter()
773 .map(|action| (action, bindings_text(ctx.bindings(action))))
774 .collect();
775 let axes: Vec<(Move, String)> = Move::all()
776 .into_iter()
777 .map(|action| (action, bindings_text(ctx.bindings(action))))
778 .collect();
779 let listening = self.listening;
780 let actuated_button = (!ctx.ui_wants_keyboard())
781 .then(|| ctx.actuated_button())
782 .flatten();
783 let actuated_axis = (!ctx.ui_wants_keyboard())
784 .then(|| ctx.actuated_axis())
785 .flatten();
786 let mut reset = None;
787
788 ctx.ui(|ui| {
789 egui::Window::new(title)
790 .collapsible(false)
791 .resizable(false)
792 .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
793 .show(ui.ctx(), |ui| {
794 if over {
795 ui.label(format!("score {}", self.score));
796 }
797 if !over {
798 ui.add(
799 egui::Slider::new(&mut self.master_volume, 0.0..=1.0).text("volume"),
800 );
801 if ui.button("resume").clicked() {
802 self.paused = false;
803 clicked = true;
804 }
805 ui.separator();
806 ui.heading("controls");
807 for (action, text) in &buttons {
808 controls_row(
809 ui,
810 action.name(),
811 text,
812 listening == Some(Listening::Button(*action)),
813 &mut self.listening,
814 Listening::Button(*action),
815 &mut reset,
816 );
817 }
818 for (action, text) in &axes {
819 controls_row(
820 ui,
821 action.name(),
822 text,
823 listening == Some(Listening::Move(*action)),
824 &mut self.listening,
825 Listening::Move(*action),
826 &mut reset,
827 );
828 }
829 }
830 if ui.button("restart").clicked() {
831 self.restart();
832 clicked = true;
833 }
834 if ui.button("quit").clicked() {
835 quit = true;
836 }
837 });
838 });
839
840 match (self.listening, actuated_button, actuated_axis) {
841 (Some(Listening::Button(action)), Some(binding), _) => {
842 ctx.rebind(action, vec![binding]);
843 self.listening = None;
844 }
845 (Some(Listening::Move(action)), _, Some(binding)) => {
846 ctx.rebind(action, vec![binding]);
847 self.listening = None;
848 }
849 _ => {}
850 }
851 match reset {
852 Some(Listening::Button(action)) => ctx.rebind(action, action.bindings()),
853 Some(Listening::Move(action)) => ctx.rebind(action, action.bindings()),
854 None => {}
855 }
856
857 if clicked {
858 ctx.play(Sound::Click);
859 }
860 if quit {
861 ctx.close();
862 }
863 }1638 fn draw_door_prompt(&self, ctx: &mut FrameContext<'_, Keep>, camera: Camera) {
1639 let near = self.position.distance(INTERACT_POINT) < INTERACT_RADIUS;
1640 let swinging = self.door_opening && self.swing_ticks < DOOR_SWING_TICKS;
1641 let text = if swinging {
1642 "opening"
1643 } else if near && !self.door_opening {
1644 "e opens the door"
1645 } else {
1646 return;
1647 };
1648
1649 let galley = ctx.text_layout(text, egui::FontId::proportional(DOOR_PROMPT_SIZE));
1650 let point = INTERACT_POINT + Vec3::Y * (DOOR_HEIGHT + DOOR_PROMPT_LIFT);
1651 let window_size = ctx.window_size();
1652 let pixels_per_point = ctx.pixels_per_point();
1653 let Some(pixel) = camera.pixel_of(point, window_size) else {
1654 return;
1655 };
1656
1657 ctx.ui(|ui| {
1658 let painter = ui.painter();
1659 let at = logical(pixel, pixels_per_point);
1660 let ink = galley.mesh_bounds;
1661 let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
1662 let backdrop = egui::Rect::from_center_size(
1663 at,
1664 ink.size() + egui::Vec2::splat(DOOR_PROMPT_PADDING * 2.0),
1665 );
1666 painter.rect_filled(
1667 backdrop,
1668 DOOR_PROMPT_PADDING,
1669 egui::Color32::from_black_alpha(DOOR_PROMPT_BACKDROP),
1670 );
1671 painter.galley(pos, galley, DOOR_PROMPT_COLOR);
1672 });
1673 }
1674
1675 /// The gem, spinning and bobbing over the chamber's floor, and the light
1676 /// it casts over it.
1677 fn draw_gem(&self, ctx: &mut FrameContext<'_, Keep>) {
1678 let t = self.simulated.as_secs_f32();
1679 let bob = (t * 2.0).sin() * GEM_BOB_HEIGHT;
1680 ctx.light(
1681 Light::point(
1682 GEM_POSITION + Vec3::Y * (bob + GEM_LIGHT_LIFT),
1683 GEM_LIGHT_COLOR,
1684 GEM_LIGHT_RANGE,
1685 )
1686 .shadow(),
1687 );
1688 ctx.draw(
1689 Gem.at(Transform::from_scale_rotation_translation(
1690 Vec3::ONE,
1691 Quat::from_rotation_y(t * GEM_SPIN_SPEED),
1692 GEM_POSITION + Vec3::Y * bob,
1693 ))
1694 .material(Material::shaded(GEM_COLOR, 0.7).emissive(GEM_COLOR.dimmed(1.6))),
1695 );
1696 }
1697
1698 /// The player: upright so it always faces the camera about `+Y`,
1699 /// windowed to its facing's row and the walk cycle's current frame.
1700 fn draw_walker(&self, ctx: &mut FrameContext<'_, Keep>, ground: Vec3) {
1701 let step = if self.walk_ticks > 0 {
1702 (self.walk_ticks / TICKS_PER_WALK_FRAME) % WALKER_COLUMNS
1703 } else {
1704 0
1705 };
1706 let cell = Sheet::new(UVec2::new(WALKER_COLUMNS, WALKER_ROWS))
1707 .cell_at(UVec2::new(step, self.facing as u32));
1708 let size = Vec2::new(WALKER_WIDTH, WALKER_HEIGHT);
1709
1710 ctx.draw(
1711 Walker
1712 .at(Transform::from_scale_rotation_translation(
1713 size.extend(1.0),
1714 Quat::IDENTITY,
1715 ground + Vec3::Y * (WALKER_HEIGHT * 0.5),
1716 ))
1717 .upright()
1718 .frame(cell),
1719 );
1720 }
1721
1722 fn frame_overworld(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1723 let drawn_at = self.previous.lerp(self.position, ctx.alpha());
1724 ctx.set_camera(Self::camera(drawn_at, OVERWORLD_CAMERA_OFFSET));
1725 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
1726
1727 self.draw_ground(ctx);
1728 self.draw_hedgerow(ctx);
1729 self.draw_pond(ctx);
1730 self.draw_crates(ctx);
1731 self.draw_well(ctx);
1732 self.draw_flora(ctx);
1733 Self::draw_mouth(ctx, ENTRANCE);
1734 self.draw_walker(ctx, drawn_at);
1735 }
1736
1737 fn frame_cave(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1738 let drawn_at = self.previous.lerp(self.position, ctx.alpha());
1739 let camera = Self::camera(drawn_at, CAVE_CAMERA_OFFSET);
1740 ctx.set_camera(camera);
1741
1742 self.draw_cave_floor(ctx);
1743 self.draw_cave_walls(ctx);
1744 Self::draw_door_wall(ctx, (self.ghost > 0.0).then_some(drawn_at.x), self.ghost);
1745 Self::draw_mouth(ctx, EXIT);
1746 self.draw_torches(ctx);
1747 self.draw_door(ctx, self.ghost);
1748 self.draw_door_frame(ctx, self.ghost);
1749 if !self.gem_taken {
1750 self.draw_gem(ctx);
1751 }
1752 self.draw_walker(ctx, drawn_at);
1753 self.draw_door_prompt(ctx, camera);
1754 }
1755
1756 /// Instructions and the door's interact hint — gathered before `ctx.ui`,
1757 /// which cannot read `ctx`.
1758 fn overlay(&mut self, ctx: &mut FrameContext<'_, Keep>) {
1759 let near_door = self.area == Area::Cave
1760 && !self.door_opening
1761 && self.position.distance(INTERACT_POINT) < INTERACT_RADIUS;
1762 let gem_taken = self.area == Area::Cave && self.gem_taken;
1763 let mut reset_clicked = false;
1764
1765 ctx.ui(|ui| {
1766 egui::Frame::new()
1767 .fill(egui::Color32::from_black_alpha(HUD_BACKDROP))
1768 .inner_margin(HUD_PADDING)
1769 .corner_radius(f32::from(HUD_PADDING))
1770 .show(ui, |ui| {
1771 ui.visuals_mut().override_text_color = Some(egui::Color32::WHITE);
1772 ui.label("wasd / arrows / stick to walk");
1773 if near_door {
1774 ui.label("e / west button to open the door");
1775 }
1776 if gem_taken {
1777 ui.label("gem recovered");
1778 }
1779 ui.label("kept between runs: position, gem, cave");
1780 ui.label("r to reset world");
1781 if ui.button("reset world").clicked() {
1782 reset_clicked = true;
1783 }
1784 });
1785 });
1786
1787 if reset_clicked {
1788 self.reset_requested = true;
1789 }
1790 }Sourcepub fn pixels_per_point(&self) -> f32
pub fn pixels_per_point(&self) -> f32
Physical pixels per logical point of the UI this frame, a fraction
over 1.0 on a dense screen: what a pixel Camera::pixel_of
returns is divided by before the UI draws at it.
Examples found in repository?
682 fn draw_bracket(&self, ctx: &mut FrameContext<'_, Self>, camera: Camera, station: StationKind) {
683 let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
684 let window_size = ctx.window_size();
685 let Some(pixel) = camera.pixel_of(top, window_size) else {
686 return;
687 };
688 let at = logical(pixel, ctx.pixels_per_point());
689
690 let name = ctx.text_layout(station.look().name, egui::FontId::proportional(BODY_SIZE));
691 let (reading_text, number_text) = station.reading(self.elapsed.as_secs_f32());
692 let reading = ctx.text_layout(&reading_text, egui::FontId::monospace(BODY_SIZE));
693 let number = ctx.text_layout(
694 &number_text,
695 egui::FontId::new(NUMBER_SIZE, egui::FontFamily::Name(DISPLAY_FAMILY.into())),
696 );
697
698 ctx.ui(|ui| bracket(ui.painter(), at, name, reading, number));
699 }
700
701 /// A `Prompt` for `Trigger::Hail`, above every `StationKind` but
702 /// `hovered`: what a player presses to reach one, apart from a hover.
703 fn draw_prompts(
704 &self,
705 ctx: &mut FrameContext<'_, Self>,
706 camera: Camera,
707 hovered: Option<StationKind>,
708 ) {
709 let Some(binding) = ctx.bindings(Trigger::Hail).into_iter().next() else {
710 return;
711 };
712 let hint = prompt(&binding);
713 let glyph = ctx.text_layout(&hint.text(), egui::FontId::new(PROMPT_SIZE, hint.family()));
714 let window_size = ctx.window_size();
715 let pixels_per_point = ctx.pixels_per_point();
716
717 ctx.ui(|ui| {
718 let painter = ui.painter();
719 for station in StationKind::ALL {
720 if Some(station) == hovered {
721 continue;
722 }
723 let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
724 let Some(pixel) = camera.pixel_of(top, window_size) else {
725 continue;
726 };
727 let at = logical(pixel, pixels_per_point);
728 let at = egui::pos2(at.x, at.y - PROMPT_LIFT);
729 prompt_at(painter, at, glyph.clone());
730 }
731 });
732 }More examples
1638 fn draw_door_prompt(&self, ctx: &mut FrameContext<'_, Keep>, camera: Camera) {
1639 let near = self.position.distance(INTERACT_POINT) < INTERACT_RADIUS;
1640 let swinging = self.door_opening && self.swing_ticks < DOOR_SWING_TICKS;
1641 let text = if swinging {
1642 "opening"
1643 } else if near && !self.door_opening {
1644 "e opens the door"
1645 } else {
1646 return;
1647 };
1648
1649 let galley = ctx.text_layout(text, egui::FontId::proportional(DOOR_PROMPT_SIZE));
1650 let point = INTERACT_POINT + Vec3::Y * (DOOR_HEIGHT + DOOR_PROMPT_LIFT);
1651 let window_size = ctx.window_size();
1652 let pixels_per_point = ctx.pixels_per_point();
1653 let Some(pixel) = camera.pixel_of(point, window_size) else {
1654 return;
1655 };
1656
1657 ctx.ui(|ui| {
1658 let painter = ui.painter();
1659 let at = logical(pixel, pixels_per_point);
1660 let ink = galley.mesh_bounds;
1661 let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
1662 let backdrop = egui::Rect::from_center_size(
1663 at,
1664 ink.size() + egui::Vec2::splat(DOOR_PROMPT_PADDING * 2.0),
1665 );
1666 painter.rect_filled(
1667 backdrop,
1668 DOOR_PROMPT_PADDING,
1669 egui::Color32::from_black_alpha(DOOR_PROMPT_BACKDROP),
1670 );
1671 painter.galley(pos, galley, DOOR_PROMPT_COLOR);
1672 });
1673 }671 fn draw_prompt(&self, ctx: &mut FrameContext<'_, Board>, camera: Camera, hover: Hover) {
672 let Some(text) = self.click_effect(hover) else {
673 return;
674 };
675 let point = match hover {
676 Hover::CurrentUnit => {
677 let (lift, _) = unit_geometry(self.turn);
678 self.current().position + Vec3::Y * (lift * 2.0 + PROMPT_UNIT_LIFT)
679 }
680 Hover::Tile(tile) => tile_center(tile) + Vec3::Y * PROMPT_TILE_LIFT,
681 Hover::None => return,
682 };
683 let galley = ctx.text_layout(text, egui::FontId::proportional(PROMPT_SIZE));
684 let window_size = ctx.window_size();
685 let pixels_per_point = ctx.pixels_per_point();
686 let Some(pixel) = camera.pixel_of(point, window_size) else {
687 return;
688 };
689 let at = logical(pixel, pixels_per_point);
690 ctx.ui(|ui| {
691 let painter = ui.painter();
692 let ink = galley.mesh_bounds;
693 let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
694 let backdrop = egui::Rect::from_center_size(
695 at,
696 ink.size() + egui::Vec2::splat(PROMPT_PADDING * 2.0),
697 );
698 painter.rect_filled(
699 backdrop,
700 PROMPT_PADDING,
701 egui::Color32::from_black_alpha(PROMPT_BACKDROP),
702 );
703 painter.galley(pos, galley, PROMPT_TEXT_COLOR);
704 });
705 }823 fn draw_prompts(&self, ctx: &mut FrameContext<'_, Scene>, camera: Camera) {
824 let sit_key = ctx
825 .bindings(Button::Interact)
826 .into_iter()
827 .next()
828 .map_or_else(|| "interact".to_owned(), |binding| binding.to_string());
829 let sit = ctx.text_layout(
830 &format!("{sit_key} sits"),
831 egui::FontId::proportional(PROMPT_SIZE),
832 );
833 let hurts = ctx.text_layout("hurts", egui::FontId::proportional(PROMPT_SIZE));
834 let walk_closer = ctx.text_layout("walk closer", egui::FontId::proportional(PROMPT_SIZE));
835
836 let mut prompts = vec![(
837 SCRUBBED_ELF_POSITION + Vec3::Y * (ELF_HEIGHT + PROMPT_LIFT),
838 walk_closer,
839 )];
840 if !self.elf_animator.state().seated() {
841 prompts.push((
842 SEAT_POSITION + Vec3::Y * (SEAT_HEAD_HEIGHT + PROMPT_LIFT),
843 sit,
844 ));
845 }
846 prompts.extend(HURT_PATCHES.map(|patch| (patch + Vec3::Y * PROMPT_LIFT, hurts.clone())));
847
848 let window_size = ctx.window_size();
849 let pixels_per_point = ctx.pixels_per_point();
850 ctx.ui(|ui| {
851 let painter = ui.painter();
852 for (point, galley) in prompts {
853 let Some(pixel) = camera.pixel_of(point, window_size) else {
854 continue;
855 };
856 let at = logical(pixel, pixels_per_point);
857 let ink = galley.mesh_bounds;
858 let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
859 let backdrop = egui::Rect::from_center_size(
860 at,
861 ink.size() + egui::Vec2::splat(PROMPT_PADDING * 2.0),
862 );
863 painter.rect_filled(
864 backdrop,
865 PROMPT_PADDING,
866 egui::Color32::from_black_alpha(PANEL_BACKDROP),
867 );
868 painter.galley(pos, galley, PANEL_TEXT_COLOR);
869 }
870 });
871 }Sourcepub fn text_layout(&self, text: &str, font: FontId) -> Arc<Galley> ⓘ
pub fn text_layout(&self, text: &str, font: FontId) -> Arc<Galley> ⓘ
text laid out in font at no width, so a row ends only where a
\n starts the next one.
Required if you want to size or place what you draw against text: a
box a name has to fit in, a line as wide as the word over it. Its
size() is the width and the height the text takes, in logical
points. One kept past a change of
pixels_per_point still reports the size
the frame that laid it out measured. The ui feature’s own.
Examples found in repository?
682 fn draw_bracket(&self, ctx: &mut FrameContext<'_, Self>, camera: Camera, station: StationKind) {
683 let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
684 let window_size = ctx.window_size();
685 let Some(pixel) = camera.pixel_of(top, window_size) else {
686 return;
687 };
688 let at = logical(pixel, ctx.pixels_per_point());
689
690 let name = ctx.text_layout(station.look().name, egui::FontId::proportional(BODY_SIZE));
691 let (reading_text, number_text) = station.reading(self.elapsed.as_secs_f32());
692 let reading = ctx.text_layout(&reading_text, egui::FontId::monospace(BODY_SIZE));
693 let number = ctx.text_layout(
694 &number_text,
695 egui::FontId::new(NUMBER_SIZE, egui::FontFamily::Name(DISPLAY_FAMILY.into())),
696 );
697
698 ctx.ui(|ui| bracket(ui.painter(), at, name, reading, number));
699 }
700
701 /// A `Prompt` for `Trigger::Hail`, above every `StationKind` but
702 /// `hovered`: what a player presses to reach one, apart from a hover.
703 fn draw_prompts(
704 &self,
705 ctx: &mut FrameContext<'_, Self>,
706 camera: Camera,
707 hovered: Option<StationKind>,
708 ) {
709 let Some(binding) = ctx.bindings(Trigger::Hail).into_iter().next() else {
710 return;
711 };
712 let hint = prompt(&binding);
713 let glyph = ctx.text_layout(&hint.text(), egui::FontId::new(PROMPT_SIZE, hint.family()));
714 let window_size = ctx.window_size();
715 let pixels_per_point = ctx.pixels_per_point();
716
717 ctx.ui(|ui| {
718 let painter = ui.painter();
719 for station in StationKind::ALL {
720 if Some(station) == hovered {
721 continue;
722 }
723 let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
724 let Some(pixel) = camera.pixel_of(top, window_size) else {
725 continue;
726 };
727 let at = logical(pixel, pixels_per_point);
728 let at = egui::pos2(at.x, at.y - PROMPT_LIFT);
729 prompt_at(painter, at, glyph.clone());
730 }
731 });
732 }
733
734 /// The title, a line and the reading, each in a font this game loaded
735 /// rather than egui's own.
736 fn panel(&self, ctx: &mut FrameContext<'_, Self>) {
737 ctx.ui(|ui| {
738 ui.label(styled(
739 "a game's own fonts",
740 egui::FontId::proportional(HEADING_SIZE),
741 ));
742 ui.label(styled(
743 "drawn in Pixel Operator, the game's proportional font",
744 egui::FontId::proportional(BODY_SIZE),
745 ));
746 ui.label(styled(
747 "the readings above each station in Pixel Operator Mono",
748 egui::FontId::monospace(BODY_SIZE),
749 ));
750 });
751 }
752
753 fn draw_dialogue(&self, ctx: &mut FrameContext<'_, Self>) {
754 let Some(dialogue) = &self.dialogue else {
755 return;
756 };
757 let whole = ctx.text_layout(
758 dialogue.current_line(),
759 egui::FontId::proportional(BODY_SIZE),
760 );
761 let size = whole.size();
762 ctx.ui(|ui| dialogue.draw(ui, size));
763 }More examples
1638 fn draw_door_prompt(&self, ctx: &mut FrameContext<'_, Keep>, camera: Camera) {
1639 let near = self.position.distance(INTERACT_POINT) < INTERACT_RADIUS;
1640 let swinging = self.door_opening && self.swing_ticks < DOOR_SWING_TICKS;
1641 let text = if swinging {
1642 "opening"
1643 } else if near && !self.door_opening {
1644 "e opens the door"
1645 } else {
1646 return;
1647 };
1648
1649 let galley = ctx.text_layout(text, egui::FontId::proportional(DOOR_PROMPT_SIZE));
1650 let point = INTERACT_POINT + Vec3::Y * (DOOR_HEIGHT + DOOR_PROMPT_LIFT);
1651 let window_size = ctx.window_size();
1652 let pixels_per_point = ctx.pixels_per_point();
1653 let Some(pixel) = camera.pixel_of(point, window_size) else {
1654 return;
1655 };
1656
1657 ctx.ui(|ui| {
1658 let painter = ui.painter();
1659 let at = logical(pixel, pixels_per_point);
1660 let ink = galley.mesh_bounds;
1661 let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
1662 let backdrop = egui::Rect::from_center_size(
1663 at,
1664 ink.size() + egui::Vec2::splat(DOOR_PROMPT_PADDING * 2.0),
1665 );
1666 painter.rect_filled(
1667 backdrop,
1668 DOOR_PROMPT_PADDING,
1669 egui::Color32::from_black_alpha(DOOR_PROMPT_BACKDROP),
1670 );
1671 painter.galley(pos, galley, DOOR_PROMPT_COLOR);
1672 });
1673 }671 fn draw_prompt(&self, ctx: &mut FrameContext<'_, Board>, camera: Camera, hover: Hover) {
672 let Some(text) = self.click_effect(hover) else {
673 return;
674 };
675 let point = match hover {
676 Hover::CurrentUnit => {
677 let (lift, _) = unit_geometry(self.turn);
678 self.current().position + Vec3::Y * (lift * 2.0 + PROMPT_UNIT_LIFT)
679 }
680 Hover::Tile(tile) => tile_center(tile) + Vec3::Y * PROMPT_TILE_LIFT,
681 Hover::None => return,
682 };
683 let galley = ctx.text_layout(text, egui::FontId::proportional(PROMPT_SIZE));
684 let window_size = ctx.window_size();
685 let pixels_per_point = ctx.pixels_per_point();
686 let Some(pixel) = camera.pixel_of(point, window_size) else {
687 return;
688 };
689 let at = logical(pixel, pixels_per_point);
690 ctx.ui(|ui| {
691 let painter = ui.painter();
692 let ink = galley.mesh_bounds;
693 let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
694 let backdrop = egui::Rect::from_center_size(
695 at,
696 ink.size() + egui::Vec2::splat(PROMPT_PADDING * 2.0),
697 );
698 painter.rect_filled(
699 backdrop,
700 PROMPT_PADDING,
701 egui::Color32::from_black_alpha(PROMPT_BACKDROP),
702 );
703 painter.galley(pos, galley, PROMPT_TEXT_COLOR);
704 });
705 }823 fn draw_prompts(&self, ctx: &mut FrameContext<'_, Scene>, camera: Camera) {
824 let sit_key = ctx
825 .bindings(Button::Interact)
826 .into_iter()
827 .next()
828 .map_or_else(|| "interact".to_owned(), |binding| binding.to_string());
829 let sit = ctx.text_layout(
830 &format!("{sit_key} sits"),
831 egui::FontId::proportional(PROMPT_SIZE),
832 );
833 let hurts = ctx.text_layout("hurts", egui::FontId::proportional(PROMPT_SIZE));
834 let walk_closer = ctx.text_layout("walk closer", egui::FontId::proportional(PROMPT_SIZE));
835
836 let mut prompts = vec![(
837 SCRUBBED_ELF_POSITION + Vec3::Y * (ELF_HEIGHT + PROMPT_LIFT),
838 walk_closer,
839 )];
840 if !self.elf_animator.state().seated() {
841 prompts.push((
842 SEAT_POSITION + Vec3::Y * (SEAT_HEAD_HEIGHT + PROMPT_LIFT),
843 sit,
844 ));
845 }
846 prompts.extend(HURT_PATCHES.map(|patch| (patch + Vec3::Y * PROMPT_LIFT, hurts.clone())));
847
848 let window_size = ctx.window_size();
849 let pixels_per_point = ctx.pixels_per_point();
850 ctx.ui(|ui| {
851 let painter = ui.painter();
852 for (point, galley) in prompts {
853 let Some(pixel) = camera.pixel_of(point, window_size) else {
854 continue;
855 };
856 let at = logical(pixel, pixels_per_point);
857 let ink = galley.mesh_bounds;
858 let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
859 let backdrop = egui::Rect::from_center_size(
860 at,
861 ink.size() + egui::Vec2::splat(PROMPT_PADDING * 2.0),
862 );
863 painter.rect_filled(
864 backdrop,
865 PROMPT_PADDING,
866 egui::Color32::from_black_alpha(PANEL_BACKDROP),
867 );
868 painter.galley(pos, galley, PANEL_TEXT_COLOR);
869 }
870 });
871 }Sourcepub fn ui_wants_pointer(&self) -> bool
pub fn ui_wants_pointer(&self) -> bool
Whether the UI took the pointer last frame. Always false without the
ui feature.
Examples found in repository?
766 fn hovered(ctx: &FrameContext<'_, Self>) -> Option<StationKind> {
767 if ctx.ui_wants_pointer() {
768 return None;
769 }
770 hit_station(
771 ctx.last_camera()
772 .ray_through(ctx.pointer(), ctx.window_size()),
773 )
774 }
775
776 /// A held [`Trigger::Hail`] turns the camera by the pointer's own
777 /// motion; the wheel zooms it.
778 fn steer(&mut self, ctx: &mut FrameContext<'_, Self>) {
779 if !ctx.ui_wants_pointer() && ctx.down(Trigger::Hail) {
780 self.orbit.turn(ctx.axis2(Turn::Look));
781 }
782 let wheel = ctx.axis(Zoom::Wheel);
783 if !ctx.ui_wants_pointer() && wheel != 0.0 {
784 self.orbit.zoom(ZOOM_STEP.powf(wheel));
785 }
786 }More examples
459 fn hovered(&self, ctx: &FrameContext<'_, Board>) -> Hover {
460 if ctx.ui_wants_pointer() {
461 return Hover::None;
462 }
463 let ray = ctx
464 .last_camera()
465 .ray_through(ctx.pointer(), ctx.window_size());
466
467 if self.current().target.is_none() {
468 let (_, half) = unit_geometry(self.turn);
469 let center = self.current().position;
470 if ray.hit_aabb(center - half, center + half).is_some() {
471 return Hover::CurrentUnit;
472 }
473 }
474 let Some(distance) = ray.hit_plane(ray::Plane {
475 point: Vec3::ZERO,
476 normal: Vec3::Y,
477 }) else {
478 return Hover::None;
479 };
480 match tile_at(ray.at(distance)) {
481 Some(tile) => Hover::Tile(tile),
482 None => Hover::None,
483 }
484 }359 fn handle_camera(&mut self, ctx: &mut FrameContext<'_, Self>, elapsed: f32) {
360 if ctx.ui_wants_pointer() || ctx.ui_wants_keyboard() {
361 return;
362 }
363 let pan = ctx.axis2(Motion::Pan);
364 let wheel = ctx.axis(Height::Wheel);
365 let look = if ctx.down(Drag::Turn) {
366 ctx.axis2(Motion::Look)
367 } else {
368 Vec2::ZERO
369 };
370 if pan == Vec2::ZERO && wheel == 0.0 && look == Vec2::ZERO {
371 return;
372 }
373
374 let player = self.player.get_or_insert_with(|| {
375 let eye = Self::orbit_eye(elapsed);
376 let forward = (Vec3::ZERO - eye).normalize();
377 Player {
378 eye,
379 yaw: (-forward.x).atan2(-forward.z),
380 pitch: forward.y.asin(),
381 }
382 });
383
384 player.yaw -= look.x;
385 player.pitch = (player.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
386
387 let forward = Vec3::new(-player.yaw.sin(), 0.0, -player.yaw.cos());
388 let right = Vec3::new(player.yaw.cos(), 0.0, -player.yaw.sin());
389 player.eye += (forward * pan.y + right * pan.x) * PAN_SPEED * ctx.dt().as_secs_f32();
390 player.eye.y =
391 (player.eye.y + wheel * WHEEL_STEP).clamp(MIN_CAMERA_HEIGHT, MAX_CAMERA_HEIGHT);
392 }802 fn fly_camera(&mut self, ctx: &mut FrameContext<'_, Self>) {
803 if !ctx.ui_wants_pointer() && ctx.down(Move::Look) {
804 let look = ctx.axis2(Turn::Look);
805 self.yaw -= look.x;
806 self.pitch = (self.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
807 }
808
809 let wheel = ctx.axis(Speed::Wheel);
810 if !ctx.ui_wants_pointer() && wheel != 0.0 {
811 self.speed_scale =
812 (self.speed_scale * SPEED_STEP.powf(wheel)).clamp(MIN_SPEED_SCALE, MAX_SPEED_SCALE);
813 }
814
815 let forward = self.forward();
816 let right = Vec3::new(self.yaw.cos(), 0.0, -self.yaw.sin());
817 let mut move_by = Vec3::ZERO;
818 if ctx.down(Move::Forward) {
819 move_by += forward;
820 }
821 if ctx.down(Move::Back) {
822 move_by -= forward;
823 }
824 if ctx.down(Move::Right) {
825 move_by += right;
826 }
827 if ctx.down(Move::Left) {
828 move_by -= right;
829 }
830 if ctx.down(Move::Up) {
831 move_by += Vec3::Y;
832 }
833 if ctx.down(Move::Down) {
834 move_by -= Vec3::Y;
835 }
836 if move_by.length_squared() > 1.0 {
837 move_by = move_by.normalize();
838 }
839
840 self.eye += move_by * MOVE_SPEED * self.speed_scale * ctx.dt().as_secs_f32();
841 self.eye.y = self.eye.y.max(MIN_EYE_HEIGHT);
842 }Sourcepub fn ui_wants_keyboard(&self) -> bool
pub fn ui_wants_keyboard(&self) -> bool
Whether the UI took the keyboard last frame. Always false without the
ui feature.
Examples found in repository?
359 fn handle_camera(&mut self, ctx: &mut FrameContext<'_, Self>, elapsed: f32) {
360 if ctx.ui_wants_pointer() || ctx.ui_wants_keyboard() {
361 return;
362 }
363 let pan = ctx.axis2(Motion::Pan);
364 let wheel = ctx.axis(Height::Wheel);
365 let look = if ctx.down(Drag::Turn) {
366 ctx.axis2(Motion::Look)
367 } else {
368 Vec2::ZERO
369 };
370 if pan == Vec2::ZERO && wheel == 0.0 && look == Vec2::ZERO {
371 return;
372 }
373
374 let player = self.player.get_or_insert_with(|| {
375 let eye = Self::orbit_eye(elapsed);
376 let forward = (Vec3::ZERO - eye).normalize();
377 Player {
378 eye,
379 yaw: (-forward.x).atan2(-forward.z),
380 pitch: forward.y.asin(),
381 }
382 });
383
384 player.yaw -= look.x;
385 player.pitch = (player.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
386
387 let forward = Vec3::new(-player.yaw.sin(), 0.0, -player.yaw.cos());
388 let right = Vec3::new(player.yaw.cos(), 0.0, -player.yaw.sin());
389 player.eye += (forward * pan.y + right * pan.x) * PAN_SPEED * ctx.dt().as_secs_f32();
390 player.eye.y =
391 (player.eye.y + wheel * WHEEL_STEP).clamp(MIN_CAMERA_HEIGHT, MAX_CAMERA_HEIGHT);
392 }More examples
765 fn menu(&mut self, ctx: &mut FrameContext<'_, Breakout>, title: &str, over: bool) {
766 let mut clicked = false;
767 let mut quit = false;
768
769 // `ctx.ui` cannot borrow `ctx`, so anything the controls list needs is
770 // read first and applied after.
771 let buttons: Vec<(Button, String)> = Button::all()
772 .into_iter()
773 .map(|action| (action, bindings_text(ctx.bindings(action))))
774 .collect();
775 let axes: Vec<(Move, String)> = Move::all()
776 .into_iter()
777 .map(|action| (action, bindings_text(ctx.bindings(action))))
778 .collect();
779 let listening = self.listening;
780 let actuated_button = (!ctx.ui_wants_keyboard())
781 .then(|| ctx.actuated_button())
782 .flatten();
783 let actuated_axis = (!ctx.ui_wants_keyboard())
784 .then(|| ctx.actuated_axis())
785 .flatten();
786 let mut reset = None;
787
788 ctx.ui(|ui| {
789 egui::Window::new(title)
790 .collapsible(false)
791 .resizable(false)
792 .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
793 .show(ui.ctx(), |ui| {
794 if over {
795 ui.label(format!("score {}", self.score));
796 }
797 if !over {
798 ui.add(
799 egui::Slider::new(&mut self.master_volume, 0.0..=1.0).text("volume"),
800 );
801 if ui.button("resume").clicked() {
802 self.paused = false;
803 clicked = true;
804 }
805 ui.separator();
806 ui.heading("controls");
807 for (action, text) in &buttons {
808 controls_row(
809 ui,
810 action.name(),
811 text,
812 listening == Some(Listening::Button(*action)),
813 &mut self.listening,
814 Listening::Button(*action),
815 &mut reset,
816 );
817 }
818 for (action, text) in &axes {
819 controls_row(
820 ui,
821 action.name(),
822 text,
823 listening == Some(Listening::Move(*action)),
824 &mut self.listening,
825 Listening::Move(*action),
826 &mut reset,
827 );
828 }
829 }
830 if ui.button("restart").clicked() {
831 self.restart();
832 clicked = true;
833 }
834 if ui.button("quit").clicked() {
835 quit = true;
836 }
837 });
838 });
839
840 match (self.listening, actuated_button, actuated_axis) {
841 (Some(Listening::Button(action)), Some(binding), _) => {
842 ctx.rebind(action, vec![binding]);
843 self.listening = None;
844 }
845 (Some(Listening::Move(action)), _, Some(binding)) => {
846 ctx.rebind(action, vec![binding]);
847 self.listening = None;
848 }
849 _ => {}
850 }
851 match reset {
852 Some(Listening::Button(action)) => ctx.rebind(action, action.bindings()),
853 Some(Listening::Move(action)) => ctx.rebind(action, action.bindings()),
854 None => {}
855 }
856
857 if clicked {
858 ctx.play(Sound::Click);
859 }
860 if quit {
861 ctx.close();
862 }
863 }238 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239 // `ctx.ui` cannot borrow `ctx`, so every reading and capture query
240 // is taken first, and `rebind` is applied once the closure returns.
241 let buttons: Vec<_> = ButtonAction::all()
242 .into_iter()
243 .map(|action| {
244 (
245 action,
246 bindings_text(ctx.bindings(action)),
247 ctx.down(action),
248 ctx.pressed(action),
249 ctx.released(action),
250 ctx.clicks(action),
251 )
252 })
253 .collect();
254 let axes: Vec<_> = AxisAction::all()
255 .into_iter()
256 .map(|action| {
257 (
258 action,
259 bindings_text(ctx.bindings(action)),
260 ctx.axis(action),
261 )
262 })
263 .collect();
264 let axes2: Vec<_> = Axis2Action::all()
265 .into_iter()
266 .map(|action| {
267 (
268 action,
269 bindings_text(ctx.bindings(action)),
270 ctx.axis2(action),
271 )
272 })
273 .collect();
274
275 let capturing = !ctx.ui_wants_keyboard();
276 let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277 let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278 let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279 if actuated_button.is_some() {
280 self.last_button = actuated_button;
281 }
282 if actuated_axis.is_some() {
283 self.last_axis = actuated_axis;
284 }
285 if actuated_axis2.is_some() {
286 self.last_axis2 = actuated_axis2;
287 }
288 let pointer = ctx.pointer();
289 let mut edits = RowEdits {
290 listening: self.listening,
291 start_listening: None,
292 cancel: false,
293 reset: None,
294 };
295
296 ctx.ui(|ui| {
297 egui::CentralPanel::default().show(ui, |ui| {
298 ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299 ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300 ui.label("rebinds persist across runs");
301 ui.label(format!(
302 "last captured: button {}, pad axis {}, pad stick {}",
303 text_of(self.last_button),
304 text_of(self.last_axis),
305 text_of(self.last_axis2),
306 ));
307 ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308 ui.separator();
309
310 ui.horizontal(|ui| {
311 ui.vertical(|ui| {
312 ui.heading("buttons");
313 egui::Grid::new("buttons-grid")
314 .num_columns(5)
315 .spacing([6.0, 2.0])
316 .show(ui, |ui| {
317 for (action, bindings, down, pressed, released, clicks) in &buttons
318 {
319 let control = Control::Button(*action);
320 ui.label(action.name());
321 ui.label(bindings);
322 ui.horizontal(|ui| {
323 mark(ui, "down", *down);
324 mark(ui, "pressed", *pressed);
325 mark(ui, "released", *released);
326 ui.label(format!("clicks {clicks}"));
327 });
328 rebind_cell(ui, control, &mut edits);
329 reset_cell(ui, control, &mut edits);
330 ui.end_row();
331 }
332 });
333 });
334
335 ui.separator();
336
337 ui.vertical(|ui| {
338 egui::Grid::new("axes-grid")
339 .num_columns(5)
340 .spacing([6.0, 2.0])
341 .show(ui, |ui| {
342 ui.heading("axes");
343 ui.end_row();
344 for (action, bindings, value) in &axes {
345 let control = Control::Axis(*action);
346 ui.label(action.name());
347 ui.label(bindings);
348 axis_bar(ui, *value);
349 rebind_cell(ui, control, &mut edits);
350 reset_cell(ui, control, &mut edits);
351 ui.end_row();
352 }
353
354 ui.heading("vectors");
355 ui.end_row();
356 for (action, bindings, value) in &axes2 {
357 let control = Control::Axis2(*action);
358 ui.label(action.name());
359 ui.label(bindings);
360 axis2_dot(ui, *value);
361 rebind_cell(ui, control, &mut edits);
362 reset_cell(ui, control, &mut edits);
363 ui.end_row();
364 }
365 });
366 });
367 });
368 });
369 });
370
371 if edits.cancel {
372 self.listening = None;
373 }
374 if let Some(control) = edits.start_listening {
375 self.listening = Some(control);
376 }
377 if let Some(control) = edits.reset {
378 match control {
379 Control::Button(action) => ctx.rebind(action, action.bindings()),
380 Control::Axis(action) => ctx.rebind(action, action.bindings()),
381 Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382 }
383 }
384 match (
385 self.listening,
386 actuated_button,
387 actuated_axis,
388 actuated_axis2,
389 ) {
390 (Some(Control::Button(action)), Some(binding), _, _) => {
391 ctx.rebind(action, vec![binding]);
392 self.listening = None;
393 }
394 (Some(Control::Axis(action)), _, Some(binding), _) => {
395 ctx.rebind(action, vec![binding]);
396 self.listening = None;
397 }
398 (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399 ctx.rebind(action, vec![binding]);
400 self.listening = None;
401 }
402 _ => {}
403 }
404 }Sourcepub fn window_size(&self) -> UVec2
pub fn window_size(&self) -> UVec2
The window’s drawing area, in physical pixels; zero while minimized.
Examples found in repository?
459 fn hovered(&self, ctx: &FrameContext<'_, Board>) -> Hover {
460 if ctx.ui_wants_pointer() {
461 return Hover::None;
462 }
463 let ray = ctx
464 .last_camera()
465 .ray_through(ctx.pointer(), ctx.window_size());
466
467 if self.current().target.is_none() {
468 let (_, half) = unit_geometry(self.turn);
469 let center = self.current().position;
470 if ray.hit_aabb(center - half, center + half).is_some() {
471 return Hover::CurrentUnit;
472 }
473 }
474 let Some(distance) = ray.hit_plane(ray::Plane {
475 point: Vec3::ZERO,
476 normal: Vec3::Y,
477 }) else {
478 return Hover::None;
479 };
480 match tile_at(ray.at(distance)) {
481 Some(tile) => Hover::Tile(tile),
482 None => Hover::None,
483 }
484 }
485
486 /// Whether a selected unit could move to `tile`: on the board, and
487 /// standing under neither unit.
488 fn reachable(&self, tile: (i32, i32)) -> bool {
489 tile != self.current().tile && tile != self.other().tile
490 }
491
492 fn draw_board(&self, ctx: &mut FrameContext<'_, Board>, hover: Hover) {
493 let scale = Vec3::new(TILE_SIZE - TILE_GAP, TILE_THICKNESS, TILE_SIZE - TILE_GAP);
494 for col in 0..BOARD_TILES {
495 for row in 0..BOARD_TILES {
496 let tile = (col, row);
497 let center = tile_center(tile) - Vec3::Y * (TILE_THICKNESS * 0.5);
498 let reachable = self.selected && self.reachable(tile);
499 let hovered = self.selected && hover == Hover::Tile(tile);
500 let color = if hovered {
501 if reachable {
502 HOVER_REACHABLE_TILE
503 } else {
504 HOVER_BLOCKED_TILE
505 }
506 } else if (col + row) % 2 == 0 {
507 LIGHT_TILE
508 } else {
509 DARK_TILE
510 };
511 ctx.draw(
512 Cube.at(Transform::from_scale_rotation_translation(
513 scale,
514 Quat::IDENTITY,
515 center,
516 ))
517 .material(Material::lit(color)),
518 );
519 if reachable && !hovered {
520 self.draw_reachable_mark(ctx, tile);
521 }
522 }
523 }
524 }
525
526 /// A mark over a reachable tile, its own tone apart from the
527 /// checker's tone and the hover tone, so the checker still reads
528 /// under it.
529 fn draw_reachable_mark(&self, ctx: &mut FrameContext<'_, Board>, tile: (i32, i32)) {
530 let center = tile_center(tile) + Vec3::Y * REACHABLE_MARK_LIFT;
531 ctx.draw(
532 Plane
533 .at(Transform::from_scale_rotation_translation(
534 Vec3::new(
535 TILE_SIZE * REACHABLE_MARK_SCALE,
536 1.0,
537 TILE_SIZE * REACHABLE_MARK_SCALE,
538 ),
539 Quat::IDENTITY,
540 center,
541 ))
542 .material(Material::color(REACHABLE_MARK)),
543 );
544 }
545
546 /// A mark bright enough to read past the sprite's own tint under the
547 /// selected unit, or a smaller, dim one under the unit whose turn it
548 /// is while nothing is selected — so the current unit reads from the
549 /// ground alone.
550 fn draw_current_mark(&self, ctx: &mut FrameContext<'_, Board>) {
551 let (color, scale) = if self.selected {
552 (CURRENT_MARK, CURRENT_MARK_SCALE)
553 } else {
554 (TURN_MARK, TURN_MARK_SCALE)
555 };
556 let center = tile_center(self.current().tile) + Vec3::Y * REACHABLE_MARK_LIFT;
557 ctx.draw(
558 Plane
559 .at(Transform::from_scale_rotation_translation(
560 Vec3::new(TILE_SIZE * scale, 1.0, TILE_SIZE * scale),
561 Quat::IDENTITY,
562 center,
563 ))
564 .material(Material::color(color)),
565 );
566 }
567
568 fn draw_rocks(&self, ctx: &mut FrameContext<'_, Board>) {
569 for &(x, z, seed, scale) in &ROCKS {
570 let angle = hash_signed(seed, 99) * core::f32::consts::PI;
571 ctx.draw(
572 Rock { seed }
573 .at(Transform::from_scale_rotation_translation(
574 Vec3::splat(scale),
575 Quat::from_rotation_y(angle),
576 Vec3::new(x, 0.5 * scale, z),
577 ))
578 .material(Material::lit(ROCK_COLOR)),
579 );
580 }
581 }
582
583 fn draw_sprite(&self, ctx: &mut FrameContext<'_, Board>, hover: Hover) {
584 let position = self.sprite.previous.lerp(self.sprite.position, ctx.alpha());
585 let current = self.turn == Turn::Sprite;
586 let (tint, glow) = if current && self.selected {
587 (SELECTED_TINT, SELECTED_GLOW)
588 } else if current && hover == Hover::CurrentUnit {
589 (HOVER_TINT, HOVER_GLOW)
590 } else if current {
591 (TURN_TINT, TURN_GLOW)
592 } else {
593 (Color::WHITE, Color::BLACK)
594 };
595 ctx.draw(
596 Sprite
597 .at(Transform::from_scale_rotation_translation(
598 Vec3::new(SPRITE_WIDTH, SPRITE_HEIGHT, 1.0),
599 Quat::IDENTITY,
600 position,
601 ))
602 .upright()
603 .frame(sprite_frame(self.sprite.facing_right))
604 .material(Material::lit(tint).cutout().emissive(glow)),
605 );
606 }
607
608 fn draw_block(&self, ctx: &mut FrameContext<'_, Board>, hover: Hover) {
609 let position = self.block.previous.lerp(self.block.position, ctx.alpha());
610 let current = self.turn == Turn::Block;
611 let (color, glow) = if current && self.selected {
612 (SELECTED_TINT, SELECTED_GLOW)
613 } else if current && hover == Hover::CurrentUnit {
614 (HOVER_TINT, HOVER_GLOW)
615 } else if current {
616 (BLOCK_TURN, TURN_GLOW)
617 } else {
618 (BLOCK_IDLE, Color::BLACK)
619 };
620 ctx.draw(
621 Cube.at(Transform::from_scale_rotation_translation(
622 Vec3::splat(BLOCK_SIZE),
623 Quat::IDENTITY,
624 position,
625 ))
626 .material(Material::lit(color).emissive(glow)),
627 );
628 }
629
630 fn draw_ground(&self, ctx: &mut FrameContext<'_, Board>) {
631 ctx.draw(
632 Plane
633 .at(Transform::from_scale_rotation_translation(
634 Vec3::new(GROUND_HALF * 2.0, 1.0, GROUND_HALF * 2.0),
635 Quat::IDENTITY,
636 Vec3::new(0.0, GROUND_Y, 0.0),
637 ))
638 .material(Material::lit(GROUND_COLOR)),
639 );
640 }
641
642 /// Whose turn it is, and what a click does next.
643 fn overlay(&self, ctx: &mut FrameContext<'_, Board>) {
644 ctx.ui(|ui| {
645 ui.label(match self.turn {
646 Turn::Sprite => "the sprite unit's turn",
647 Turn::Block => "the block unit's turn",
648 });
649 ui.label(if self.selected {
650 "click a marked tile to order the move"
651 } else {
652 "click the glowing unit to select it"
653 });
654 });
655 }
656
657 /// What a click at `hover` does, named for the player; `None` where a
658 /// click has no effect.
659 fn click_effect(&self, hover: Hover) -> Option<&'static str> {
660 match hover {
661 Hover::CurrentUnit if self.selected => Some("deselect"),
662 Hover::CurrentUnit => Some("select"),
663 Hover::Tile(tile) if self.selected && self.reachable(tile) => Some("move here"),
664 Hover::Tile(_) if self.selected => Some("occupied"),
665 _ => None,
666 }
667 }
668
669 /// A prompt beside the pointer's target, naming what its click does;
670 /// absent where [`Self::click_effect`] reads no effect.
671 fn draw_prompt(&self, ctx: &mut FrameContext<'_, Board>, camera: Camera, hover: Hover) {
672 let Some(text) = self.click_effect(hover) else {
673 return;
674 };
675 let point = match hover {
676 Hover::CurrentUnit => {
677 let (lift, _) = unit_geometry(self.turn);
678 self.current().position + Vec3::Y * (lift * 2.0 + PROMPT_UNIT_LIFT)
679 }
680 Hover::Tile(tile) => tile_center(tile) + Vec3::Y * PROMPT_TILE_LIFT,
681 Hover::None => return,
682 };
683 let galley = ctx.text_layout(text, egui::FontId::proportional(PROMPT_SIZE));
684 let window_size = ctx.window_size();
685 let pixels_per_point = ctx.pixels_per_point();
686 let Some(pixel) = camera.pixel_of(point, window_size) else {
687 return;
688 };
689 let at = logical(pixel, pixels_per_point);
690 ctx.ui(|ui| {
691 let painter = ui.painter();
692 let ink = galley.mesh_bounds;
693 let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
694 let backdrop = egui::Rect::from_center_size(
695 at,
696 ink.size() + egui::Vec2::splat(PROMPT_PADDING * 2.0),
697 );
698 painter.rect_filled(
699 backdrop,
700 PROMPT_PADDING,
701 egui::Color32::from_black_alpha(PROMPT_BACKDROP),
702 );
703 painter.galley(pos, galley, PROMPT_TEXT_COLOR);
704 });
705 }More examples
682 fn draw_bracket(&self, ctx: &mut FrameContext<'_, Self>, camera: Camera, station: StationKind) {
683 let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
684 let window_size = ctx.window_size();
685 let Some(pixel) = camera.pixel_of(top, window_size) else {
686 return;
687 };
688 let at = logical(pixel, ctx.pixels_per_point());
689
690 let name = ctx.text_layout(station.look().name, egui::FontId::proportional(BODY_SIZE));
691 let (reading_text, number_text) = station.reading(self.elapsed.as_secs_f32());
692 let reading = ctx.text_layout(&reading_text, egui::FontId::monospace(BODY_SIZE));
693 let number = ctx.text_layout(
694 &number_text,
695 egui::FontId::new(NUMBER_SIZE, egui::FontFamily::Name(DISPLAY_FAMILY.into())),
696 );
697
698 ctx.ui(|ui| bracket(ui.painter(), at, name, reading, number));
699 }
700
701 /// A `Prompt` for `Trigger::Hail`, above every `StationKind` but
702 /// `hovered`: what a player presses to reach one, apart from a hover.
703 fn draw_prompts(
704 &self,
705 ctx: &mut FrameContext<'_, Self>,
706 camera: Camera,
707 hovered: Option<StationKind>,
708 ) {
709 let Some(binding) = ctx.bindings(Trigger::Hail).into_iter().next() else {
710 return;
711 };
712 let hint = prompt(&binding);
713 let glyph = ctx.text_layout(&hint.text(), egui::FontId::new(PROMPT_SIZE, hint.family()));
714 let window_size = ctx.window_size();
715 let pixels_per_point = ctx.pixels_per_point();
716
717 ctx.ui(|ui| {
718 let painter = ui.painter();
719 for station in StationKind::ALL {
720 if Some(station) == hovered {
721 continue;
722 }
723 let top = station.center() + Vec3::Y * (STATION_SIZE.y * 0.5);
724 let Some(pixel) = camera.pixel_of(top, window_size) else {
725 continue;
726 };
727 let at = logical(pixel, pixels_per_point);
728 let at = egui::pos2(at.x, at.y - PROMPT_LIFT);
729 prompt_at(painter, at, glyph.clone());
730 }
731 });
732 }
733
734 /// The title, a line and the reading, each in a font this game loaded
735 /// rather than egui's own.
736 fn panel(&self, ctx: &mut FrameContext<'_, Self>) {
737 ctx.ui(|ui| {
738 ui.label(styled(
739 "a game's own fonts",
740 egui::FontId::proportional(HEADING_SIZE),
741 ));
742 ui.label(styled(
743 "drawn in Pixel Operator, the game's proportional font",
744 egui::FontId::proportional(BODY_SIZE),
745 ));
746 ui.label(styled(
747 "the readings above each station in Pixel Operator Mono",
748 egui::FontId::monospace(BODY_SIZE),
749 ));
750 });
751 }
752
753 fn draw_dialogue(&self, ctx: &mut FrameContext<'_, Self>) {
754 let Some(dialogue) = &self.dialogue else {
755 return;
756 };
757 let whole = ctx.text_layout(
758 dialogue.current_line(),
759 egui::FontId::proportional(BODY_SIZE),
760 );
761 let size = whole.size();
762 ctx.ui(|ui| dialogue.draw(ui, size));
763 }
764
765 /// The `StationKind` under the pointer, `None` while the UI holds it.
766 fn hovered(ctx: &FrameContext<'_, Self>) -> Option<StationKind> {
767 if ctx.ui_wants_pointer() {
768 return None;
769 }
770 hit_station(
771 ctx.last_camera()
772 .ray_through(ctx.pointer(), ctx.window_size()),
773 )
774 }1638 fn draw_door_prompt(&self, ctx: &mut FrameContext<'_, Keep>, camera: Camera) {
1639 let near = self.position.distance(INTERACT_POINT) < INTERACT_RADIUS;
1640 let swinging = self.door_opening && self.swing_ticks < DOOR_SWING_TICKS;
1641 let text = if swinging {
1642 "opening"
1643 } else if near && !self.door_opening {
1644 "e opens the door"
1645 } else {
1646 return;
1647 };
1648
1649 let galley = ctx.text_layout(text, egui::FontId::proportional(DOOR_PROMPT_SIZE));
1650 let point = INTERACT_POINT + Vec3::Y * (DOOR_HEIGHT + DOOR_PROMPT_LIFT);
1651 let window_size = ctx.window_size();
1652 let pixels_per_point = ctx.pixels_per_point();
1653 let Some(pixel) = camera.pixel_of(point, window_size) else {
1654 return;
1655 };
1656
1657 ctx.ui(|ui| {
1658 let painter = ui.painter();
1659 let at = logical(pixel, pixels_per_point);
1660 let ink = galley.mesh_bounds;
1661 let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
1662 let backdrop = egui::Rect::from_center_size(
1663 at,
1664 ink.size() + egui::Vec2::splat(DOOR_PROMPT_PADDING * 2.0),
1665 );
1666 painter.rect_filled(
1667 backdrop,
1668 DOOR_PROMPT_PADDING,
1669 egui::Color32::from_black_alpha(DOOR_PROMPT_BACKDROP),
1670 );
1671 painter.galley(pos, galley, DOOR_PROMPT_COLOR);
1672 });
1673 }823 fn draw_prompts(&self, ctx: &mut FrameContext<'_, Scene>, camera: Camera) {
824 let sit_key = ctx
825 .bindings(Button::Interact)
826 .into_iter()
827 .next()
828 .map_or_else(|| "interact".to_owned(), |binding| binding.to_string());
829 let sit = ctx.text_layout(
830 &format!("{sit_key} sits"),
831 egui::FontId::proportional(PROMPT_SIZE),
832 );
833 let hurts = ctx.text_layout("hurts", egui::FontId::proportional(PROMPT_SIZE));
834 let walk_closer = ctx.text_layout("walk closer", egui::FontId::proportional(PROMPT_SIZE));
835
836 let mut prompts = vec![(
837 SCRUBBED_ELF_POSITION + Vec3::Y * (ELF_HEIGHT + PROMPT_LIFT),
838 walk_closer,
839 )];
840 if !self.elf_animator.state().seated() {
841 prompts.push((
842 SEAT_POSITION + Vec3::Y * (SEAT_HEAD_HEIGHT + PROMPT_LIFT),
843 sit,
844 ));
845 }
846 prompts.extend(HURT_PATCHES.map(|patch| (patch + Vec3::Y * PROMPT_LIFT, hurts.clone())));
847
848 let window_size = ctx.window_size();
849 let pixels_per_point = ctx.pixels_per_point();
850 ctx.ui(|ui| {
851 let painter = ui.painter();
852 for (point, galley) in prompts {
853 let Some(pixel) = camera.pixel_of(point, window_size) else {
854 continue;
855 };
856 let at = logical(pixel, pixels_per_point);
857 let ink = galley.mesh_bounds;
858 let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
859 let backdrop = egui::Rect::from_center_size(
860 at,
861 ink.size() + egui::Vec2::splat(PROMPT_PADDING * 2.0),
862 );
863 painter.rect_filled(
864 backdrop,
865 PROMPT_PADDING,
866 egui::Color32::from_black_alpha(PANEL_BACKDROP),
867 );
868 painter.galley(pos, galley, PANEL_TEXT_COLOR);
869 }
870 });
871 }454 fn controls(&mut self, ctx: &mut FrameContext<'_, Self>, camera: &Camera) {
455 let submitted = self.field.len();
456 let seeds = self.applied_seed_count;
457 let average_ms = self.frame_times.average_ms();
458 let fps = if average_ms > 0.0 {
459 1000.0 / average_ms
460 } else {
461 0.0
462 };
463 let elapsed = ctx.elapsed().as_secs_f32();
464 let (in_view, sampled) = self.count_in_view(camera, ctx.window_size());
465
466 ctx.ui(|ui| {
467 egui::Frame::new()
468 .fill(egui::Color32::from_gray(24))
469 .inner_margin(PANEL_PADDING)
470 .corner_radius(f32::from(PANEL_PADDING))
471 .show(ui, |ui| {
472 ui.add(
473 egui::Slider::new(
474 &mut self.settings.instance_count,
475 MIN_INSTANCE_COUNT..=MAX_INSTANCE_COUNT,
476 )
477 .text("instance count"),
478 );
479 ui.add(
480 egui::Slider::new(
481 &mut self.settings.seed_count,
482 MIN_SEED_COUNT..=MAX_SEED_COUNT,
483 )
484 .text("distinct seeds"),
485 );
486 ui.checkbox(&mut self.settings.sun_shadow, "sun shadow");
487 ui.checkbox(&mut self.settings.moving, "moving fraction");
488 ui.separator();
489 ui.label(format!("instances submitted {submitted}"));
490 if sampled {
491 ui.label(format!("in view, sampled {in_view}"));
492 } else {
493 ui.label(format!("instances in view {in_view}"));
494 }
495 ui.label(format!("distinct seeds {seeds}"));
496 ui.label(format!("frame time {average_ms:.2}ms, {fps:.0} fps"));
497 ui.label(format!("elapsed {elapsed:.1}s"));
498 });
499 });
500 }Sourcepub fn last_camera(&self) -> Camera
pub fn last_camera(&self) -> Camera
The camera the last drawn frame was viewed from; Camera::default
before the first frame.
Required if you want a ray through a pixel: the player points at what was last drawn, not at what this frame has set since.
Examples found in repository?
More examples
459 fn hovered(&self, ctx: &FrameContext<'_, Board>) -> Hover {
460 if ctx.ui_wants_pointer() {
461 return Hover::None;
462 }
463 let ray = ctx
464 .last_camera()
465 .ray_through(ctx.pointer(), ctx.window_size());
466
467 if self.current().target.is_none() {
468 let (_, half) = unit_geometry(self.turn);
469 let center = self.current().position;
470 if ray.hit_aabb(center - half, center + half).is_some() {
471 return Hover::CurrentUnit;
472 }
473 }
474 let Some(distance) = ray.hit_plane(ray::Plane {
475 point: Vec3::ZERO,
476 normal: Vec3::Y,
477 }) else {
478 return Hover::None;
479 };
480 match tile_at(ray.at(distance)) {
481 Some(tile) => Hover::Tile(tile),
482 None => Hover::None,
483 }
484 }Sourcepub fn config(&self) -> &Config
pub fn config(&self) -> &Config
The configuration the engine started with.
Examples found in repository?
1037 fn controls(&mut self, ctx: &mut FrameContext<'_, Self>) {
1038 let tonemap = ctx.config().tonemap();
1039 let antialiasing = ctx.config().antialiasing();
1040 let shadow_resolution = ctx.config().shadow_resolution();
1041
1042 ctx.ui(|ui| {
1043 egui::Panel::right("controls")
1044 .resizable(false)
1045 .default_size(300.0)
1046 .show(ui, |ui| {
1047 egui::ScrollArea::vertical().show(ui, |ui| {
1048 ui.label(
1049 "right mouse button to look, W/A/S/D to move, \
1050 space/shift up and down, wheel to scale speed",
1051 );
1052 ui.separator();
1053 self.lighting_controls(ui);
1054 ui.separator();
1055 self.light_controls(ui);
1056 ui.separator();
1057 self.material_controls(ui);
1058 ui.separator();
1059 ui.label(format!(
1060 "tone map {tonemap:?} \u{b7} antialiasing {antialiasing} \u{b7} \
1061 shadow {shadow_resolution}px: set at startup, not live"
1062 ));
1063 ui.add(egui::Slider::new(&mut self.exposure, 0.1..=3.0).text("exposure"));
1064 ui.add(egui::Slider::new(&mut self.bloom, 0.0..=1.0).text("bloom"));
1065 });
1066 });
1067 });
1068 }Sourcepub fn prepare<M>(&mut self, mesh: M)
pub fn prepare<M>(&mut self, mesh: M)
Builds and uploads mesh now, instead of on its first draw; the
copy is then held like any drawn mesh’s under
Config::with_mesh_memory, and no longer than that.
Takes a mesh of Game::Meshes and no other.
Auto Trait Implementations§
impl<'a, G> !RefUnwindSafe for FrameContext<'a, G>
impl<'a, G> !UnwindSafe for FrameContext<'a, G>
impl<'a, G> Freeze for FrameContext<'a, G>
impl<'a, G> Send for FrameContext<'a, G>
impl<'a, G> Sync for FrameContext<'a, G>
impl<'a, G> Unpin for FrameContext<'a, G>
impl<'a, G> UnsafeUnpin for FrameContext<'a, G>where
&'a mut MeshCatalog<<G as Game>::Meshes>: UnsafeUnpin,
&'a mut SkyCatalog<<G as Game>::Skyboxes>: UnsafeUnpin,
&'a mut Sounding<<G as Game>::Sounds>: UnsafeUnpin,
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
impl<S, T> Duplex<S> for Twhere
T: FromSample<S> + ToSample<S>,
Source§impl<S> FromSample<S> for S
impl<S> FromSample<S> for S
fn from_sample_(s: S) -> S
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more