Skip to main content

TickContext

Struct TickContext 

Source
pub struct TickContext<'a, G: Game> { /* private fields */ }
Expand description

The work Game::tick may do: simulate, never draw.

Implementations§

Source§

impl<'a, G: Game> TickContext<'a, G>

Source

pub fn down<A: InputButtonAction>(&self, action: A) -> bool
where G::InputActions: Holds<A, A::Binding>,

Whether action is held right now.

Every tick of one drawn frame reads the same controls.

Examples found in repository?
examples/animation.rs (line 703)
692    fn advance(&mut self, ctx: &mut TickContext<'_, Scene>) -> f32 {
693        let control = ctx.axis2(Move::Walk).clamp_length_max(1.0);
694        let turn = Quat::from_rotation_y(self.camera_yaw);
695        let heading = turn * Vec3::X * control.x + turn * Vec3::NEG_Z * control.y;
696        let dt = ctx.dt().as_secs_f32();
697        if let Some(direction) = heading.try_normalize() {
698            let wanted = direction.x.atan2(direction.z);
699            let turn = (wanted - self.elf_yaw + core::f32::consts::PI).rem_euclid(TAU)
700                - core::f32::consts::PI;
701            self.elf_yaw += turn.clamp(-TURN_RATE * dt, TURN_RATE * dt);
702        }
703        let cap = if ctx.down(Button::Run) { 1.0 } else { WALK_CAP };
704        self.elf_pos += heading * cap * ELF_SPEED * dt;
705        heading.length() * cap
706    }
Source

pub fn pressed<A: InputButtonAction>(&self, action: A) -> bool
where G::InputActions: Holds<A, A::Binding>,

Whether action went down since the last frame whose ticks ran.

A frame that runs no ticks holds its edges for the ticks that follow, so every press is read by the ticks of exactly one frame.

Examples found in repository?
examples/breakout-game.rs (line 404)
399    fn hold_ball(&mut self, ctx: &mut TickContext<'_, Breakout>) {
400        self.ball_prev = self.ball_pos;
401        self.ball_pos.x = self.paddle_x;
402        self.ball_trail = [self.ball_pos; TRAIL_LEN + 1];
403
404        if !ctx.ui_wants_keyboard() && ctx.pressed(Button::Serve) {
405            self.launch();
406            ctx.play(Sound::Serve);
407        }
408    }
More examples
Hide additional examples
examples/ui-fonts.rs (line 804)
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    }
examples/sound-lab.rs (line 488)
474    fn handle_drag(&mut self, ctx: &mut TickContext<'_, SoundCheck>) {
475        // Read before the check below for the UI's own claim on the
476        // pointer, so a release over it still frees a source a drag moved
477        // there.
478        if ctx.released(Button::Select) {
479            self.dragging = None;
480        }
481        if ctx.ui_wants_pointer() {
482            return;
483        }
484        let ray = ctx
485            .last_camera()
486            .ray_through(ctx.pointer(), ctx.window_size());
487
488        if ctx.pressed(Button::Select) {
489            self.dragging = self.sources.iter().position(|source| {
490                ray.hit_sphere(source.position, SOURCE_PICK_RADIUS)
491                    .is_some()
492            });
493        }
494
495        let Some(index) = self.dragging else {
496            return;
497        };
498        let Some(distance) = ray.hit_plane(ray::Plane {
499            point: Vec3::ZERO,
500            normal: Vec3::Y,
501        }) else {
502            return;
503        };
504        let hit = ray.at(distance);
505        let dropped =
506            Vec2::new(hit.x, hit.z).clamp(Vec2::splat(-PLAY_BOUND), Vec2::splat(PLAY_BOUND));
507        self.sources[index].position = Vec3::new(dropped.x, SOURCE_HEIGHT, dropped.y);
508    }
examples/animation.rs (line 737)
730    fn tick_elf(&mut self, ctx: &mut TickContext<'_, Scene>) {
731        let grounded = matches!(
732            self.elf_animator.state(),
733            ElfState::Idle | ElfState::Locomotion
734        );
735
736        self.elf_input.speed = self.advance(ctx);
737        self.elf_input.attack = ctx.pressed(Button::Attack);
738        self.elf_input.jump = ctx.pressed(Button::Jump);
739        self.elf_input.dance = ctx.pressed(Button::Dance);
740
741        self.elf_input.near_seat = self.elf_pos.distance(SEAT_POSITION) < SEAT_INTERACT_RADIUS;
742        self.elf_input.interact = ctx.pressed(Button::Interact);
743        if self.elf_input.interact && grounded && self.elf_input.near_seat {
744            self.elf_pos = SEAT_SPOT;
745            self.elf_yaw = SEAT_FACING;
746        }
747
748        let underfoot = self.patch_underfoot();
749        let entered_patch = underfoot.is_some() && !self.in_patch;
750        self.in_patch = underfoot.is_some();
751        self.hits += u32::from(entered_patch);
752        self.elf_input.hit = entered_patch && self.hits < FATAL_HITS;
753        self.elf_input.dying = entered_patch && self.hits >= FATAL_HITS;
754        if entered_patch {
755            self.last_event = match self.elf_input.dying {
756                true => "elf died",
757                false => "elf hit",
758            };
759        }
760    }
761
762    /// Starts a new [`Animator`] over the elf's own state, its position and
763    /// hit count reset with it.
764    fn restart_elf(&mut self) {
765        self.elf_animator = Animator::new();
766        self.elf_pos = ELF_START;
767        self.elf_prev = ELF_START;
768        self.elf_yaw = 0.0;
769        self.elf_height = 0.0;
770        self.elf_height_prev = 0.0;
771        self.jump_speed = 0.0;
772        self.elf_input = ElfInput::default();
773        self.hits = 0;
774        self.in_patch = false;
775        self.last_event = "new elf started";
776    }
777
778    fn panel(&self, ctx: &mut FrameContext<'_, Scene>) {
779        let state = match self.elf_animator.state() {
780            ElfState::Idle => "idle",
781            ElfState::Locomotion if self.elf_input.speed > WALK_CAP => "running",
782            ElfState::Locomotion => "walking",
783            ElfState::Attack => "attacking",
784            ElfState::Hit => "hit",
785            ElfState::Death => "dead",
786            ElfState::SitDown => "sitting down",
787            ElfState::Sit => "sitting",
788            ElfState::StandUp => "standing up",
789            ElfState::Jump => "jumping",
790            ElfState::Dance => "dancing",
791        };
792        ctx.ui(|ui| {
793            egui::Frame::new()
794                .fill(egui::Color32::from_black_alpha(PANEL_BACKDROP))
795                .inner_margin(PANEL_PADDING)
796                .corner_radius(f32::from(PANEL_PADDING))
797                .show(ui, |ui| {
798                    ui.heading(panel_text(format!("elf is {state}")));
799                    ui.label(panel_text(format!(
800                        "hits taken {} of the {} red patches hurt for, {}",
801                        self.hits, FATAL_HITS, self.last_event
802                    )));
803                    ui.label(panel_text(match self.elf_animator.transitioning() {
804                        true => "fading between clips",
805                        false => "one clip playing",
806                    }));
807                    ui.add_space(f32::from(PANEL_PADDING));
808                    egui::Grid::new("controls").show(ui, |ui| {
809                        for (key, does) in CONTROLS {
810                            ui.label(panel_text(key));
811                            ui.label(panel_text(does));
812                            ui.end_row();
813                        }
814                    });
815                });
816        });
817    }
818
819    /// A prompt over the seat, each hurt patch, and the scrubbed elf,
820    /// naming what a player finds there; the seat's own prompt names the
821    /// live binding of `Button::Interact` by its own name, not one fixed
822    /// in the code, and is absent while the elf sits on it.
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    }
872}
873
874impl Game for Scene {
875    type Meshes = Shape;
876    type Sounds = NoSounds;
877    type InputActions = Controls;
878    type Skyboxes = Sky;
879    type SurfaceStyles = ();
880    type PostEffects = ();
881
882    fn tick(&mut self, ctx: &mut TickContext<'_, Scene>) {
883        self.elf_prev = self.elf_pos;
884        self.elf_height_prev = self.elf_height;
885
886        if ctx.pressed(Button::Restart) {
887            self.restart_elf();
888        }
889        if ctx.pressed(Button::Hold) {
890            self.holding = true;
891        }
892        if ctx.pressed(Button::Release) {
893            self.holding = false;
894        }
895
896        self.elf_input.landed = self.fall(ctx.dt().as_secs_f32());
897        self.tick_elf(ctx);
898        ctx.animate(Elf, &mut self.elf_animator, &self.elf_input);
899
900        if self.elf_animator.entered(ElfState::Jump) {
901            self.jump_speed = JUMP_LAUNCH_SPEED;
902        }
903        if self.elf_animator.left(ElfState::StandUp) {
904            self.last_event = "elf stood up";
905        }
906        if self.elf_animator.entered(ElfState::Sit) {
907            self.last_event = "elf sat down";
908        }
909        if self.elf_animator.entered(ElfState::Death) {
910            self.last_event = "elf died";
911        }
912
913        let scrubbed_input = ScrubbedInput {
914            settled: settled_at(self.elf_pos.distance(SCRUBBED_ELF_POSITION)),
915        };
916        ctx.animate(Elf, &mut self.scrubbed_animator, &scrubbed_input);
917        ctx.animate(Butterfly, &mut self.butterfly_animator, &());
918    }
examples/isometric-board.rs (line 415)
414    fn handle_click(&mut self, ctx: &mut TickContext<'_, Board>) {
415        if ctx.ui_wants_pointer() || !ctx.pressed(Button::Select) {
416            return;
417        }
418        let ray = ctx
419            .last_camera()
420            .ray_through(ctx.pointer(), ctx.window_size());
421        let (lift, half) = unit_geometry(self.turn);
422
423        if self.current().target.is_none() {
424            let center = self.current().position;
425            if ray.hit_aabb(center - half, center + half).is_some() {
426                self.selected = !self.selected;
427                return;
428            }
429        }
430        if !self.selected {
431            return;
432        }
433
434        let Some(distance) = ray.hit_plane(ray::Plane {
435            point: Vec3::ZERO,
436            normal: Vec3::Y,
437        }) else {
438            return;
439        };
440        let Some(tile) = tile_at(ray.at(distance)) else {
441            return;
442        };
443        if tile == self.current().tile || tile == self.other().tile {
444            return;
445        }
446
447        let destination = tile_center(tile) + Vec3::Y * lift;
448        let heading = destination.x - self.current().position.x;
449        let current = self.current_mut();
450        if heading.abs() > f32::EPSILON {
451            current.facing_right = heading > 0.0;
452        }
453        current.target = Some(destination);
454        self.selected = false;
455        ctx.play(Sound::Click);
456    }
examples/sprite-adventure.rs (line 1169)
1155    fn tick_cave(&mut self, ctx: &mut TickContext<'_, Keep>) {
1156        self.push_out_of(Self::cave_obstacles(self.door_obstacle()));
1157        self.position.x = self.position.x.clamp(-CAVE_HALF_WIDTH, CAVE_HALF_WIDTH);
1158        self.position.z = self.position.z.clamp(CAVE_WALK_FAR_Z, CAVE_WALK_NEAR_Z);
1159
1160        let target = if self.position.z < DOOR_WALL_NEAR_Z {
1161            1.0
1162        } else {
1163            0.0
1164        };
1165        let step = 1.0 / GHOST_RAMP_TICKS as f32;
1166        self.ghost += (target - self.ghost).clamp(-step, step);
1167
1168        if !self.door_opening
1169            && ctx.pressed(Button::Interact)
1170            && self.position.distance(INTERACT_POINT) < INTERACT_RADIUS
1171        {
1172            self.door_opening = true;
1173            self.swing_ticks = 0;
1174            ctx.play(Sound::Interact);
1175        }
1176        if self.door_opening && self.swing_ticks < DOOR_SWING_TICKS {
1177            self.swing_ticks += 1;
1178        }
1179
1180        if !self.gem_taken && self.position.distance(GEM_POSITION) < PICKUP_RADIUS {
1181            self.gem_taken = true;
1182            ctx.play(Sound::Gem);
1183            ctx.save(Flag::GemTaken, true);
1184            ctx.save(Position::X, self.position.x as f64);
1185            ctx.save(Position::Z, self.position.z as f64);
1186        }
1187
1188        if EXIT.holds(self.position) {
1189            if EXIT.holds(self.previous) {
1190                self.position.z = self.previous.z;
1191            } else {
1192                self.exit_cave(ctx);
1193            }
1194        }
1195    }
1196
1197    /// Puts the player back at [`PLAYER_SPAWN`] with the cave and the gem
1198    /// returned to their saved fallbacks, all in this tick: a reset saves
1199    /// every key's own fallback, since there is nothing to clear it to.
1200    fn reset(&mut self, ctx: &mut TickContext<'_, Keep>) {
1201        ctx.save(Position::X, Position::X.fallback());
1202        ctx.save(Position::Z, Position::Z.fallback());
1203        ctx.save(Flag::InCave, Flag::InCave.fallback());
1204        ctx.save(Flag::GemTaken, Flag::GemTaken.fallback());
1205
1206        self.area = Area::Overworld;
1207        self.position = PLAYER_SPAWN;
1208        self.previous = PLAYER_SPAWN;
1209        self.gem_taken = false;
1210        self.door_opening = false;
1211        self.swing_ticks = 0;
1212        self.ghost = 0.0;
1213    }
1214
1215    /// Steps into the cave at [`CAVE_SPAWN`], saving the transition.
1216    fn enter_cave(&mut self, ctx: &mut TickContext<'_, Keep>) {
1217        self.area = Area::Cave;
1218        self.position = CAVE_SPAWN;
1219        self.previous = CAVE_SPAWN;
1220        ctx.save(Flag::InCave, true);
1221        ctx.save(Position::X, CAVE_SPAWN.x as f64);
1222        ctx.save(Position::Z, CAVE_SPAWN.z as f64);
1223    }
1224
1225    /// Steps back out to the mouth at [`RETURN_SPAWN`], saving the
1226    /// transition.
1227    fn exit_cave(&mut self, ctx: &mut TickContext<'_, Keep>) {
1228        self.area = Area::Overworld;
1229        self.position = RETURN_SPAWN;
1230        self.previous = RETURN_SPAWN;
1231        ctx.save(Flag::InCave, false);
1232        ctx.save(Position::X, RETURN_SPAWN.x as f64);
1233        ctx.save(Position::Z, RETURN_SPAWN.z as f64);
1234    }
1235
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    }
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    }
1791}
1792
1793impl Game for Keep {
1794    type Meshes = Shape;
1795    type Sounds = Sound;
1796    type InputActions = Controls;
1797    type Skyboxes = NoSkyboxes;
1798    type SurfaceStyles = Looks;
1799    type PostEffects = ();
1800
1801    fn tick(&mut self, ctx: &mut TickContext<'_, Keep>) {
1802        self.previous = self.position;
1803
1804        if self.reset_requested || ctx.pressed(Button::Reset) {
1805            self.reset_requested = false;
1806            self.reset(ctx);
1807            return;
1808        }
1809
1810        let heading = if ctx.ui_wants_keyboard() {
1811            Vec2::ZERO
1812        } else {
1813            ctx.axis2(Move::Walk)
1814        };
1815        match Facing::from_heading(heading) {
1816            Some(facing) => {
1817                self.facing = facing;
1818                self.walk_ticks += 1;
1819            }
1820            None => self.walk_ticks = 0,
1821        }
1822        let stride = Vec3::new(heading.x, 0.0, -heading.y) * WALK_SPEED * ctx.dt().as_secs_f32();
1823        self.position += stride;
1824        self.simulated += ctx.dt();
1825
1826        match self.area {
1827            Area::Overworld => self.tick_overworld(ctx),
1828            Area::Cave => self.tick_cave(ctx),
1829        }
1830    }
Source

pub fn released<A: InputButtonAction>(&self, action: A) -> bool
where G::InputActions: Holds<A, A::Binding>,

Whether action came up since the last frame whose ticks ran.

A release is held for the ticks that follow, the same as a press.

Examples found in repository?
examples/sound-lab.rs (line 478)
474    fn handle_drag(&mut self, ctx: &mut TickContext<'_, SoundCheck>) {
475        // Read before the check below for the UI's own claim on the
476        // pointer, so a release over it still frees a source a drag moved
477        // there.
478        if ctx.released(Button::Select) {
479            self.dragging = None;
480        }
481        if ctx.ui_wants_pointer() {
482            return;
483        }
484        let ray = ctx
485            .last_camera()
486            .ray_through(ctx.pointer(), ctx.window_size());
487
488        if ctx.pressed(Button::Select) {
489            self.dragging = self.sources.iter().position(|source| {
490                ray.hit_sphere(source.position, SOURCE_PICK_RADIUS)
491                    .is_some()
492            });
493        }
494
495        let Some(index) = self.dragging else {
496            return;
497        };
498        let Some(distance) = ray.hit_plane(ray::Plane {
499            point: Vec3::ZERO,
500            normal: Vec3::Y,
501        }) else {
502            return;
503        };
504        let hit = ray.at(distance);
505        let dropped =
506            Vec2::new(hit.x, hit.z).clamp(Vec2::splat(-PLAY_BOUND), Vec2::splat(PLAY_BOUND));
507        self.sources[index].position = Vec3::new(dropped.x, SOURCE_HEIGHT, dropped.y);
508    }
Source

pub fn clicks<A: InputButtonAction>(&self, action: A) -> u32
where G::InputActions: Holds<A, A::Binding>,

Presses of action in a row, counting the one these ticks read: 1 for a single click, 2 for a double, 0 where they read no press; see FrameContext::clicks.

The ticks of one frame read one press however many landed in them, so a control pressed twice inside one frame reads one press of two clicks.

Source

pub fn axis<A: InputAxisAction>(&self, action: A) -> f32
where G::InputActions: Holds<A, A::Binding>,

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.

Read those two in Game::frame: the ticks of one frame each read the whole distance that frame moved.

Examples found in repository?
examples/breakout-game.rs (line 985)
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    }
Source

pub fn axis2<A: InputAxis2Action>(&self, action: A) -> Vec2
where G::InputActions: Holds<A, A::Binding>,

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.

Read the pointer in Game::frame: the ticks of one frame each read the whole distance that frame moved.

Examples found in repository?
examples/sound-lab.rs (line 466)
461    fn handle_walk(&mut self, ctx: &mut TickContext<'_, SoundCheck>) {
462        self.player_prev = self.player;
463        if ctx.ui_wants_keyboard() {
464            return;
465        }
466        let walk = ctx.axis2(Move::Walk);
467        let world = Vec2::new(walk.x, -walk.y);
468        self.player = (self.player + world * WALK_SPEED * ctx.dt().as_secs_f32())
469            .clamp(Vec2::splat(-PLAY_BOUND), Vec2::splat(PLAY_BOUND));
470    }
More examples
Hide additional examples
examples/animation.rs (line 693)
692    fn advance(&mut self, ctx: &mut TickContext<'_, Scene>) -> f32 {
693        let control = ctx.axis2(Move::Walk).clamp_length_max(1.0);
694        let turn = Quat::from_rotation_y(self.camera_yaw);
695        let heading = turn * Vec3::X * control.x + turn * Vec3::NEG_Z * control.y;
696        let dt = ctx.dt().as_secs_f32();
697        if let Some(direction) = heading.try_normalize() {
698            let wanted = direction.x.atan2(direction.z);
699            let turn = (wanted - self.elf_yaw + core::f32::consts::PI).rem_euclid(TAU)
700                - core::f32::consts::PI;
701            self.elf_yaw += turn.clamp(-TURN_RATE * dt, TURN_RATE * dt);
702        }
703        let cap = if ctx.down(Button::Run) { 1.0 } else { WALK_CAP };
704        self.elf_pos += heading * cap * ELF_SPEED * dt;
705        heading.length() * cap
706    }
examples/sprite-adventure.rs (line 1813)
1801    fn tick(&mut self, ctx: &mut TickContext<'_, Keep>) {
1802        self.previous = self.position;
1803
1804        if self.reset_requested || ctx.pressed(Button::Reset) {
1805            self.reset_requested = false;
1806            self.reset(ctx);
1807            return;
1808        }
1809
1810        let heading = if ctx.ui_wants_keyboard() {
1811            Vec2::ZERO
1812        } else {
1813            ctx.axis2(Move::Walk)
1814        };
1815        match Facing::from_heading(heading) {
1816            Some(facing) => {
1817                self.facing = facing;
1818                self.walk_ticks += 1;
1819            }
1820            None => self.walk_ticks = 0,
1821        }
1822        let stride = Vec3::new(heading.x, 0.0, -heading.y) * WALK_SPEED * ctx.dt().as_secs_f32();
1823        self.position += stride;
1824        self.simulated += ctx.dt();
1825
1826        match self.area {
1827            Area::Overworld => self.tick_overworld(ctx),
1828            Area::Cave => self.tick_cave(ctx),
1829        }
1830    }
Source

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?
examples/ui-fonts.rs (line 637)
634    fn handle_hail(&mut self, ctx: &mut TickContext<'_, Self>) {
635        let ray = ctx
636            .last_camera()
637            .ray_through(ctx.pointer(), ctx.window_size());
638        let Some(station) = hit_station(ray) else {
639            return;
640        };
641
642        if self.hailed != Some(station) {
643            self.hailed = Some(station);
644            let look = station.look();
645            self.dialogue = Some(Dialogue::start(look.name, look.lines.map(str::to_owned)));
646            return;
647        }
648        let Some(dialogue) = &mut self.dialogue else {
649            return;
650        };
651        if !dialogue.advance() {
652            self.dialogue = None;
653            self.hailed = None;
654        }
655    }
More examples
Hide additional examples
examples/sound-lab.rs (line 486)
474    fn handle_drag(&mut self, ctx: &mut TickContext<'_, SoundCheck>) {
475        // Read before the check below for the UI's own claim on the
476        // pointer, so a release over it still frees a source a drag moved
477        // there.
478        if ctx.released(Button::Select) {
479            self.dragging = None;
480        }
481        if ctx.ui_wants_pointer() {
482            return;
483        }
484        let ray = ctx
485            .last_camera()
486            .ray_through(ctx.pointer(), ctx.window_size());
487
488        if ctx.pressed(Button::Select) {
489            self.dragging = self.sources.iter().position(|source| {
490                ray.hit_sphere(source.position, SOURCE_PICK_RADIUS)
491                    .is_some()
492            });
493        }
494
495        let Some(index) = self.dragging else {
496            return;
497        };
498        let Some(distance) = ray.hit_plane(ray::Plane {
499            point: Vec3::ZERO,
500            normal: Vec3::Y,
501        }) else {
502            return;
503        };
504        let hit = ray.at(distance);
505        let dropped =
506            Vec2::new(hit.x, hit.z).clamp(Vec2::splat(-PLAY_BOUND), Vec2::splat(PLAY_BOUND));
507        self.sources[index].position = Vec3::new(dropped.x, SOURCE_HEIGHT, dropped.y);
508    }
examples/isometric-board.rs (line 420)
414    fn handle_click(&mut self, ctx: &mut TickContext<'_, Board>) {
415        if ctx.ui_wants_pointer() || !ctx.pressed(Button::Select) {
416            return;
417        }
418        let ray = ctx
419            .last_camera()
420            .ray_through(ctx.pointer(), ctx.window_size());
421        let (lift, half) = unit_geometry(self.turn);
422
423        if self.current().target.is_none() {
424            let center = self.current().position;
425            if ray.hit_aabb(center - half, center + half).is_some() {
426                self.selected = !self.selected;
427                return;
428            }
429        }
430        if !self.selected {
431            return;
432        }
433
434        let Some(distance) = ray.hit_plane(ray::Plane {
435            point: Vec3::ZERO,
436            normal: Vec3::Y,
437        }) else {
438            return;
439        };
440        let Some(tile) = tile_at(ray.at(distance)) else {
441            return;
442        };
443        if tile == self.current().tile || tile == self.other().tile {
444            return;
445        }
446
447        let destination = tile_center(tile) + Vec3::Y * lift;
448        let heading = destination.x - self.current().position.x;
449        let current = self.current_mut();
450        if heading.abs() > f32::EPSILON {
451            current.facing_right = heading > 0.0;
452        }
453        current.target = Some(destination);
454        self.selected = false;
455        ctx.play(Sound::Click);
456    }
Source

pub fn play(&mut self, sound: impl Into<SoundCue<G::Sounds>>)

Plays sound once, keeping wherever it is placed as of this tick.

Every call is a voice of its own, so the same sound twice over is heard twice; the ticks of one frame are played together at the end of it.

Examples found in repository?
examples/breakout-game.rs (line 406)
399    fn hold_ball(&mut self, ctx: &mut TickContext<'_, Breakout>) {
400        self.ball_prev = self.ball_pos;
401        self.ball_pos.x = self.paddle_x;
402        self.ball_trail = [self.ball_pos; TRAIL_LEN + 1];
403
404        if !ctx.ui_wants_keyboard() && ctx.pressed(Button::Serve) {
405            self.launch();
406            ctx.play(Sound::Serve);
407        }
408    }
409
410    fn step_paddle(&mut self, axis: f32, dt: f32) {
411        self.paddle_prev_x = self.paddle_x;
412        self.paddle_x =
413            (self.paddle_x + axis * PADDLE_SPEED * dt).clamp(-PADDLE_LIMIT, PADDLE_LIMIT);
414    }
415
416    fn step_ball(&mut self, ctx: &mut TickContext<'_, Breakout>, dt: f32) {
417        self.ball_prev = self.ball_pos;
418        self.ball_pos += self.ball_vel * dt;
419
420        self.bounce_walls(ctx);
421        self.bounce_paddle(ctx);
422        self.bounce_bricks(ctx);
423        self.push_trail();
424
425        if self.ball_pos.z - BALL_RADIUS > COURT_HALF_DEPTH {
426            self.lose_life(ctx);
427        }
428    }
429
430    /// Shifts the ghost trail back one slot and records the ball's newly
431    /// resolved position at the front.
432    fn push_trail(&mut self) {
433        self.ball_trail.rotate_right(1);
434        self.ball_trail[0] = self.ball_pos;
435    }
436
437    fn bounce_walls(&mut self, ctx: &mut TickContext<'_, Breakout>) {
438        let left = -COURT_HALF_WIDTH + WALL_THICKNESS;
439        let right = COURT_HALF_WIDTH - WALL_THICKNESS;
440        let top = -COURT_HALF_DEPTH + WALL_THICKNESS;
441
442        let mut hit = false;
443        if self.ball_pos.x - BALL_RADIUS < left {
444            self.ball_pos.x = left + BALL_RADIUS;
445            self.ball_vel.x = self.ball_vel.x.abs();
446            hit = true;
447        } else if self.ball_pos.x + BALL_RADIUS > right {
448            self.ball_pos.x = right - BALL_RADIUS;
449            self.ball_vel.x = -self.ball_vel.x.abs();
450            hit = true;
451        }
452
453        if self.ball_pos.z - BALL_RADIUS < top {
454            self.ball_pos.z = top + BALL_RADIUS;
455            self.ball_vel.z = self.ball_vel.z.abs();
456            hit = true;
457        }
458
459        if hit {
460            ctx.play(Sound::Bounce.pitch(WALL_BOUNCE_PITCH));
461        }
462    }
463
464    /// Bounces the ball off the paddle, steering it by where it landed.
465    fn bounce_paddle(&mut self, ctx: &mut TickContext<'_, Breakout>) {
466        if self.ball_vel.z <= 0.0 {
467            return;
468        }
469        let reach_x = PADDLE_HALF_WIDTH + BALL_RADIUS;
470        let reach_z = PADDLE_HALF_DEPTH + BALL_RADIUS;
471        let dx = self.ball_pos.x - self.paddle_x;
472        let dz = self.ball_pos.z - PADDLE_Z;
473        if dx.abs() > reach_x || dz.abs() > reach_z {
474            return;
475        }
476
477        let offset = (dx / PADDLE_HALF_WIDTH).clamp(-1.0, 1.0);
478        self.ball_vel = Vec3::new(offset, 0.0, -1.0).normalize() * BALL_SPEED;
479        self.ball_pos.z = PADDLE_Z - reach_z;
480        self.paddle_flash = PADDLE_FLASH;
481        ctx.play(Sound::Bounce.pitch(PADDLE_BOUNCE_PITCH));
482    }
483
484    /// Bounces the ball off the nearest overlapping brick, damaging it.
485    fn bounce_bricks(&mut self, ctx: &mut TickContext<'_, Breakout>) {
486        let reach_x = BRICK_HALF_WIDTH + BALL_RADIUS;
487        let reach_z = BRICK_HALF_DEPTH + BALL_RADIUS;
488        let mut broken = None;
489
490        for brick in self
491            .bricks
492            .iter_mut()
493            .filter(|brick| brick.hits_remaining > 0)
494        {
495            let dx = self.ball_pos.x - brick.position.x;
496            let dz = self.ball_pos.z - brick.position.z;
497            if dx.abs() > reach_x || dz.abs() > reach_z {
498                continue;
499            }
500
501            if reach_x - dx.abs() < reach_z - dz.abs() {
502                self.ball_vel.x = if dx < 0.0 {
503                    -self.ball_vel.x.abs()
504                } else {
505                    self.ball_vel.x.abs()
506                };
507            } else {
508                self.ball_vel.z = if dz < 0.0 {
509                    -self.ball_vel.z.abs()
510                } else {
511                    self.ball_vel.z.abs()
512                };
513            }
514
515            brick.hits_remaining -= 1;
516            self.score += 10 * (BRICK_ROWS - brick.row) as u32;
517            ctx.play(Sound::Bounce.pitch(BRICK_BOUNCE_PITCH));
518            if brick.hits_remaining == 0 {
519                ctx.play(
520                    Sound::BrickBreak
521                        .at(brick.position)
522                        .reference(BRICK_BREAK_REFERENCE),
523                );
524                self.brick_flash = BRICK_FLASH;
525                broken = Some((brick.position, BRICK_ROW_COLORS[brick.row]));
526            }
527            break;
528        }
529
530        if let Some((position, color)) = broken {
531            self.spawn_sparks(position, color);
532        }
533
534        if self.bricks.iter().all(|brick| brick.hits_remaining == 0) {
535            self.phase = Phase::Won;
536            ctx.play(Sound::LevelClear);
537        }
538    }
539
540    /// Sends [`SPARK_BURST_COUNT`] sparks outward and upward from a broken
541    /// brick's position, spread by index so no randomness is needed.
542    fn spawn_sparks(&mut self, position: Vec3, color: Color) {
543        for i in 0..SPARK_BURST_COUNT {
544            let t = i as f32 / SPARK_BURST_COUNT as f32;
545            let azimuth = t * TAU;
546            let rise = 0.6 + 0.4 * (t * 3.0).fract();
547            let speed = SPARK_SPEED_MIN.lerp(SPARK_SPEED_MAX, (t * 5.0).fract());
548            let direction = Vec3::new(azimuth.cos(), rise, azimuth.sin()).normalize();
549            self.sparks.push(Spark {
550                position,
551                velocity: direction * speed,
552                roll: azimuth,
553                age: 0.0,
554                color,
555            });
556        }
557    }
558
559    /// Integrates and ages the live sparks, dropping any past their
560    /// lifetime.
561    fn step_sparks(&mut self, dt: f32) {
562        for spark in &mut self.sparks {
563            spark.velocity.y -= SPARK_GRAVITY * dt;
564            spark.position += spark.velocity * dt;
565            spark.age += dt;
566        }
567        self.sparks.retain(|spark| spark.age < SPARK_LIFETIME);
568    }
569
570    fn lose_life(&mut self, ctx: &mut TickContext<'_, Breakout>) {
571        self.life_lost_flash = LIFE_LOST_FLASH;
572        self.lives = self.lives.saturating_sub(1);
573        if self.lives == 0 {
574            self.phase = Phase::Lost;
575            ctx.play(Sound::GameOver);
576        } else {
577            ctx.play(Sound::BallLost);
578            self.ready_serve();
579        }
580    }
More examples
Hide additional examples
examples/isometric-board.rs (line 455)
414    fn handle_click(&mut self, ctx: &mut TickContext<'_, Board>) {
415        if ctx.ui_wants_pointer() || !ctx.pressed(Button::Select) {
416            return;
417        }
418        let ray = ctx
419            .last_camera()
420            .ray_through(ctx.pointer(), ctx.window_size());
421        let (lift, half) = unit_geometry(self.turn);
422
423        if self.current().target.is_none() {
424            let center = self.current().position;
425            if ray.hit_aabb(center - half, center + half).is_some() {
426                self.selected = !self.selected;
427                return;
428            }
429        }
430        if !self.selected {
431            return;
432        }
433
434        let Some(distance) = ray.hit_plane(ray::Plane {
435            point: Vec3::ZERO,
436            normal: Vec3::Y,
437        }) else {
438            return;
439        };
440        let Some(tile) = tile_at(ray.at(distance)) else {
441            return;
442        };
443        if tile == self.current().tile || tile == self.other().tile {
444            return;
445        }
446
447        let destination = tile_center(tile) + Vec3::Y * lift;
448        let heading = destination.x - self.current().position.x;
449        let current = self.current_mut();
450        if heading.abs() > f32::EPSILON {
451            current.facing_right = heading > 0.0;
452        }
453        current.target = Some(destination);
454        self.selected = false;
455        ctx.play(Sound::Click);
456    }
examples/sprite-adventure.rs (line 1174)
1155    fn tick_cave(&mut self, ctx: &mut TickContext<'_, Keep>) {
1156        self.push_out_of(Self::cave_obstacles(self.door_obstacle()));
1157        self.position.x = self.position.x.clamp(-CAVE_HALF_WIDTH, CAVE_HALF_WIDTH);
1158        self.position.z = self.position.z.clamp(CAVE_WALK_FAR_Z, CAVE_WALK_NEAR_Z);
1159
1160        let target = if self.position.z < DOOR_WALL_NEAR_Z {
1161            1.0
1162        } else {
1163            0.0
1164        };
1165        let step = 1.0 / GHOST_RAMP_TICKS as f32;
1166        self.ghost += (target - self.ghost).clamp(-step, step);
1167
1168        if !self.door_opening
1169            && ctx.pressed(Button::Interact)
1170            && self.position.distance(INTERACT_POINT) < INTERACT_RADIUS
1171        {
1172            self.door_opening = true;
1173            self.swing_ticks = 0;
1174            ctx.play(Sound::Interact);
1175        }
1176        if self.door_opening && self.swing_ticks < DOOR_SWING_TICKS {
1177            self.swing_ticks += 1;
1178        }
1179
1180        if !self.gem_taken && self.position.distance(GEM_POSITION) < PICKUP_RADIUS {
1181            self.gem_taken = true;
1182            ctx.play(Sound::Gem);
1183            ctx.save(Flag::GemTaken, true);
1184            ctx.save(Position::X, self.position.x as f64);
1185            ctx.save(Position::Z, self.position.z as f64);
1186        }
1187
1188        if EXIT.holds(self.position) {
1189            if EXIT.holds(self.previous) {
1190                self.position.z = self.previous.z;
1191            } else {
1192                self.exit_cave(ctx);
1193            }
1194        }
1195    }
Source

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.

Source

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 these ticks belong to is drawn, and only where a value changed, so saving every tick costs nothing.

Examples found in repository?
examples/sprite-adventure.rs (line 1183)
1155    fn tick_cave(&mut self, ctx: &mut TickContext<'_, Keep>) {
1156        self.push_out_of(Self::cave_obstacles(self.door_obstacle()));
1157        self.position.x = self.position.x.clamp(-CAVE_HALF_WIDTH, CAVE_HALF_WIDTH);
1158        self.position.z = self.position.z.clamp(CAVE_WALK_FAR_Z, CAVE_WALK_NEAR_Z);
1159
1160        let target = if self.position.z < DOOR_WALL_NEAR_Z {
1161            1.0
1162        } else {
1163            0.0
1164        };
1165        let step = 1.0 / GHOST_RAMP_TICKS as f32;
1166        self.ghost += (target - self.ghost).clamp(-step, step);
1167
1168        if !self.door_opening
1169            && ctx.pressed(Button::Interact)
1170            && self.position.distance(INTERACT_POINT) < INTERACT_RADIUS
1171        {
1172            self.door_opening = true;
1173            self.swing_ticks = 0;
1174            ctx.play(Sound::Interact);
1175        }
1176        if self.door_opening && self.swing_ticks < DOOR_SWING_TICKS {
1177            self.swing_ticks += 1;
1178        }
1179
1180        if !self.gem_taken && self.position.distance(GEM_POSITION) < PICKUP_RADIUS {
1181            self.gem_taken = true;
1182            ctx.play(Sound::Gem);
1183            ctx.save(Flag::GemTaken, true);
1184            ctx.save(Position::X, self.position.x as f64);
1185            ctx.save(Position::Z, self.position.z as f64);
1186        }
1187
1188        if EXIT.holds(self.position) {
1189            if EXIT.holds(self.previous) {
1190                self.position.z = self.previous.z;
1191            } else {
1192                self.exit_cave(ctx);
1193            }
1194        }
1195    }
1196
1197    /// Puts the player back at [`PLAYER_SPAWN`] with the cave and the gem
1198    /// returned to their saved fallbacks, all in this tick: a reset saves
1199    /// every key's own fallback, since there is nothing to clear it to.
1200    fn reset(&mut self, ctx: &mut TickContext<'_, Keep>) {
1201        ctx.save(Position::X, Position::X.fallback());
1202        ctx.save(Position::Z, Position::Z.fallback());
1203        ctx.save(Flag::InCave, Flag::InCave.fallback());
1204        ctx.save(Flag::GemTaken, Flag::GemTaken.fallback());
1205
1206        self.area = Area::Overworld;
1207        self.position = PLAYER_SPAWN;
1208        self.previous = PLAYER_SPAWN;
1209        self.gem_taken = false;
1210        self.door_opening = false;
1211        self.swing_ticks = 0;
1212        self.ghost = 0.0;
1213    }
1214
1215    /// Steps into the cave at [`CAVE_SPAWN`], saving the transition.
1216    fn enter_cave(&mut self, ctx: &mut TickContext<'_, Keep>) {
1217        self.area = Area::Cave;
1218        self.position = CAVE_SPAWN;
1219        self.previous = CAVE_SPAWN;
1220        ctx.save(Flag::InCave, true);
1221        ctx.save(Position::X, CAVE_SPAWN.x as f64);
1222        ctx.save(Position::Z, CAVE_SPAWN.z as f64);
1223    }
1224
1225    /// Steps back out to the mouth at [`RETURN_SPAWN`], saving the
1226    /// transition.
1227    fn exit_cave(&mut self, ctx: &mut TickContext<'_, Keep>) {
1228        self.area = Area::Overworld;
1229        self.position = RETURN_SPAWN;
1230        self.previous = RETURN_SPAWN;
1231        ctx.save(Flag::InCave, false);
1232        ctx.save(Position::X, RETURN_SPAWN.x as f64);
1233        ctx.save(Position::Z, RETURN_SPAWN.z as f64);
1234    }
Source

pub fn sound_unlocked(&self) -> bool

Whether the platform allows sound to start right now; see FrameContext::sound_unlocked.

Source

pub fn animate<M, P, S>( &mut self, mesh: M, animator: &mut Animator<M, S>, input: &S::Input, )
where G::Meshes: Holds<M> + From<M>, M: Mesh<P, S::Clip>, P: Part, S: AnimationStates,

Runs animator up to this tick against input, over the clips mesh holds: where its state goes from here, and what the state it lands in plays.

Takes a mesh of Game::Meshes and a machine typed by that mesh, so a machine runs on the clips of the value the game draws and no other. Every tick of one drawn frame runs at one instant, as every one of them reads one snapshot of the controls, and the frame draws at that same instant.

Examples found in repository?
examples/animation.rs (line 898)
882    fn tick(&mut self, ctx: &mut TickContext<'_, Scene>) {
883        self.elf_prev = self.elf_pos;
884        self.elf_height_prev = self.elf_height;
885
886        if ctx.pressed(Button::Restart) {
887            self.restart_elf();
888        }
889        if ctx.pressed(Button::Hold) {
890            self.holding = true;
891        }
892        if ctx.pressed(Button::Release) {
893            self.holding = false;
894        }
895
896        self.elf_input.landed = self.fall(ctx.dt().as_secs_f32());
897        self.tick_elf(ctx);
898        ctx.animate(Elf, &mut self.elf_animator, &self.elf_input);
899
900        if self.elf_animator.entered(ElfState::Jump) {
901            self.jump_speed = JUMP_LAUNCH_SPEED;
902        }
903        if self.elf_animator.left(ElfState::StandUp) {
904            self.last_event = "elf stood up";
905        }
906        if self.elf_animator.entered(ElfState::Sit) {
907            self.last_event = "elf sat down";
908        }
909        if self.elf_animator.entered(ElfState::Death) {
910            self.last_event = "elf died";
911        }
912
913        let scrubbed_input = ScrubbedInput {
914            settled: settled_at(self.elf_pos.distance(SCRUBBED_ELF_POSITION)),
915        };
916        ctx.animate(Elf, &mut self.scrubbed_animator, &scrubbed_input);
917        ctx.animate(Butterfly, &mut self.butterfly_animator, &());
918    }
Source

pub fn dt(&self) -> Duration

This tick’s fixed time step: Config::tick_interval until set_tick_interval changes it.

Examples found in repository?
examples/sound-lab.rs (line 468)
461    fn handle_walk(&mut self, ctx: &mut TickContext<'_, SoundCheck>) {
462        self.player_prev = self.player;
463        if ctx.ui_wants_keyboard() {
464            return;
465        }
466        let walk = ctx.axis2(Move::Walk);
467        let world = Vec2::new(walk.x, -walk.y);
468        self.player = (self.player + world * WALK_SPEED * ctx.dt().as_secs_f32())
469            .clamp(Vec2::splat(-PLAY_BOUND), Vec2::splat(PLAY_BOUND));
470    }
More examples
Hide additional examples
examples/isometric-board.rs (line 810)
804    fn tick(&mut self, ctx: &mut TickContext<'_, Board>) {
805        self.sprite.previous = self.sprite.position;
806        self.block.previous = self.block.position;
807
808        self.handle_click(ctx);
809
810        if self.current_mut().advance(ctx.dt()) {
811            if let Some(tile) = tile_at(self.current().position) {
812                self.current_mut().tile = tile;
813            }
814            self.turn = self.turn.other();
815            self.selected = false;
816        }
817    }
examples/ui-fonts.rs (line 798)
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    }
examples/animation.rs (line 696)
692    fn advance(&mut self, ctx: &mut TickContext<'_, Scene>) -> f32 {
693        let control = ctx.axis2(Move::Walk).clamp_length_max(1.0);
694        let turn = Quat::from_rotation_y(self.camera_yaw);
695        let heading = turn * Vec3::X * control.x + turn * Vec3::NEG_Z * control.y;
696        let dt = ctx.dt().as_secs_f32();
697        if let Some(direction) = heading.try_normalize() {
698            let wanted = direction.x.atan2(direction.z);
699            let turn = (wanted - self.elf_yaw + core::f32::consts::PI).rem_euclid(TAU)
700                - core::f32::consts::PI;
701            self.elf_yaw += turn.clamp(-TURN_RATE * dt, TURN_RATE * dt);
702        }
703        let cap = if ctx.down(Button::Run) { 1.0 } else { WALK_CAP };
704        self.elf_pos += heading * cap * ELF_SPEED * dt;
705        heading.length() * cap
706    }
707
708    /// Integrates [`Self::elf_height`] under [`GRAVITY`] from
709    /// [`Self::jump_speed`], held at the ground; `true` the tick it
710    /// returns there from above it.
711    fn fall(&mut self, dt: f32) -> bool {
712        let off_ground = self.elf_height > 0.0;
713        self.jump_speed -= GRAVITY * dt;
714        self.elf_height = (self.elf_height + self.jump_speed * dt).max(0.0);
715        if self.elf_height == 0.0 {
716            self.jump_speed = 0.0;
717        }
718        off_ground && self.elf_height == 0.0
719    }
720
721    /// The hurt patch `elf_pos` stands inside, if any.
722    fn patch_underfoot(&self) -> Option<Vec3> {
723        HURT_PATCHES
724            .into_iter()
725            .find(|&patch| self.elf_pos.distance(patch) < HURT_RADIUS)
726    }
727
728    /// Reads the controls and moves the elf, filling [`Self::elf_input`]
729    /// for [`ElfState`] to read.
730    fn tick_elf(&mut self, ctx: &mut TickContext<'_, Scene>) {
731        let grounded = matches!(
732            self.elf_animator.state(),
733            ElfState::Idle | ElfState::Locomotion
734        );
735
736        self.elf_input.speed = self.advance(ctx);
737        self.elf_input.attack = ctx.pressed(Button::Attack);
738        self.elf_input.jump = ctx.pressed(Button::Jump);
739        self.elf_input.dance = ctx.pressed(Button::Dance);
740
741        self.elf_input.near_seat = self.elf_pos.distance(SEAT_POSITION) < SEAT_INTERACT_RADIUS;
742        self.elf_input.interact = ctx.pressed(Button::Interact);
743        if self.elf_input.interact && grounded && self.elf_input.near_seat {
744            self.elf_pos = SEAT_SPOT;
745            self.elf_yaw = SEAT_FACING;
746        }
747
748        let underfoot = self.patch_underfoot();
749        let entered_patch = underfoot.is_some() && !self.in_patch;
750        self.in_patch = underfoot.is_some();
751        self.hits += u32::from(entered_patch);
752        self.elf_input.hit = entered_patch && self.hits < FATAL_HITS;
753        self.elf_input.dying = entered_patch && self.hits >= FATAL_HITS;
754        if entered_patch {
755            self.last_event = match self.elf_input.dying {
756                true => "elf died",
757                false => "elf hit",
758            };
759        }
760    }
761
762    /// Starts a new [`Animator`] over the elf's own state, its position and
763    /// hit count reset with it.
764    fn restart_elf(&mut self) {
765        self.elf_animator = Animator::new();
766        self.elf_pos = ELF_START;
767        self.elf_prev = ELF_START;
768        self.elf_yaw = 0.0;
769        self.elf_height = 0.0;
770        self.elf_height_prev = 0.0;
771        self.jump_speed = 0.0;
772        self.elf_input = ElfInput::default();
773        self.hits = 0;
774        self.in_patch = false;
775        self.last_event = "new elf started";
776    }
777
778    fn panel(&self, ctx: &mut FrameContext<'_, Scene>) {
779        let state = match self.elf_animator.state() {
780            ElfState::Idle => "idle",
781            ElfState::Locomotion if self.elf_input.speed > WALK_CAP => "running",
782            ElfState::Locomotion => "walking",
783            ElfState::Attack => "attacking",
784            ElfState::Hit => "hit",
785            ElfState::Death => "dead",
786            ElfState::SitDown => "sitting down",
787            ElfState::Sit => "sitting",
788            ElfState::StandUp => "standing up",
789            ElfState::Jump => "jumping",
790            ElfState::Dance => "dancing",
791        };
792        ctx.ui(|ui| {
793            egui::Frame::new()
794                .fill(egui::Color32::from_black_alpha(PANEL_BACKDROP))
795                .inner_margin(PANEL_PADDING)
796                .corner_radius(f32::from(PANEL_PADDING))
797                .show(ui, |ui| {
798                    ui.heading(panel_text(format!("elf is {state}")));
799                    ui.label(panel_text(format!(
800                        "hits taken {} of the {} red patches hurt for, {}",
801                        self.hits, FATAL_HITS, self.last_event
802                    )));
803                    ui.label(panel_text(match self.elf_animator.transitioning() {
804                        true => "fading between clips",
805                        false => "one clip playing",
806                    }));
807                    ui.add_space(f32::from(PANEL_PADDING));
808                    egui::Grid::new("controls").show(ui, |ui| {
809                        for (key, does) in CONTROLS {
810                            ui.label(panel_text(key));
811                            ui.label(panel_text(does));
812                            ui.end_row();
813                        }
814                    });
815                });
816        });
817    }
818
819    /// A prompt over the seat, each hurt patch, and the scrubbed elf,
820    /// naming what a player finds there; the seat's own prompt names the
821    /// live binding of `Button::Interact` by its own name, not one fixed
822    /// in the code, and is absent while the elf sits on it.
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    }
872}
873
874impl Game for Scene {
875    type Meshes = Shape;
876    type Sounds = NoSounds;
877    type InputActions = Controls;
878    type Skyboxes = Sky;
879    type SurfaceStyles = ();
880    type PostEffects = ();
881
882    fn tick(&mut self, ctx: &mut TickContext<'_, Scene>) {
883        self.elf_prev = self.elf_pos;
884        self.elf_height_prev = self.elf_height;
885
886        if ctx.pressed(Button::Restart) {
887            self.restart_elf();
888        }
889        if ctx.pressed(Button::Hold) {
890            self.holding = true;
891        }
892        if ctx.pressed(Button::Release) {
893            self.holding = false;
894        }
895
896        self.elf_input.landed = self.fall(ctx.dt().as_secs_f32());
897        self.tick_elf(ctx);
898        ctx.animate(Elf, &mut self.elf_animator, &self.elf_input);
899
900        if self.elf_animator.entered(ElfState::Jump) {
901            self.jump_speed = JUMP_LAUNCH_SPEED;
902        }
903        if self.elf_animator.left(ElfState::StandUp) {
904            self.last_event = "elf stood up";
905        }
906        if self.elf_animator.entered(ElfState::Sit) {
907            self.last_event = "elf sat down";
908        }
909        if self.elf_animator.entered(ElfState::Death) {
910            self.last_event = "elf died";
911        }
912
913        let scrubbed_input = ScrubbedInput {
914            settled: settled_at(self.elf_pos.distance(SCRUBBED_ELF_POSITION)),
915        };
916        ctx.animate(Elf, &mut self.scrubbed_animator, &scrubbed_input);
917        ctx.animate(Butterfly, &mut self.butterfly_animator, &());
918    }
examples/breakout-game.rs (line 970)
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    }
examples/sprite-adventure.rs (line 1822)
1801    fn tick(&mut self, ctx: &mut TickContext<'_, Keep>) {
1802        self.previous = self.position;
1803
1804        if self.reset_requested || ctx.pressed(Button::Reset) {
1805            self.reset_requested = false;
1806            self.reset(ctx);
1807            return;
1808        }
1809
1810        let heading = if ctx.ui_wants_keyboard() {
1811            Vec2::ZERO
1812        } else {
1813            ctx.axis2(Move::Walk)
1814        };
1815        match Facing::from_heading(heading) {
1816            Some(facing) => {
1817                self.facing = facing;
1818                self.walk_ticks += 1;
1819            }
1820            None => self.walk_ticks = 0,
1821        }
1822        let stride = Vec3::new(heading.x, 0.0, -heading.y) * WALK_SPEED * ctx.dt().as_secs_f32();
1823        self.position += stride;
1824        self.simulated += ctx.dt();
1825
1826        match self.area {
1827            Area::Overworld => self.tick_overworld(ctx),
1828            Area::Cave => self.tick_cave(ctx),
1829        }
1830    }
Source

pub fn set_tick_interval(&mut self, interval: Duration)

Sets the simulated time every later tick covers; see FrameContext::set_tick_interval.

Source

pub fn close(&mut self)

Ends the run once the frame these ticks belong to is drawn; see FrameContext::close.

Source

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?
examples/ui-fonts.rs (line 811)
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    }
More examples
Hide additional examples
examples/sound-lab.rs (line 481)
474    fn handle_drag(&mut self, ctx: &mut TickContext<'_, SoundCheck>) {
475        // Read before the check below for the UI's own claim on the
476        // pointer, so a release over it still frees a source a drag moved
477        // there.
478        if ctx.released(Button::Select) {
479            self.dragging = None;
480        }
481        if ctx.ui_wants_pointer() {
482            return;
483        }
484        let ray = ctx
485            .last_camera()
486            .ray_through(ctx.pointer(), ctx.window_size());
487
488        if ctx.pressed(Button::Select) {
489            self.dragging = self.sources.iter().position(|source| {
490                ray.hit_sphere(source.position, SOURCE_PICK_RADIUS)
491                    .is_some()
492            });
493        }
494
495        let Some(index) = self.dragging else {
496            return;
497        };
498        let Some(distance) = ray.hit_plane(ray::Plane {
499            point: Vec3::ZERO,
500            normal: Vec3::Y,
501        }) else {
502            return;
503        };
504        let hit = ray.at(distance);
505        let dropped =
506            Vec2::new(hit.x, hit.z).clamp(Vec2::splat(-PLAY_BOUND), Vec2::splat(PLAY_BOUND));
507        self.sources[index].position = Vec3::new(dropped.x, SOURCE_HEIGHT, dropped.y);
508    }
examples/isometric-board.rs (line 415)
414    fn handle_click(&mut self, ctx: &mut TickContext<'_, Board>) {
415        if ctx.ui_wants_pointer() || !ctx.pressed(Button::Select) {
416            return;
417        }
418        let ray = ctx
419            .last_camera()
420            .ray_through(ctx.pointer(), ctx.window_size());
421        let (lift, half) = unit_geometry(self.turn);
422
423        if self.current().target.is_none() {
424            let center = self.current().position;
425            if ray.hit_aabb(center - half, center + half).is_some() {
426                self.selected = !self.selected;
427                return;
428            }
429        }
430        if !self.selected {
431            return;
432        }
433
434        let Some(distance) = ray.hit_plane(ray::Plane {
435            point: Vec3::ZERO,
436            normal: Vec3::Y,
437        }) else {
438            return;
439        };
440        let Some(tile) = tile_at(ray.at(distance)) else {
441            return;
442        };
443        if tile == self.current().tile || tile == self.other().tile {
444            return;
445        }
446
447        let destination = tile_center(tile) + Vec3::Y * lift;
448        let heading = destination.x - self.current().position.x;
449        let current = self.current_mut();
450        if heading.abs() > f32::EPSILON {
451            current.facing_right = heading > 0.0;
452        }
453        current.target = Some(destination);
454        self.selected = false;
455        ctx.play(Sound::Click);
456    }
Source

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?
examples/breakout-game.rs (line 404)
399    fn hold_ball(&mut self, ctx: &mut TickContext<'_, Breakout>) {
400        self.ball_prev = self.ball_pos;
401        self.ball_pos.x = self.paddle_x;
402        self.ball_trail = [self.ball_pos; TRAIL_LEN + 1];
403
404        if !ctx.ui_wants_keyboard() && ctx.pressed(Button::Serve) {
405            self.launch();
406            ctx.play(Sound::Serve);
407        }
408    }
409
410    fn step_paddle(&mut self, axis: f32, dt: f32) {
411        self.paddle_prev_x = self.paddle_x;
412        self.paddle_x =
413            (self.paddle_x + axis * PADDLE_SPEED * dt).clamp(-PADDLE_LIMIT, PADDLE_LIMIT);
414    }
415
416    fn step_ball(&mut self, ctx: &mut TickContext<'_, Breakout>, dt: f32) {
417        self.ball_prev = self.ball_pos;
418        self.ball_pos += self.ball_vel * dt;
419
420        self.bounce_walls(ctx);
421        self.bounce_paddle(ctx);
422        self.bounce_bricks(ctx);
423        self.push_trail();
424
425        if self.ball_pos.z - BALL_RADIUS > COURT_HALF_DEPTH {
426            self.lose_life(ctx);
427        }
428    }
429
430    /// Shifts the ghost trail back one slot and records the ball's newly
431    /// resolved position at the front.
432    fn push_trail(&mut self) {
433        self.ball_trail.rotate_right(1);
434        self.ball_trail[0] = self.ball_pos;
435    }
436
437    fn bounce_walls(&mut self, ctx: &mut TickContext<'_, Breakout>) {
438        let left = -COURT_HALF_WIDTH + WALL_THICKNESS;
439        let right = COURT_HALF_WIDTH - WALL_THICKNESS;
440        let top = -COURT_HALF_DEPTH + WALL_THICKNESS;
441
442        let mut hit = false;
443        if self.ball_pos.x - BALL_RADIUS < left {
444            self.ball_pos.x = left + BALL_RADIUS;
445            self.ball_vel.x = self.ball_vel.x.abs();
446            hit = true;
447        } else if self.ball_pos.x + BALL_RADIUS > right {
448            self.ball_pos.x = right - BALL_RADIUS;
449            self.ball_vel.x = -self.ball_vel.x.abs();
450            hit = true;
451        }
452
453        if self.ball_pos.z - BALL_RADIUS < top {
454            self.ball_pos.z = top + BALL_RADIUS;
455            self.ball_vel.z = self.ball_vel.z.abs();
456            hit = true;
457        }
458
459        if hit {
460            ctx.play(Sound::Bounce.pitch(WALL_BOUNCE_PITCH));
461        }
462    }
463
464    /// Bounces the ball off the paddle, steering it by where it landed.
465    fn bounce_paddle(&mut self, ctx: &mut TickContext<'_, Breakout>) {
466        if self.ball_vel.z <= 0.0 {
467            return;
468        }
469        let reach_x = PADDLE_HALF_WIDTH + BALL_RADIUS;
470        let reach_z = PADDLE_HALF_DEPTH + BALL_RADIUS;
471        let dx = self.ball_pos.x - self.paddle_x;
472        let dz = self.ball_pos.z - PADDLE_Z;
473        if dx.abs() > reach_x || dz.abs() > reach_z {
474            return;
475        }
476
477        let offset = (dx / PADDLE_HALF_WIDTH).clamp(-1.0, 1.0);
478        self.ball_vel = Vec3::new(offset, 0.0, -1.0).normalize() * BALL_SPEED;
479        self.ball_pos.z = PADDLE_Z - reach_z;
480        self.paddle_flash = PADDLE_FLASH;
481        ctx.play(Sound::Bounce.pitch(PADDLE_BOUNCE_PITCH));
482    }
483
484    /// Bounces the ball off the nearest overlapping brick, damaging it.
485    fn bounce_bricks(&mut self, ctx: &mut TickContext<'_, Breakout>) {
486        let reach_x = BRICK_HALF_WIDTH + BALL_RADIUS;
487        let reach_z = BRICK_HALF_DEPTH + BALL_RADIUS;
488        let mut broken = None;
489
490        for brick in self
491            .bricks
492            .iter_mut()
493            .filter(|brick| brick.hits_remaining > 0)
494        {
495            let dx = self.ball_pos.x - brick.position.x;
496            let dz = self.ball_pos.z - brick.position.z;
497            if dx.abs() > reach_x || dz.abs() > reach_z {
498                continue;
499            }
500
501            if reach_x - dx.abs() < reach_z - dz.abs() {
502                self.ball_vel.x = if dx < 0.0 {
503                    -self.ball_vel.x.abs()
504                } else {
505                    self.ball_vel.x.abs()
506                };
507            } else {
508                self.ball_vel.z = if dz < 0.0 {
509                    -self.ball_vel.z.abs()
510                } else {
511                    self.ball_vel.z.abs()
512                };
513            }
514
515            brick.hits_remaining -= 1;
516            self.score += 10 * (BRICK_ROWS - brick.row) as u32;
517            ctx.play(Sound::Bounce.pitch(BRICK_BOUNCE_PITCH));
518            if brick.hits_remaining == 0 {
519                ctx.play(
520                    Sound::BrickBreak
521                        .at(brick.position)
522                        .reference(BRICK_BREAK_REFERENCE),
523                );
524                self.brick_flash = BRICK_FLASH;
525                broken = Some((brick.position, BRICK_ROW_COLORS[brick.row]));
526            }
527            break;
528        }
529
530        if let Some((position, color)) = broken {
531            self.spawn_sparks(position, color);
532        }
533
534        if self.bricks.iter().all(|brick| brick.hits_remaining == 0) {
535            self.phase = Phase::Won;
536            ctx.play(Sound::LevelClear);
537        }
538    }
539
540    /// Sends [`SPARK_BURST_COUNT`] sparks outward and upward from a broken
541    /// brick's position, spread by index so no randomness is needed.
542    fn spawn_sparks(&mut self, position: Vec3, color: Color) {
543        for i in 0..SPARK_BURST_COUNT {
544            let t = i as f32 / SPARK_BURST_COUNT as f32;
545            let azimuth = t * TAU;
546            let rise = 0.6 + 0.4 * (t * 3.0).fract();
547            let speed = SPARK_SPEED_MIN.lerp(SPARK_SPEED_MAX, (t * 5.0).fract());
548            let direction = Vec3::new(azimuth.cos(), rise, azimuth.sin()).normalize();
549            self.sparks.push(Spark {
550                position,
551                velocity: direction * speed,
552                roll: azimuth,
553                age: 0.0,
554                color,
555            });
556        }
557    }
558
559    /// Integrates and ages the live sparks, dropping any past their
560    /// lifetime.
561    fn step_sparks(&mut self, dt: f32) {
562        for spark in &mut self.sparks {
563            spark.velocity.y -= SPARK_GRAVITY * dt;
564            spark.position += spark.velocity * dt;
565            spark.age += dt;
566        }
567        self.sparks.retain(|spark| spark.age < SPARK_LIFETIME);
568    }
569
570    fn lose_life(&mut self, ctx: &mut TickContext<'_, Breakout>) {
571        self.life_lost_flash = LIFE_LOST_FLASH;
572        self.lives = self.lives.saturating_sub(1);
573        if self.lives == 0 {
574            self.phase = Phase::Lost;
575            ctx.play(Sound::GameOver);
576        } else {
577            ctx.play(Sound::BallLost);
578            self.ready_serve();
579        }
580    }
581
582    fn camera() -> Camera {
583        Camera::new(
584            View::look_at(Vec3::new(0.0, 13.5, 12.5), Vec3::new(0.0, 0.0, 0.5)),
585            Projection::perspective(50.0),
586        )
587    }
588
589    /// The paddle's face material: fully lit by the court's own lights, its
590    /// base tone flashed with emissive light past `1.0` just after it last
591    /// hit the ball, and a small emissive kept under that so it stays
592    /// visible where the ball's own light does not reach.
593    fn paddle_face_material(&self) -> Material {
594        let t = (self.paddle_flash / PADDLE_FLASH).clamp(0.0, 1.0);
595        let flash = PADDLE_FLASH_EMISSIVE.dimmed(t);
596        let emissive = Color::rgb(
597            PADDLE_AMBIENT_EMISSIVE.red + flash.red,
598            PADDLE_AMBIENT_EMISSIVE.green + flash.green,
599            PADDLE_AMBIENT_EMISSIVE.blue + flash.blue,
600        );
601        Material::lit(PADDLE_BASE).emissive(emissive)
602    }
603
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 = ();
963    type PostEffects = ();
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    }
More examples
Hide additional examples
examples/sound-lab.rs (line 463)
461    fn handle_walk(&mut self, ctx: &mut TickContext<'_, SoundCheck>) {
462        self.player_prev = self.player;
463        if ctx.ui_wants_keyboard() {
464            return;
465        }
466        let walk = ctx.axis2(Move::Walk);
467        let world = Vec2::new(walk.x, -walk.y);
468        self.player = (self.player + world * WALK_SPEED * ctx.dt().as_secs_f32())
469            .clamp(Vec2::splat(-PLAY_BOUND), Vec2::splat(PLAY_BOUND));
470    }
examples/sprite-adventure.rs (line 1810)
1801    fn tick(&mut self, ctx: &mut TickContext<'_, Keep>) {
1802        self.previous = self.position;
1803
1804        if self.reset_requested || ctx.pressed(Button::Reset) {
1805            self.reset_requested = false;
1806            self.reset(ctx);
1807            return;
1808        }
1809
1810        let heading = if ctx.ui_wants_keyboard() {
1811            Vec2::ZERO
1812        } else {
1813            ctx.axis2(Move::Walk)
1814        };
1815        match Facing::from_heading(heading) {
1816            Some(facing) => {
1817                self.facing = facing;
1818                self.walk_ticks += 1;
1819            }
1820            None => self.walk_ticks = 0,
1821        }
1822        let stride = Vec3::new(heading.x, 0.0, -heading.y) * WALK_SPEED * ctx.dt().as_secs_f32();
1823        self.position += stride;
1824        self.simulated += ctx.dt();
1825
1826        match self.area {
1827            Area::Overworld => self.tick_overworld(ctx),
1828            Area::Cave => self.tick_cave(ctx),
1829        }
1830    }
Source

pub fn window_size(&self) -> UVec2

The window’s drawing area, in physical pixels; zero while minimized.

Examples found in repository?
examples/ui-fonts.rs (line 637)
634    fn handle_hail(&mut self, ctx: &mut TickContext<'_, Self>) {
635        let ray = ctx
636            .last_camera()
637            .ray_through(ctx.pointer(), ctx.window_size());
638        let Some(station) = hit_station(ray) else {
639            return;
640        };
641
642        if self.hailed != Some(station) {
643            self.hailed = Some(station);
644            let look = station.look();
645            self.dialogue = Some(Dialogue::start(look.name, look.lines.map(str::to_owned)));
646            return;
647        }
648        let Some(dialogue) = &mut self.dialogue else {
649            return;
650        };
651        if !dialogue.advance() {
652            self.dialogue = None;
653            self.hailed = None;
654        }
655    }
More examples
Hide additional examples
examples/sound-lab.rs (line 486)
474    fn handle_drag(&mut self, ctx: &mut TickContext<'_, SoundCheck>) {
475        // Read before the check below for the UI's own claim on the
476        // pointer, so a release over it still frees a source a drag moved
477        // there.
478        if ctx.released(Button::Select) {
479            self.dragging = None;
480        }
481        if ctx.ui_wants_pointer() {
482            return;
483        }
484        let ray = ctx
485            .last_camera()
486            .ray_through(ctx.pointer(), ctx.window_size());
487
488        if ctx.pressed(Button::Select) {
489            self.dragging = self.sources.iter().position(|source| {
490                ray.hit_sphere(source.position, SOURCE_PICK_RADIUS)
491                    .is_some()
492            });
493        }
494
495        let Some(index) = self.dragging else {
496            return;
497        };
498        let Some(distance) = ray.hit_plane(ray::Plane {
499            point: Vec3::ZERO,
500            normal: Vec3::Y,
501        }) else {
502            return;
503        };
504        let hit = ray.at(distance);
505        let dropped =
506            Vec2::new(hit.x, hit.z).clamp(Vec2::splat(-PLAY_BOUND), Vec2::splat(PLAY_BOUND));
507        self.sources[index].position = Vec3::new(dropped.x, SOURCE_HEIGHT, dropped.y);
508    }
examples/isometric-board.rs (line 420)
414    fn handle_click(&mut self, ctx: &mut TickContext<'_, Board>) {
415        if ctx.ui_wants_pointer() || !ctx.pressed(Button::Select) {
416            return;
417        }
418        let ray = ctx
419            .last_camera()
420            .ray_through(ctx.pointer(), ctx.window_size());
421        let (lift, half) = unit_geometry(self.turn);
422
423        if self.current().target.is_none() {
424            let center = self.current().position;
425            if ray.hit_aabb(center - half, center + half).is_some() {
426                self.selected = !self.selected;
427                return;
428            }
429        }
430        if !self.selected {
431            return;
432        }
433
434        let Some(distance) = ray.hit_plane(ray::Plane {
435            point: Vec3::ZERO,
436            normal: Vec3::Y,
437        }) else {
438            return;
439        };
440        let Some(tile) = tile_at(ray.at(distance)) else {
441            return;
442        };
443        if tile == self.current().tile || tile == self.other().tile {
444            return;
445        }
446
447        let destination = tile_center(tile) + Vec3::Y * lift;
448        let heading = destination.x - self.current().position.x;
449        let current = self.current_mut();
450        if heading.abs() > f32::EPSILON {
451            current.facing_right = heading > 0.0;
452        }
453        current.target = Some(destination);
454        self.selected = false;
455        ctx.play(Sound::Click);
456    }
Source

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.

Examples found in repository?
examples/ui-fonts.rs (line 636)
634    fn handle_hail(&mut self, ctx: &mut TickContext<'_, Self>) {
635        let ray = ctx
636            .last_camera()
637            .ray_through(ctx.pointer(), ctx.window_size());
638        let Some(station) = hit_station(ray) else {
639            return;
640        };
641
642        if self.hailed != Some(station) {
643            self.hailed = Some(station);
644            let look = station.look();
645            self.dialogue = Some(Dialogue::start(look.name, look.lines.map(str::to_owned)));
646            return;
647        }
648        let Some(dialogue) = &mut self.dialogue else {
649            return;
650        };
651        if !dialogue.advance() {
652            self.dialogue = None;
653            self.hailed = None;
654        }
655    }
More examples
Hide additional examples
examples/sound-lab.rs (line 485)
474    fn handle_drag(&mut self, ctx: &mut TickContext<'_, SoundCheck>) {
475        // Read before the check below for the UI's own claim on the
476        // pointer, so a release over it still frees a source a drag moved
477        // there.
478        if ctx.released(Button::Select) {
479            self.dragging = None;
480        }
481        if ctx.ui_wants_pointer() {
482            return;
483        }
484        let ray = ctx
485            .last_camera()
486            .ray_through(ctx.pointer(), ctx.window_size());
487
488        if ctx.pressed(Button::Select) {
489            self.dragging = self.sources.iter().position(|source| {
490                ray.hit_sphere(source.position, SOURCE_PICK_RADIUS)
491                    .is_some()
492            });
493        }
494
495        let Some(index) = self.dragging else {
496            return;
497        };
498        let Some(distance) = ray.hit_plane(ray::Plane {
499            point: Vec3::ZERO,
500            normal: Vec3::Y,
501        }) else {
502            return;
503        };
504        let hit = ray.at(distance);
505        let dropped =
506            Vec2::new(hit.x, hit.z).clamp(Vec2::splat(-PLAY_BOUND), Vec2::splat(PLAY_BOUND));
507        self.sources[index].position = Vec3::new(dropped.x, SOURCE_HEIGHT, dropped.y);
508    }
examples/isometric-board.rs (line 419)
414    fn handle_click(&mut self, ctx: &mut TickContext<'_, Board>) {
415        if ctx.ui_wants_pointer() || !ctx.pressed(Button::Select) {
416            return;
417        }
418        let ray = ctx
419            .last_camera()
420            .ray_through(ctx.pointer(), ctx.window_size());
421        let (lift, half) = unit_geometry(self.turn);
422
423        if self.current().target.is_none() {
424            let center = self.current().position;
425            if ray.hit_aabb(center - half, center + half).is_some() {
426                self.selected = !self.selected;
427                return;
428            }
429        }
430        if !self.selected {
431            return;
432        }
433
434        let Some(distance) = ray.hit_plane(ray::Plane {
435            point: Vec3::ZERO,
436            normal: Vec3::Y,
437        }) else {
438            return;
439        };
440        let Some(tile) = tile_at(ray.at(distance)) else {
441            return;
442        };
443        if tile == self.current().tile || tile == self.other().tile {
444            return;
445        }
446
447        let destination = tile_center(tile) + Vec3::Y * lift;
448        let heading = destination.x - self.current().position.x;
449        let current = self.current_mut();
450        if heading.abs() > f32::EPSILON {
451            current.facing_right = heading > 0.0;
452        }
453        current.target = Some(destination);
454        self.selected = false;
455        ctx.play(Sound::Click);
456    }
Source

pub fn config(&self) -> &Config

The configuration the engine started with.

Auto Trait Implementations§

§

impl<'a, G> !RefUnwindSafe for TickContext<'a, G>

§

impl<'a, G> !Send for TickContext<'a, G>

§

impl<'a, G> !Sync for TickContext<'a, G>

§

impl<'a, G> !UnwindSafe for TickContext<'a, G>

§

impl<'a, G> Freeze for TickContext<'a, G>
where &'a mut MeshCache<<G as Game>::Meshes>: Freeze, &'a mut Audio<<G as Game>::Sounds>: Freeze,

§

impl<'a, G> Unpin for TickContext<'a, G>
where &'a mut MeshCache<<G as Game>::Meshes>: Unpin, &'a mut Audio<<G as Game>::Sounds>: Unpin,

§

impl<'a, G> UnsafeUnpin for TickContext<'a, G>
where &'a mut MeshCache<<G as Game>::Meshes>: UnsafeUnpin, &'a mut Audio<<G as Game>::Sounds>: UnsafeUnpin,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert 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>

Convert 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)

Convert &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)

Convert &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> Downcast<T> for T

Source§

fn downcast(&self) -> &T

Source§

impl<S, T> Duplex<S> for T
where T: FromSample<S> + ToSample<S>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<S> FromSample<S> for S

Source§

fn from_sample_(s: S) -> S

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T, S> SimdFrom<T, S> for T
where S: Simd,

Source§

fn simd_from(_simd: S, value: T) -> T

Source§

impl<F, T, S> SimdInto<T, S> for F
where T: SimdFrom<F, S>, S: Simd,

Source§

fn simd_into(self, simd: S) -> T

Source§

impl<T, U> ToSample<U> for T
where U: FromSample<T>,

Source§

fn to_sample_(self) -> U

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> Upcast<T> for T

Source§

fn upcast(&self) -> Option<&T>

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more