Import Oxide engine (Stages 0–10) under MIT license
Full project snapshot migrated to new Gitea remote without history: engine, editor, physics, script, examples, tests, docs, and assets. Relicensed from GPLv3 to MIT and updated repo URLs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
//! The [`CharacterController`] component — a kinematic capsule character.
|
||||
//!
|
||||
//! A character controller is *not* a rigid body: it never reacts to forces.
|
||||
//! Instead the game asks it to move by some desired translation each frame and
|
||||
//! the controller resolves that against the world's colliders — sliding along
|
||||
//! walls, stepping up small ledges, sticking to the ground on slopes, and
|
||||
//! refusing to climb anything too steep — then reports whether the character
|
||||
//! ended up grounded. It is the basis for player/NPC movement and is reused by
|
||||
//! the Stage-15 prototyping kit.
|
||||
//!
|
||||
//! The character is a **capsule** (radius + cylindrical half-height along `+Y`).
|
||||
//! An entity with this component is moved through
|
||||
//! [`PhysicsWorld::move_character`](crate::PhysicsWorld::move_character); it does
|
||||
//! **not** carry a [`RigidBody`](crate::RigidBody)/[`Collider`](crate::Collider)
|
||||
//! (it isn't simulated), so it never appears in the body set and never
|
||||
//! self-collides.
|
||||
|
||||
use oxide_engine::math::Vec3;
|
||||
use oxide_engine::reflect::Reflect;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Component: a kinematic capsule character moved by move-and-slide.
|
||||
///
|
||||
/// Tune the capsule shape (`radius`/`half_height`) and the movement rules
|
||||
/// (`max_slope_degrees`, `step_offset`, `snap_to_ground`, `skin_width`). Pass an
|
||||
/// entity carrying this to
|
||||
/// [`PhysicsWorld::move_character`](crate::PhysicsWorld::move_character).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Reflect)]
|
||||
pub struct CharacterController {
|
||||
/// Capsule radius.
|
||||
pub radius: f32,
|
||||
/// Capsule cylindrical half-height (the straight segment between the two
|
||||
/// hemispherical caps), along local `+Y`.
|
||||
pub half_height: f32,
|
||||
/// Steepest ground slope (degrees from horizontal) the character will walk
|
||||
/// up; steeper surfaces are treated as walls.
|
||||
pub max_slope_degrees: f32,
|
||||
/// Tallest step/ledge the character will automatically climb. `0` disables
|
||||
/// auto-stepping.
|
||||
pub step_offset: f32,
|
||||
/// Distance below the feet within which the character snaps down to stay
|
||||
/// grounded (prevents bouncing down stairs/slopes). `0` disables snapping.
|
||||
pub snap_to_ground: f32,
|
||||
/// A small collision-detection margin kept around the capsule.
|
||||
pub skin_width: f32,
|
||||
}
|
||||
|
||||
impl Default for CharacterController {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
radius: 0.3,
|
||||
half_height: 0.6,
|
||||
max_slope_degrees: 45.0,
|
||||
step_offset: 0.3,
|
||||
snap_to_ground: 0.2,
|
||||
skin_width: 0.01,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CharacterController {
|
||||
/// The total half-height of the capsule (cylinder half-height + one radius),
|
||||
/// i.e. the distance from the capsule center to either tip.
|
||||
pub fn total_half_height(&self) -> f32 {
|
||||
self.half_height + self.radius
|
||||
}
|
||||
|
||||
/// The local-space offset from the capsule center down to the feet (the
|
||||
/// bottom tip), useful for placing/grounding the character.
|
||||
pub fn foot_offset(&self) -> Vec3 {
|
||||
Vec3::new(0.0, -self.total_half_height(), 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn default_is_a_reasonable_humanoid_capsule() {
|
||||
let c = CharacterController::default();
|
||||
assert_eq!(c.radius, 0.3);
|
||||
assert!((c.total_half_height() - 0.9).abs() < 1e-6);
|
||||
assert!((c.foot_offset() - Vec3::new(0.0, -0.9, 0.0)).length() < 1e-6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trips_through_ron() {
|
||||
let c = CharacterController {
|
||||
radius: 0.4,
|
||||
half_height: 1.0,
|
||||
max_slope_degrees: 50.0,
|
||||
step_offset: 0.0,
|
||||
snap_to_ground: 0.0,
|
||||
skin_width: 0.02,
|
||||
};
|
||||
let ron = ron::to_string(&c).unwrap();
|
||||
let back: CharacterController = ron::from_str(&ron).unwrap();
|
||||
assert_eq!(c, back);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user