CharacterMotor
Physics-driven movement and rotation component for characters. Manages velocity, acceleration, grounding detection, collision response, and jump mechanics. Internally creates and drives a Unity CharacterController for physics-based movement.
Definition
Namespace: Paragon.Townskeep.CharacterSystem Assembly: Townskeep.dll
[Serializable]
public class CharacterMotor : CharacterComponentInheritance: CharacterComponent → CharacterMotor
Remarks
CharacterMotor is the lowest-level movement primitive in the Character system. It operates on a target pose model: callers set a desired position and rotation via MoveBy(), MoveTo(), RotateBy(), etc., and the motor interpolates towards that target each frame during Tick().
Unity CharacterController Integration
On OnInitialize(), the motor creates a UnityEngine.CharacterController component on the character's GameObject, configured with the motor's body parameters (Radius, Height, SkinWidth, Center, StepOffset). The Unity controller's Move() method is cached as a delegate for efficient per-frame calls.
The Unity CharacterController component is hidden in the Inspector (HideFlags.HideInInspector) since all configuration is driven by the motor's serialized fields.
Tick Loop
Each frame, Tick(float dt) runs the following pipeline:
CheckIsGrounded — SphereCast downward to detect ground
UpdateAcceleration — Apply gravity (5x) when airborne; zero when grounded
UpdateVelocity — Compute horizontal velocity towards target, add vertical acceleration
UpdatePosition — Move via
CharacterController.Move()or snap for small distancesUpdateRotation — Slerp towards target rotation
CheckIsGrounded — Re-check after movement
ApplyCollision — CapsuleCast to deflect velocity off obstacles when airborne
Speed Modes
The motor supports three speed tiers, switched via SetSprint() and SetWalk():
Walk
WalkSpeed
SetWalk(true)
Normal
MoveSpeed
SetWalk(false) or SetSprint(false)
Sprint
SprintSpeed
SetSprint(true)
Minimum Step Distance
When the target is very close (< MinimumStepDistance) and the velocity for this frame is also below the threshold, the motor snaps directly to the target position instead of using CharacterController.Move(). This prevents micro-oscillation around the target.
Quick Lookup
Move by local-space delta
motor.MoveBy(new Vector3(1, 0, 0))
Move by world-space delta
motor.MoveBy(delta, worldSpace: true)
Move to world position
motor.MoveTo(position)
Rotate by angle (input-driven)
motor.RotateBy(angle)
Face a direction
motor.RotateTowards(direction)
Face a position
motor.RotateTo(position)
Jump
motor.Jump()
Sprint mode
motor.SetSprint(true)
Walk mode
motor.SetWalk(true)
Check if grounded
motor.IsGrounded
Read current velocity
motor.Velocity
Read current speed tier
motor.CurrentSpeed
Read facing direction
motor.Direction
Properties
Acceleration
Vector3
Current acceleration vector (gravity when airborne, zero when grounded)
Velocity
Vector3
Current velocity vector
IsGrounded
bool
Whether the character is touching the ground
CurrentSpeed
float
Active speed tier (walk, normal, or sprint)
Direction
Vector3
Character's forward direction (transform.forward)
TargetPose
Pose
The target position and rotation the motor is interpolating towards
Fields
Movement
WalkSpeed
float
Speed when walking
MoveSpeed
float
Default movement speed
SprintSpeed
float
Speed when sprinting
RotationSpeed
float
Rotation speed multiplier
JumpSpeed
float
Initial vertical velocity when jumping
Body
Radius
float
Capsule collider radius
Height
float
Capsule collider height
Center
Vector3
Capsule collider center offset
SkinWidth
float
CharacterController skin width
StepOffset
float
Max step height the character can climb
MinimumStepDistance
float
Below this distance, snap to target instead of Move()
Methods
MoveBy
Sets the target position as a delta from the current position. The delta is clamped to magnitude 1.0.
delta
Vector3
—
Movement direction and magnitude (clamped to 1.0)
worldSpace
bool
false
If false, delta is transformed from local to world space
MoveTo
Sets the target position to a specific world-space coordinate. Internally calls MoveBy() with worldSpace: true.
position
Vector3
World-space target position
RotateBy
Rotates the target rotation by an angle around the Y axis. Multiplied by RotationSpeed * Time.deltaTime.
angle
float
Rotation angle (typically raw input axis value)
RotateTowards
Sets the target rotation to face a world-space direction. The Y component is zeroed out (horizontal rotation only).
direction
Vector3
World-space direction to face (Y component ignored)
RotateTo
Sets the target rotation to face a world-space position. Computes the direction from the character to the target, then calls RotateTowards().
target
Vector3
World-space position to face
Jump
Applies an upward velocity impulse if the character is grounded.
Grounded check — Jump() only applies velocity when IsGrounded is true. Calling it while airborne has no effect.
SetSprint
Sets the active speed to SprintSpeed (enabled) or MoveSpeed (disabled).
enableSprint
bool
true for sprint speed, false for normal speed
SetWalk
Sets the active speed to WalkSpeed (enabled) or MoveSpeed (disabled).
enableWalk
bool
true for walk speed, false for normal speed
OnDebug (override)
Draws debug visualization: current pose, target pose, line between them, and velocity ray. Only draws when the character is spawned.
Common Pitfalls
MoveBy clamps to magnitude 1.0 Vector3.ClampMagnitude(delta, 1f) is applied to the input. If you need to move further than 1 unit per call, use MoveTo() instead or call MoveBy() multiple frames with the target accumulating naturally.
Gravity is multiplied by 5x When airborne, the motor applies Physics.gravity * 5f as acceleration. This produces faster-than-realistic falling. Adjust the gravity scale if different fall speed is needed (currently hardcoded).
SetSprint and SetWalk share the same speed variable Both methods write to currentSpeed. Calling SetSprint(true) followed by SetWalk(true) results in walk speed. The last call wins. There is no priority system.
Unity CharacterController is hidden The motor creates a Unity CharacterController with HideFlags.HideInInspector. It will not appear in the Inspector. If you need to debug collision parameters, temporarily remove the hide flag or inspect via code.
RotateBy uses Time.deltaTime internally The RotateBy() method already multiplies by Time.deltaTime * RotationSpeed. Do not multiply your input by deltaTime before passing it in, or rotation will be frame-rate-dependent squared.
Examples
Direct Motor Control
Reading Motor State
Speed Mode Switching
See Also
CharacterController — high-level facade that delegates to this motor
CharacterComponent — abstract base class
Character — the owning entity
Motor Overview — subfolder overview
Last updated