Gameplay Verbs
The gameplay layer gives a 2D body the actions and questions a game designer talks about: jump off a wall, bounce off a pad, drive along the floor, stick to it, align to it, aim at an angle, fall slower, predict where the ball lands, ask whether a jump can still reach a height. Game code then reads like its rules:
using Altruist.Gaming.TwoD;
if (jumpPressed && onSurface) car.JumpOff(surfaceNormal, cfg.JumpSpeed);
if (grounded)
{
car.DriveAlong(tangent, throttle * cfg.MaxSpeed, cfg.Acceleration, dt);
car.StickTo(groundNormal, cfg.Grip, dt);
car.AlignToSurface(groundNormal, cfg.AlignRate);
}
else
{
car.AimAt(stickAngle, cfg.AimGain, cfg.MaxSpin, cfg.SpinAcceleration, dt);
car.ApplyDrag(cfg.AirDrag, dt);
}
car.ClampTopSpeed(cfg.TopSpeed);
ball.Fall(gravity, dt, gravityScale: superCharged ? 0.5f : 1f);
Verbs stay generic: they name what happens to the body, never which game it is. Each verb delegates to one physics-layer operation, which delegates to the math layer, keeping the exact operation order: a verb gives the same bits as the hand-written update it replaces. Conventions: +Y up, gravity is the pull toward -Y, counter-clockwise angles with the body's local +Y as its up.
Launching and Jumping
| Verb | Effect |
|---|---|
JumpOff(surfaceNormal, speed) | Cancel any motion into the surface, then add speed out along its normal (pass the body's own up for an air jump) |
PushAlong(direction, speed) | Add speed in a direction, keep the current motion |
LaunchAlong(direction, speed) | Replace the motion with speed along direction |
LaunchForward(localForward, speed) | LaunchAlong the body's own axis (e.g. Vector2.UnitX) in world space |
CancelMotionAgainst(direction, fraction = 1) | Cancel part of the motion opposing a thrust before applying it |
BounceOff(normal, outSpeed, tangentKeep[, incoming]) | Rebound or launch pad: outSpeed out of the surface plus a share of the motion along it, from the current or a recorded velocity |
AbsorbImpact(velocityBefore, spinBefore, keep) | Keep only keep of the velocity change a hit caused (recoil) |
Driving on Surfaces
| Verb | Effect |
|---|---|
DriveAlong(tangent, targetSpeed, acceleration, dt) | Speed up / slow down along the surface tangent, motion across it untouched |
BrakeAlong(direction, deceleration, dt) | Brake toward a stop along a direction |
SetSpeedAlong(direction, speed) | Set the speed along a direction (e.g. a fixed drift off a wall) |
FollowSurface(tangent) | Carry all speed along the tangent, so curves do not throw the body off |
StickTo(surfaceNormal, acceleration, dt) | Press into the surface (grip) |
BodyMotionExtensions2D.SpeedAlong(direction) reads the current speed along a direction for the decisions between these verbs.
Horizontal and Vertical Motion
Each writes one component directly and keeps the other (a component along (1, 0) through SetSpeedAlong would round differently):
| Verb | Effect |
|---|---|
SetVelocityX(x) / SetVelocityY(y) | Set one component |
ScaleVelocityX(factor) / ScaleVelocityY(factor) | Scale one component (cut the run-up when launching up, damp the lift of a burst) |
ClampVelocityY(min, max) | MathF.Min(MathF.Max(v.Y, min), max): e.g. no fall and at most a small hop |
// A flip burst: a fixed horizontal speed, the lift capped to a small hop.
body.SetVelocityX(flipDir * burstSpeed);
body.ClampVelocityY(0, cfg.HopSpeed);
Speed, Drag and Gravity
| Verb | Effect |
|---|---|
ClampTopSpeed(maxSpeed) | Never faster than maxSpeed |
ApplyDrag(drag, dt) | Linear (air) drag for one step |
BleedSpeed(amount) | Lose amount of speed, keep the direction |
Fall(gravity, dt, gravityScale = 1) | Per-body gravity for one step: below 1 floats / hovers, above 1 is heavier |
KeepRolling(minSpeed, fallbackDirection) | Never come to rest horizontally |
Orientation
| Verb | Effect |
|---|---|
AlignToSurface(normal, rate) | Turn the body's up toward a surface normal |
TurnToAngleIn(angle, time, minTime) | Turn so the angle is reached in time seconds |
AimAt(angle, gain, maxRate, angularAcceleration, dt) | Stick-style steering: rate proportional to the error, capped, reached with limited angular acceleration |
StopSpinning(angularAcceleration, dt) | Slow the spin to zero |
HoldAngle(angle, gain) | Hold an angle with a proportional rate |
UprightAngleOn(normal) | The angle standing upright on that surface, nearest the current rotation (never an extra full turn) |
LevelAngle() | The level angle nearest the current rotation (after a spin) |
SnapUprightOn(normal, sink = 0) | Snap upright onto a surface, move sink into it, stop spinning |
FlipHalfTurn() | Half a turn on the spot (roof becomes underside), position and motion kept |
JumpAsAimed(aimedRotation, speed) | JumpOff(Rotation2D.UpAt(aimedRotation), speed): jump along where the player aims, not the half-turned body |
ResetMotion(position, angle, velocity, spin) | Place the body with a motion and wake it |
Questions (Prediction, Reach, Aim)
Bots and assists ask these:
| Verb | Answer |
|---|---|
ImpactSpeedAgainst(normal) | How hard the body hits a surface |
VelocityAt(worldPoint) | Velocity of a point of the body (spin included) |
PredictPosition(t, gravity) | Where a free-flying body is in t seconds |
PredictLanding(gravity, dt, steps, floorY, restitution) | Position and velocity after stepping, bouncing on a floor |
RiseLeft(gravity) | How much higher free flight still takes it |
CanReachHeight(height, gravity, margin = 0) | Whether free flight still reaches height above it |
WillEnter(zone, gravity, lookahead, steps, marginX, marginY, velocity?) | Whether its flight enters an Aabb2D (a goal, a hazard) within lookahead seconds |
PredictZoneEntry(zones, gravity, dt, steps, floorY = -∞) | (Zone, Step): which of several zones its flight enters first, and when; (-1, -1) if none or it lands first |
UpDirection() / HeightAbove(point) | The body's up in the world / how far a point is above its center along that up (a one-way platform: is the ball over the top face?) |
VelocityToHit(target, gravity, time) | The throw that lands on target in time seconds |
TimeToReach(target, acceleration, maxSpeed) | Seconds to get there in a straight line from the current speed toward it |
BrakingDistanceAlong(direction, deceleration) | Distance needed to stop the motion along a direction |
IsWithinBox(point, halfW, halfH, padX, padY) / IsClearOfBox(point, halfW, halfH, gap) | A point against the body's box (e.g. a nose tip against another car) |
if (ball.WillEnter(ownGoal, gravity, lookahead: 1.2f, steps: 24, marginX: 1.3f))
keeper.JumpOff(Vector2.UnitY, cfg.JumpSpeed);
var lob = striker.VelocityToHit(target, gravity, time: 0.9f);
if (!bot.CanReachHeight(ball.Position.Y - bot.Position.Y, gravity, margin: 1)) PlanAerial();
Facing Bodies (FacingFrame2D)
A box-shaped body that faces left or right (a side-view vehicle, a fighter): new FacingFrame2D(body, facing, halfWidth, halfHeight), with facing +1 for a nose on local +X and -1 for local -X. A view over the body, cheap to make whenever needed.
| Member | Answer |
|---|---|
Nose / Up | GetWorldVector((facing, 0)) / GetWorldVector((0, 1)) |
NoseTip | Position + Nose * halfWidth |
IsInFront(point) | GetLocalPoint(point).X * facing > 0: did it come in through the front? |
NoseAlignment(direction) | Nose · direction: how well the nose lines up with a direction |
ZoneAt(point, axisBias) / ZoneToward(direction, axisBias) | Which side (BoxSide2D.Front, Back, Top, Bottom, in facing terms) a contact point touched / a direction faces (Geometry2D.ClassifyBoxSide) |
UndersideEnds() | (Back, Front): the bottom corners, e.g. to cast rays down from both ends |
AimRotationToward(direction) | The body rotation that points the nose along direction (Rotation2D.AngleAligningForward) |
var frame = new FacingFrame2D(car, facing, halfW, halfH);
var zone = frame.ZoneAt(contactPoint, axisBias: 1.35f); // map to your own names: Front = nose, Bottom = wheels
if (zone == BoxSide2D.Bottom) RefillAirJump();
var (back, front) = frame.UndersideEnds();
var target = frame.AimRotationToward(stick); // then car.AimAt(target, ...)
Client Twin
The TypeScript package has the same verbs as functions taking the body first (jumpOff(body, normal, speed)), working directly on planck.js bodies, so a client-side prediction of the server simulation calls the same verbs.