Skip to content

Characters have physics ​

Every character a game spawns from a Gameable Studio export has a physics body: its parts as rigid bodies on the engine's physics, joined by joints with the ranges a real body has. A hit, a fall and a push are ONE command. Use them before you write a reaction of your own: a hurt clip, a nudge, a sink into the floor and a fade are what games did before the body was there, and they look like it.

What the body is ​

  • Eighteen parts on the skeleton (hips, belly, ribs, chest, neck, head; an upper arm, a forearm and a hand each side; a thigh, a shin and a foot each side), sized from the character's own joints and widths, with the masses of a body of its height. Rounded boxes for the trunk, capsules for the limbs and the head, boxes for the feet with their soles on the floor.
  • A swing-twist joint between each part and the one it hangs from, with a physiotherapist's ranges (a hip flexes 115 degrees forward and extends 35 back; elbows and knees are hinges), soft at the ends, with the viscosity and the little friction flesh has. A limp body sags and settles; it does not ring.
  • The parts live in the engine's physics world (physics() in the modules), so a body falls onto the place's collider, the floor and the walls, and never through them, and a crate dropped on a character bumps it. The parts are on the debris layer and collide with the default layer, static geometry, pickups and other debris; never with a walking capsule, which they stand inside of.
  • While the character stands, the parts are led by the animation (kinematic): nothing of the animation changes, and the parts are there for what bumps into them. A blow lets them go with the motion they had.

Nothing of this is in the package: the body is read off the skeleton when the character loads. A game that boots without the physics module gets characters without bodies, and hit says so once.

The one command ​

ts
import { character } from '@aosengine/sdk';

// The sword's tip, where it is, which way it swings.
character.hit(enemy, { at: tip, direction: swing, strength: 'small' }); // a flinch, back in a second
character.hit(enemy, { at: tip, direction: swing, strength: 'big' }); // down, and stays down
character.hit(enemy, { at: impact, direction: bullet, strength: 'shot' }); // sharp, with a flinch
character.hit(enemy, { at: palm, direction: shove, strength: 60 }); // a push of 60 newton-seconds
character.recover(enemy); // back into the animation, where it stood
  • at is where the blow lands, in the world; the nearest part takes it.
  • direction is which way the blow travels; omitted, it comes from the character's front. A punch is levelled a little so a blow from above does not drive the character into the floor.
  • strength:
    • 'small': a punch of 14 newton-seconds. The character stays on its feet and in what it was doing: the upper body takes the blow on the physics, the muscles round the hit give for a moment, the body sways from the ankles, then pulls itself back over about a second (measured on Hollow Reign's heroes: the chest moves 4 to 10 cm at most, back in 1.8 s).
    • 'big': a punch of 22 newton-seconds to the head, the neck, the body or the legs floors the character. It loses its tone the way a body does (the arms first, the knees folding, the trunk last), lands on the floor and lies there until recover. A big blow to an arm or a foot swings the limb hard and no more.
    • 'shot' / 'big-shot': a sharper push (30 / 70 newton-seconds) with a flinch, the body curling in for a third of a second; big-shot floors.
    • a number: a push of that many newton-seconds. 40 staggers; 90 and over floors.
  • recover blends the lying pose back into the animation over 0.8 seconds, where the character stood. To have it get up where it lies, move its entity there first (the fell event says where the hips are; jointOf(entity, 'root') on the host reads them any time).

A blow on a character that is down only moves the body where it lies. A blow during a reaction adds to it.

What comes back ​

Three anim-events on the clip ragdoll, as any clip's end arrives:

nameWhen
hitThe blow landed (on a standing, reacting or lying character).
fellA floored body came to rest on the floor (three seconds at the most).
recoveredThe character is back in its animation, after a reaction or a rise.
ts
for (const event of ctx.events) {
  if (event.tag !== 'anim-event' || event.val.clip !== 'ragdoll') continue;
  if (event.val.name === 'fell') scheduleRise(event.val.entity, 3);
}

time is the seconds since the hit.

What it costs ​

A standing character's body costs about 0.1 ms a frame (eighteen parts moved to their joints). While bodies move, the physics runs three collision steps a fixed step instead of one, with the joints' flesh before each. Four of Hollow Reign's heroes down at once: about 0.2 ms a frame for the bodies, the frame itself unchanged.

What a game should not do any more ​

  • Do not play a hurt clip on a hit. The body reacts to where it was hit and how hard; a clip plays the same reaction every time, whatever landed.
  • Do not nudge the position on a hit. The push is in the physics, and it carries into the joints in proportion to the masses: a light head snaps where a heavy chest only rocks.
  • Do not sink a dead character into the floor or fade it. Floor it with 'big', let it lie, and recover or despawn it when the game moves on.
  • Do not write a stagger from poses. A push of 40 newton-seconds staggers.

On the host ​

The bridge (@aosengine/wasm-host/characters) has the same three in TypeScript, for a page that drives characters itself: bridge.hit(entity, { at, direction, strength }), bridge.fall(entity) (down where it stands, no blow) and bridge.recover(entity). bridge.entryOf(entity).ragdoll is the body: its phase (stand, react, down, rise), the seconds in it, its plan (the parts, their sizes and masses) and what it cost last frame. createCharacterBridge({ ragdoll: false }) builds no bodies; ragdoll: { layer, mask } puts the parts on other layers.

Underneath, engine.get('physics').createRagdoll(spec) builds any ragdoll on the world (a creature's, a prop's) from a list of parts with their shapes, masses and joint ranges; ragdollPartOf(id) says which part a ray landed on.

See also ​