Start here
Your first walker – step by step
A complete, small walker you can build in an evening: a vehicle that walks at a set speed, turns, and has two legs that swing with its speed. Everything else on this site builds on this skeleton.
Step 1 – Start from a vehicle
A walker in Arma Reforger is a vehicle – that gives you seats, damage, replication and physics for free.
- In the Resource Browser find a simple wheeled vehicle prefab (for example a car or truck).
- Right click → Inherit in addon (creates your own prefab that inherits everything). Name it
MyWalker.et. - Open it. For now keep the car mesh – you will replace it with your walker body later. Seats keep working.
MyWalker.et in a world and drive it like the original car.Step 2 – Take over the driving
This component reads the driver's normal car input (throttle, brake, steering) and writes the velocity itself. The wheels still exist but no longer decide how fast you go.
[ComponentEditorProps(category: "MyWalker", description: "Simple walker drive: sets the velocity directly")]
class MyWalker_SimpleDriveComponentClass : ScriptComponentClass
{
}
class MyWalker_SimpleDriveComponent : ScriptComponent
{
[Attribute("15", desc: "Top speed forward (km/h)")]
protected float m_fMaxSpeedKmh;
[Attribute("5", desc: "Top speed backwards (km/h)")]
protected float m_fMaxReverseKmh;
[Attribute("2", desc: "Acceleration (m/s per second)")]
protected float m_fAccel;
[Attribute("35", desc: "Turn rate at full steering (degrees per second)")]
protected float m_fTurnRate;
protected float m_fSpeed; // current walking speed (m/s, + = forward)
protected RplComponent m_Rpl;
protected VehicleWheeledSimulation m_Sim;
override void OnPostInit(IEntity owner)
{
super.OnPostInit(owner);
m_Rpl = RplComponent.Cast(owner.FindComponent(RplComponent));
m_Sim = VehicleWheeledSimulation.Cast(owner.FindComponent(VehicleWheeledSimulation));
SetEventMask(owner, EntityEvent.FRAME);
}
override void EOnFrame(IEntity owner, float timeSlice)
{
// Only the machine that simulates this vehicle may move it.
if (m_Rpl && !m_Rpl.IsOwner())
return;
Physics phys = owner.GetPhysics();
if (!phys || !phys.IsDynamic() || !m_Sim)
return;
// --- 1. what does the driver want?
float throttle = m_Sim.GetThrottle(); // 0..1 (W)
float steering = m_Sim.GetSteering(); // -1..1 (A/D)
bool reverse = IsReverseGear();
float target = throttle * m_fMaxSpeedKmh / 3.6; // km/h -> m/s
if (reverse)
target = -throttle * m_fMaxReverseKmh / 3.6;
if (!m_Sim.EngineIsOn())
target = 0;
// --- 2. move the current speed toward the target, but not faster than the acceleration allows
float step = m_fAccel * timeSlice;
m_fSpeed = m_fSpeed + Math.Clamp(target - m_fSpeed, -step, step);
// --- 3. write the velocity: forward speed along the vehicle's nose, keep the vertical part (gravity)
vector forward = owner.GetTransformAxis(2);
forward[1] = 0;
forward.Normalize();
vector v = phys.GetVelocity();
vector newVel = forward * m_fSpeed;
newVel[1] = v[1];
phys.SetVelocity(newVel);
// --- 4. turning: set the rotation speed around the up axis
vector w = phys.GetAngularVelocity();
w[1] = steering * m_fTurnRate * Math.DEG2RAD;
phys.SetAngularVelocity(w);
}
//! The gearbox numbers gears from reverse up: everything below neutral is reverse.
protected bool IsReverseGear()
{
int reverseGears = m_Sim.GearboxGearsCount() - m_Sim.GearboxForwardGearsCount() - 1;
return m_Sim.GetGear() < reverseGears;
}
}
Compile, add MyWalker_SimpleDriveComponent to MyWalker.et, place it, get in, start the engine and
drive.
EOnFrame runs at all (add a Print). Forgetting SetEventMask(owner, EntityEvent.FRAME) is the most common reason.Step 3 – Add two legs
- In the prefab, add two child entities (right click the vehicle in the hierarchy → add child), name them
LegLandLegR. - Give each a
MeshObjectcomponent with a simple mesh (a long box is fine for now). - Note the position where each leg should hang from the body (the hip). You will type it into the component.
Step 4 – Make the legs swing
This component runs on every machine, because it is only for looks. It measures how fast the vehicle moves (from the position change, which works on every machine) and swings the legs forward and back in opposite phase.
[ComponentEditorProps(category: "MyWalker", description: "Swings two child legs with the walking speed")]
class MyWalker_SimpleLegsComponentClass : ScriptComponentClass
{
}
class MyWalker_SimpleLegsComponent : ScriptComponent
{
[Attribute("LegL", desc: "Name of the left leg child entity")]
protected string m_sLeftName;
[Attribute("LegR", desc: "Name of the right leg child entity")]
protected string m_sRightName;
[Attribute("-1.2 2.5 0", desc: "Left hip position relative to the vehicle (m)")]
protected vector m_vLeftHip;
[Attribute("1.2 2.5 0", desc: "Right hip position relative to the vehicle (m)")]
protected vector m_vRightHip;
[Attribute("2.0", desc: "Distance covered per full step cycle (m)")]
protected float m_fStride;
[Attribute("30", desc: "Maximum leg swing (degrees)")]
protected float m_fSwingDeg;
protected IEntity m_LegL;
protected IEntity m_LegR;
protected vector m_vLastPos;
protected float m_fPhase; // 0..1, one full step cycle
protected float m_fSpeed; // smoothed, m/s
override void OnPostInit(IEntity owner)
{
super.OnPostInit(owner);
m_vLastPos = owner.GetOrigin();
SetEventMask(owner, EntityEvent.FRAME);
}
//! Children are attached a moment after creation: look them up lazily.
protected void FindLegs(IEntity owner)
{
IEntity child = owner.GetChildren();
while (child)
{
if (child.GetName() == m_sLeftName)
m_LegL = child;
else if (child.GetName() == m_sRightName)
m_LegR = child;
child = child.GetSibling();
}
}
override void EOnFrame(IEntity owner, float timeSlice)
{
if (timeSlice <= 0)
return;
if (!m_LegL || !m_LegR)
FindLegs(owner);
if (!m_LegL || !m_LegR)
return;
// --- speed along the vehicle's nose, measured from the movement (works on every machine)
vector pos = owner.GetOrigin();
vector delta = pos - m_vLastPos;
m_vLastPos = pos;
float measured = vector.Dot(delta, owner.GetTransformAxis(2)) / timeSlice;
m_fSpeed = Math.Lerp(m_fSpeed, measured, Math.Clamp(timeSlice * 5, 0, 1)); // smooth it
// --- advance the step cycle: faster walking = faster steps
m_fPhase = m_fPhase + (m_fSpeed / m_fStride) * timeSlice;
m_fPhase = m_fPhase - Math.Floor(m_fPhase); // keep it 0..1
// --- swing amount grows with speed, standing still = legs straight
float amount = Math.Clamp(Math.AbsFloat(m_fSpeed) / 2.0, 0, 1);
float swing = Math.Sin(m_fPhase * Math.PI2) * m_fSwingDeg * amount;
PoseLeg(m_LegL, m_vLeftHip, swing);
PoseLeg(m_LegR, m_vRightHip, -swing); // opposite phase
}
//! Rotate a leg around its hip (pitch = forward/back).
protected void PoseLeg(IEntity leg, vector hip, float pitchDeg)
{
vector mat[4];
Math3D.AnglesToMatrix(Vector(0, pitchDeg, 0), mat); // yaw, pitch, roll in degrees
mat[3] = hip;
leg.SetLocalTransform(mat);
leg.Update();
}
}
Step 5 – Hide the car
Replace the vehicle's mesh with your body mesh, or hide it, and make the wheels invisible (remove their meshes in the prefab). The wheels keep working as invisible ground contact – that is exactly how M.A.W.S. works.
Step 6 – Test in multiplayer
- Start a dedicated server and a client with your addon.
- Drive: the drive component runs only where
IsOwner()is true, so the walker must still move. - Watch another player's walker: its legs must swing too – the leg component measures the movement locally.
EOnFrame is not ticking there. That is an engine behaviour for vehicles you do not own. The fix is a fallback tick, see Architecture → The fallback tick.Where to go next
| You want | Read |
|---|---|
| smooth turning toward the camera, no tipping over | Leg drive |
| feet that stay on the ground, knees that bend | Procedural leg rig |
| a crew that can aim while walking | Torso & aim stabilization |
| weapons and parts that can be swapped | Loadout & modules |