moveCharacter method
void
moveCharacter(
- int character,
- double dt,
- Vector3 velocity,
- CharacterState3D out, {
- required Vector3 stepDown,
- required Vector3 stepUp,
override
Moves character for one update at velocity: it slides along walls,
keeps to the floor (stepDown, pointing down; zero to turn off) and
climbs steps up to stepUp (pointing up; zero to turn off). Writes
where it ended up into out.
Implementation
@override
void moveCharacter(
int character,
double dt,
Vector3 velocity,
CharacterState3D out, {
required Vector3 stepDown,
required Vector3 stepUp,
}) {
final c = characters[character];
if (c == null) {
_clearState(out);
return;
}
c.moves++;
final r = c.settings.radius, h = c._halfHeight;
final p = c.position;
final start = Vector3.copy(p);
final wasGrounded = c.groundState == GroundState3D.onGround;
final solids = [
for (final b in bodies.values)
if (b._isSolid && _collide(b.layer, c.settings.layer)) b,
];
// The capsule as a box: x and z ± r, y from the feet up 2h.
bool overlaps(FakeBody3D b) =>
p.x + r > b.minX &&
p.x - r < b.maxX &&
p.z + r > b.minZ &&
p.z - r < b.maxZ &&
p.y > b.minY + 1e-6 &&
p.y - 2 * h < b.maxY - 1e-6;
final climb = stepUp.length;
void moveAxis(int axis, double d) {
if (d == 0) return;
final before = Vector3.copy(p);
if (axis == 0) {
p.x += d;
} else {
p.z += d;
}
for (final b in solids) {
if (!overlaps(b)) continue;
// A step low enough: up onto it.
final rise = p.y - b.minY;
if (rise > 0 && rise <= climb + 1e-6) {
final feet = p.y;
p.y = b.minY;
if (!solids.any(overlaps)) continue;
p.y = feet;
}
p.setFrom(before);
if (axis == 0) {
p.x = d > 0
? math.min(p.x + d, b.minX - r)
: math.max(p.x + d, b.maxX + r);
} else {
p.z = d > 0
? math.min(p.z + d, b.minZ - r)
: math.max(p.z + d, b.maxZ + r);
}
}
}
moveAxis(0, velocity.x * dt);
moveAxis(2, velocity.z * dt);
var vy = velocity.y;
p.y += vy * dt;
for (final b in solids) {
if (!overlaps(b)) continue;
// Out on the side its middle is on, so a platform rising into the
// feet lifts the character instead of swallowing it.
if (p.y - h <= (b.minY + b.maxY) / 2) {
p.y = b.minY; // On top (+Y is down).
if (vy > 0) vy = 0;
} else {
p.y = b.maxY + 2 * h; // Head on a ceiling.
if (vy < 0) vy = 0;
}
}
// Ground: something just under the feet; within stepDown when it was
// on the ground already, so it keeps to stairs going down.
final reach = wasGrounded ? math.max(stepDown.length, 0.02) : 0.02;
FakeBody3D? ground;
for (final b in solids) {
final below = b.minY - p.y;
if (below < -1e-6 || below > reach) continue;
if (p.x + r <= b.minX ||
p.x - r >= b.maxX ||
p.z + r <= b.minZ ||
p.z - r >= b.maxZ) {
continue;
}
if (ground == null || b.minY < ground.minY) ground = b;
}
if (ground != null && vy >= 0) {
p.y = ground.minY;
c.groundState = GroundState3D.onGround;
c.groundBody = ground.handle;
} else {
c.groundState = GroundState3D.inAir;
c.groundBody = 0;
}
c.velocity
..setFrom(p)
..sub(start)
..scale(dt > 0 ? 1 / dt : 0);
if (c.groundState == GroundState3D.onGround) c.velocity.y = 0;
if (c.innerBody != 0) {
bodies[c.innerBody]?.position.setValues(p.x, p.y - h, p.z);
}
characterState(character, out);
}