diff --git a/rust/src/player/mod.rs b/rust/src/player/mod.rs index 81f9509..674a7a0 100644 --- a/rust/src/player/mod.rs +++ b/rust/src/player/mod.rs @@ -1,6 +1,10 @@ use std::{collections::HashSet, f32::consts::PI, ops::Neg}; -use godot::{classes::ICharacterBody3D, obj::WithBaseField, prelude::*}; +use godot::{ + classes::{ICharacterBody3D, KinematicCollision3D}, + obj::WithBaseField, + prelude::*, +}; use serde::{Deserialize, Serialize}; use crate::{ @@ -70,25 +74,56 @@ pub trait PlayerCommon { impl PlayerCommon for T { fn player_update(&mut self, delta: f64) { + // Gravity self.set_y_speed(self.get_y_speed() - 9.8 * delta as f32); if self.base().is_on_floor() && self.get_y_speed() < 0. { self.set_y_speed(0.); } + let vertical_velocity = Vector3::UP * self.get_y_speed(); - let speed = Vector3::UP * self.get_y_speed() - + self - .get_movement_dir() - .try_normalized() - .unwrap_or_default() - .rotated(Vector3::UP, self.base().get_rotation().y) - * self.get_move_speed(); + // Figure out the ground plane normal (to move on the right plane) + let mut up_vector = Vector3::UP; + let mut most_upward_coll: Option> = None; + for coll in (0..self.base().get_slide_collision_count()) + .flat_map(|i| self.base().get_slide_collision(i)) + { + let coll_norm_y = coll.get_normal().y; + if let Some(prev_most_upward) = &most_upward_coll { + if coll_norm_y > prev_most_upward.get_normal().y { + most_upward_coll = Some(coll); + } + } else if coll_norm_y > 0.5 { + // sin(30 deg) == 0.5, so this only accepts 30 deg or shallower slopes + most_upward_coll = Some(coll); + } + } + if let Some(coll) = most_upward_coll { + up_vector = coll.get_normal(); + } + // Ground movement (along the plane the player is currently on) + let input_move_on_xz = self.get_movement_dir().try_normalized().unwrap_or_default(); + let ground_plane_basis = Basis::from_axis_angle(up_vector, self.base().get_rotation().y); + let input_move_on_ground_plane = ground_plane_basis * input_move_on_xz; + + let move_speed = self.get_move_speed(); + let ground_plane_velocity = input_move_on_ground_plane * move_speed; + + // Commit if self.is_dead() { self.base_mut().set_velocity(Vector3::ZERO); } else { - self.base_mut().set_velocity(speed); + self.base_mut() + .set_velocity(vertical_velocity + ground_plane_velocity); self.base_mut().move_and_slide(); } + + // Check if hitting a ceiling; if so, stop upwards velocity (done here instead of + // at the start of the next frame to get the right-after-move_and_slide state + // rather than the simulated one). + if self.base().get_velocity().y < 0.001 && self.get_y_speed() > 0.001 { + self.set_y_speed(0.0); + } } fn try_jump(&mut self) -> bool {