Movement Modes
Core movement is built into the character and always runs. Slide, swim, and whatever your game needs are components you add or delete. Enabling swimming is adding SwimMode; disabling it is removing SwimMode. There is no motor to edit either way.
Core, and modules
PartyCharacter is the motor. Walking, running, jumping, falling, gravity, facing and the ground sensors live in it, always run, and are not a module.
Most other ways of moving are components implementing IMovementModule (crouch is the one exception — see below). The character finds them with a plain GetComponents call, so the set of movement your game has is the set of components on the prefab — no registration, no priority table in the motor, no list to keep in sync.
// Enable swimming for a game:
player.AddComponent<SwimMode>();
// Disable it:
Destroy(player.GetComponent<SwimMode>());
// Neither of those touches PartyCharacter.Each frame the motor hands control to the highest-priority module whose WantsControl is true. Core walk runs when none of them wants it. Crucially, a module owns its own detection: swim looks for water, slide looks at the slope. The motor does not centralise any of that, which is why adding a module cannot require editing it.
| Field | Type | What it does |
|---|---|---|
Priority | int | Higher wins when several modules want control in the same frame. The two shipped modules are Swim at 100 and Slide at 90. |
WantsControl(motor) | bool | The module's own activation test — water for swim, slope for slide. This is the row that used to be in a central table. |
Mode | IMovementMode | The actuator run while this module holds control. Usually the component itself, which implements both interfaces. |
What a mode is
IMovementMode is the acting half — enter, tick, exit, and a say in what it will hand over to. It also declares two things about itself once, at its own definition, which is the pattern worth copying:
public interface IMovementMode
{
LocomotionCategory Category { get; }
int AnimatorModeId { get; }
void Enter(PartyCharacter motor);
void Tick(PartyCharacter motor, float fixedDeltaTime);
void Exit(PartyCharacter motor);
bool CanTransitionTo(IMovementMode next);
}| Field | Type | What it does |
|---|---|---|
Grounded | LocomotionCategory | Normal ground-based movement — walk, slide, crouch. If the ground sensor says otherwise while in this category, that is a real jump or fall, not a mode-specific quirk. |
Attached | LocomotionCategory | Stuck to a surface under its own rules, such as a ladder or a zipline. Not falling, even though the ground sensor reads false the whole time. |
Submerged | LocomotionCategory | Moving through a fluid volume under its own rules. Same reasoning as Attached — not falling. |
Animator ids
The motor writes each mode's AnimatorModeId to an integer animator parameter every frame, and the controller gives each locomotion state a transition conditioned on it. The bundled values:
| Field | Type | What it does |
|---|---|---|
Walk | 0 | Core locomotion. |
Slide | 1 | SlideMode. |
Swim | 2 | SwimMode. |
Crouch | 3 | CrouchMode. |
Tuning: MovementConfig
One asset holds the numbers — Create → Party Core Kit → Movement Config— shared by the motor and every module, so a character's feel is one file you can duplicate per character type.
| Field | Type | Default | What it does |
|---|---|---|---|
walkSpeed | float | 4 | Base ground speed, metres per second. |
walkAcceleration | float | 25 | How quickly the character reaches its target speed. |
rotationSpeed | float | 720 | Degrees per second the character turns to face its direction of travel. |
jumpForce | float | 5 | Jump impulse. |
sprintActivation | ButtonActivationMode | Hold | Hold: sprint only while the button is down. Toggle: press once to start, again to stop. |
sprintStaminaCostPerSecond | float | 12 | Stamina drained per second while actually sprint-moving. Needs a stamina provider on the player — without one, sprint is free. 0 disables the cost. |
staminaRegenPerSecond | float | 8 | Stamina restored per second while the sprint button is not held. 0 disables regeneration. |
stepOffset | float | 0.4 | Tallest ledge the character walks straight up without jumping — curbs, stair treads. Anything above is a wall you must jump. 0 disables stepping entirely. |
groundSnapDistance | float | 0.3 | How far below the feet the motor keeps hunting for ground while walking. Without it, every downward step launches a brief fall — which is what makes descents flicker between walk and fall animations. |
airControl | float | 0.4 | Fraction of ground acceleration available mid-air. 1 is identical steering airborne and grounded; 0 is committed jumps with no steering. |
coyoteTime | float | 0.12 | Grace period after walking off a ledge during which a jump still works. The single most forgiving thing in a platformer; 0 means a frame-perfect edge. |
jumpBufferTime | float | 0.15 | How long a jump press is remembered while airborne, so pressing slightly before landing still jumps on touchdown instead of being swallowed. |
uphillSpeedMultiplier | float | 0.7 | Fraction of flat-ground pace kept while climbing a slope. Eases in with steepness and with how directly you face up it; running across a slope is never affected. |
downhillSpeedMultiplier | float | 1 | Fraction of flat-ground pace kept while descending. The default means a descent covers exactly as much ground per second as flat ground. |
crouchSpeed | float | 2 | Ground speed while crouched. |
crouchHeight | float | 1 | Capsule height while crouched. Standing height is read from the capsule collider at startup. |
crouchActivation | ButtonActivationMode | Hold | Hold or Toggle, same as sprint. |
slideThresholdAngle | float | 40 | Slope angle above which walking becomes sliding — and ALSO the motor's walkable limit. See the warning below. |
slideAcceleration | float | 12 | How hard gravity pulls along a slide. |
maxSlideSpeed | float | 10 | Speed cap while sliding. |
swimSpeed | float | 3 | Horizontal swim speed. |
swimAscendSpeed | float | 3 | Vertical rise while Jump is held in water — a smooth swim-up, not a jump impulse. |
swimSinkSpeed | float | 1 | Vertical sink while Jump is not held: you must actively ascend or you slowly go down. |
swimDiveSpeed | float | 3 | Vertical dive while Crouch is held — actively pushes down against buoyancy. Release and you float back up. |
swimAcceleration | float | 10 | Acceleration toward the wish direction while actively swimming. |
waterDrag | float | 6 | Passive water resistance: how fast velocity settles when there is no input, and how fast drift decays after releasing it. |
customCharacterController | RuntimeAnimatorController | — | Leave empty for the auto-generated default controller, rebuilt on every Install from your character's clips. Assign your own to take full ownership — Install will use it as-is and never overwrite it. |
The shipped modules
| Field | Type | What it does |
|---|---|---|
SwimMode | Priority 100 | Takes control in water. Detects a trigger volume by tag, and distinguishes a swimmable pool from an out-of-bounds sea by size. |
SlideMode | Priority 90 | Takes control on ground steeper than slideThresholdAngle. Accelerates downhill to a capped speed. |
CrouchMode | core, not a module | Driven by the motor's core selection rather than the module list. Shrinks the capsule while active, and stays sticky — it will not hand back to walking while standing up would clip into something overhead. |
Swim has a few settings of its own, since water needs describing:
| Field | Type | Default | What it does |
|---|---|---|---|
priority | int | 100 | Selection priority — higher runs first when several modules want control. |
waterTag | string | "Water" | A trigger volume with this tag is water. |
maxSwimVolumeSize | float | 30 | Water this size or smaller in X/Z is a swimmable pool; larger is the out-of-bounds sea. |
seaIsOutOfBounds | bool | true | Falling into the huge sea triggers the out-of-bounds respawn — freeze plus swim animation. Off means the sea is ignored and you fall to the kill height. |
Writing your own mode
Implement both halves on one component
IMovementModulefor selection —Priority,WantsControl,Mode— andIMovementModefor acting. ReturningthisfromModeis the shipped pattern.Own your detection
Put the "should I be running?" test insideWantsControl. Nothing else needs to know your mode exists.Declare your category and animator id
Pick theLocomotionCategorythat describes your physics, and an unusedAnimatorModeId. Add a matching state to your controller.Add the component
That is the installation step. There is no registration call.