pub struct Animator<M, S: AnimationStates> { /* private fields */ }Expand description
The machine a game poses a draw with: the state it is in, what that state plays, and the fade out of the state it left.
Typed by the mesh it poses, so a draw of another mesh does not take it.
ctx.animate runs one and
Instance::posed draws in the pose it
holds; no clip length and no time of the game’s own crosses either call.
Implementations§
Source§impl<M, S: AnimationStates> Animator<M, S>
impl<M, S: AnimationStates> Animator<M, S>
Sourcepub fn new() -> Self
pub fn new() -> Self
A machine in its entry state, at the start of what that state plays reading the default input.
Examples found in repository?
659 fn init(_ctx: &mut InitContext<'_, Scene>) -> Result<Self, Error> {
660 Ok(Self {
661 elf_pos: ELF_START,
662 elf_prev: ELF_START,
663 elf_yaw: 0.0,
664 elf_height: 0.0,
665 elf_height_prev: 0.0,
666 jump_speed: 0.0,
667 elf_input: ElfInput::default(),
668 elf_animator: Animator::new(),
669 scrubbed_animator: Animator::new(),
670 butterfly_animator: Animator::new(),
671 hits: 0,
672 in_patch: false,
673 holding: false,
674 camera_yaw: 0.0,
675 camera_pitch: 0.0,
676 last_event: "none yet",
677 })
678 }
679
680 /// Turns the camera by how far the pointer moves sideways and upward,
681 /// [`CAMERA_PITCH_RANGE`] holding how far it tilts.
682 fn steer_camera(&mut self, ctx: &mut FrameContext<'_, Scene>) {
683 self.camera_yaw -= ctx.axis(Axis::CameraYaw);
684 self.camera_pitch = (self.camera_pitch + ctx.axis(Axis::CameraPitch))
685 .clamp(CAMERA_PITCH_RANGE.start, CAMERA_PITCH_RANGE.end);
686 }
687
688 /// Turns `elf_yaw` toward the heading `ctx` reads, relative to the
689 /// camera's own turn, and moves `elf_pos` along it; the speed it moves
690 /// at, a fraction of [`ELF_SPEED`], held at [`WALK_CAP`] until
691 /// `Button::Run` is held.
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 }Sourcepub fn state(&self) -> S
pub fn state(&self) -> S
The state it is in, which is the state being faded into while a fade runs.
Examples found in repository?
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 }Sourcepub fn entered(&self, state: S) -> bool
pub fn entered(&self, state: S) -> bool
Whether a transition into state started the last time animate
ran it.
Examples found in repository?
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 }Sourcepub fn left(&self, state: S) -> bool
pub fn left(&self, state: S) -> bool
Whether a transition out of state started the last time animate
ran it.
Examples found in repository?
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 }Sourcepub fn transitioning(&self) -> bool
pub fn transitioning(&self) -> bool
Whether a fade out of the state it left was still running the last
time animate ran it.
Examples found in repository?
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 }Trait Implementations§
Source§impl<M, S: AnimationStates> Clone for Animator<M, S>
impl<M, S: AnimationStates> Clone for Animator<M, S>
Source§impl<M, S: AnimationStates> Debug for Animator<M, S>
impl<M, S: AnimationStates> Debug for Animator<M, S>
Source§impl<M, S: AnimationStates> Default for Animator<M, S>
impl<M, S: AnimationStates> Default for Animator<M, S>
Source§fn default() -> Self
fn default() -> Self
A machine in its entry state; see Animator::new.
Auto Trait Implementations§
impl<M, S> Freeze for Animator<M, S>
impl<M, S> RefUnwindSafe for Animator<M, S>
impl<M, S> Send for Animator<M, S>
impl<M, S> Sync for Animator<M, S>
impl<M, S> Unpin for Animator<M, S>
impl<M, S> UnsafeUnpin for Animator<M, S>
impl<M, S> UnwindSafe for Animator<M, S>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
impl<S, T> Duplex<S> for Twhere
T: FromSample<S> + ToSample<S>,
Source§impl<S> FromSample<S> for S
impl<S> FromSample<S> for S
fn from_sample_(s: S) -> S
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more