Combat
Torso & aim stabilization
The legs bounce, the torso must not. And the gunner's sight should stay on the target while the walker walks and turns – without ever fighting the mouse.
Torso: keep the slow part, drop the fast part
The torso hangs on the pelvis, but only on its posture (crouch, slope levelling, slow settling). Bob, sway, roll and lean are taken out. This runs in the same frame as the leg rig, right after the pelvis – a frame of lag would show as jitter.
void ApplyUpperBody(MyLegRig rig, MyPartRegistry reg, float dt)
{
vector posture[4];
rig.GetPelvisPostureLocal(posture); // crouch + slope, no bob/sway/roll
vector settle[4];
rig.GetPelvisSettleLocal(settle); // slow height adaption: follow it, or the waist opens a gap
vector torsoBase[4];
Math3D.MatrixMultiply4(posture, settle, torsoBase);
vector yaw[4];
Math3D.AnglesToMatrix(Vector(m_fTorsoYaw, 0, 0), yaw);
yaw[3] = m_vTorsoPivot - rig.GetPelvisRestOrigin();
vector torso[4];
Math3D.MatrixMultiply4(torsoBase, yaw, torso);
reg.SetTarget(PART_TORSO, torso);
reg.Write(PART_TORSO);
}
Fast arms, slow torso
A 20 t torso that snaps to the mouse looks wrong. The arms take small and fast corrections inside a ±35° window; the torso follows lazily (gain × remaining angle, max 80°/s, 3° dead band).
float FollowTorso(float aimYaw, float dt)
{
float err = MyMath.WrapDeg(aimYaw - m_fTorsoYaw);
float over = Math.Max(Math.AbsFloat(err) - m_fTorsoDeadband, 0);
float speed = Math.Min(m_fTorsoMaxSpeed, m_fTorsoGain * over);
m_fTorsoYaw += Math.Clamp(err, -speed * dt, speed * dt);
// never let the aim leave the arm window
m_fTorsoYaw = aimYaw - Math.Clamp(MyMath.WrapDeg(aimYaw - m_fTorsoYaw), -m_fArmWindow, m_fArmWindow);
return m_fTorsoYaw;
}
Convergence on the measured aim point
The two arm weapons sit 6 m apart. Instead of a fixed convergence distance, a ray from the gunner's eye along the sight measures the real distance (every 0.1 s, smoothed in log space so 20→40 m feels like 500→1000 m) and each barrel line is aimed exactly through that point.
The aim point lock
The engine has no turret stabilization API, and simply re-applying the old direction every frame overwrote the player's input – the turret could not be turned while walking. The working solution locks a world point:
- While the gunner gives aim input, do nothing and keep re-taking the point under the crosshair.
- When input stops, the last point stays locked.
- Every frame without input, compute the angles to that point from the current turret base and set them.
bool PlayerIsAiming(IEntity localChar)
{
ChimeraCharacter ch = ChimeraCharacter.Cast(localChar);
if (!ch || !ch.GetCharacterController())
return false;
CharacterInputContext ctx = ch.GetCharacterController().GetInputContext();
return ctx && ctx.GetAimChange().LengthSq() > 1e-10;
}
void HoldPoint(TurretComponent turret, TurretControllerComponent ctrl, vector baseMat[4], bool aiming)
{
vector dir = turret.GetAimingDirectionWorld().Normalized();
vector eye = EyePosition(dir);
if (aiming || !m_bHasPoint)
{
m_vPoint = eye + dir * TraceDistance(eye, dir); // re-take the point
m_bHasPoint = true;
return;
}
vector local = MyMath.DirToLocal(baseMat, m_vPoint - eye).Normalized();
vector m[4];
Math3D.DirectionAndUpMatrix(local, "0 1 0", m);
vector want = Math3D.MatrixToAngles(m); // degrees: yaw, pitch
vector cur = turret.GetAimingRotation(); // degrees
float dYaw = MyMath.WrapDeg(want[0] - cur[0]);
float dPitch = want[1] - cur[1];
if (Math.AbsFloat(dYaw) + Math.AbsFloat(dPitch) < 0.02)
return;
ctrl.SetAimingAngles((cur[0] + dYaw) * Math.DEG2RAD, (cur[1] + dPitch) * Math.DEG2RAD); // RADIANS
}
SetAimingAngles takes radians, GetAimingRotation returns degrees. Passing degrees drives both axes into their limits and freezes them there.acos(dot): near 1 it produces ~0.02° of noise per frame, which looks like constant motion. Use the length of the cross product (= sin of the angle).