KROYSDOCS
HomeWhat's newv0.1.0
MANUAL / Scripting / Fixed step and every frame

Fixed step and every frame

The simulation runs at a fixed rate; rendering runs as fast as it can. Decide and move in OnFixedUpdate on the exact pose; drive visuals in OnUpdate from the interpolated pose.

Fixed rate60 Hz by default
Exact poseTransform.WorldPosition
Rendered poseTransform.InterpolatedPosition
01

Two clocks

Character controllers, rigid bodies, navigation agents and OnFixedUpdate move entities only in fixed steps (60 per second by default). Rendering, OnUpdate and OnLateUpdate run once per frame, at any frame rate. At 240 frames per second a 60 Hz character moves on one frame in four.

You want toRun it inRead
Make gameplay decisions, move things, apply forces, read input for movementOnFixedUpdateTransform.WorldPosition and WorldRotation (exact), CharacterController.GetAchievedVelocity, IsGrounded
Drive visuals from motion: animation parameters, markers, camera smoothing, audio pitch from speedOnUpdate / OnLateUpdateTransform.InterpolatedPosition and InterpolatedRotation, Time.FixedAlpha
Teleport or place an entityeitherwrite Transform.WorldPosition (writes are always exact)
02

Why it matters

Transform.WorldPosition is the exact simulation pose. Read every frame, it repeats on the frames between fixed steps and then jumps: a speed measured per frame from it reads zero, zero, zero, then four times too fast. Transform.InterpolatedPosition is the pose the renderer draws this frame, between the last two fixed steps at Time.FixedAlpha. It lags the simulation by up to one step and moves smoothly at any frame rate.

CharacterPresentation.cs
public sealed class CharacterPresentation : EntityScript
{
    public string MarkerName = "Marker";   // a floating marker, a child of the level root
    private Entity marker;

    protected override void OnStart() => marker = Entity.FindChild(MarkerName, recursive: true);

    protected override void OnFixedUpdate(float fixedDeltaTime)
    {
        // Decisions and motion: the exact simulation pose.
        if (Transform.WorldPosition.Y < -50.0f)
        {
            Transform transform = Transform;
            transform.WorldPosition = new Vector3(0.0f, 2.0f, 0.0f);   // writes are always exact
        }
    }

    protected override void OnUpdate(float deltaTime)
    {
        // Visuals: the pose the renderer draws this frame, smooth at any frame rate.
        if (marker.IsAlive)
        {
            Transform markerTransform = marker.Transform;
            markerTransform.WorldPosition = Transform.InterpolatedPosition + Vector3.UnitY * 2.2f;
        }
        Vector3 velocity = CharacterController.GetAchievedVelocity(Entity);
        Animator.SetFloat(Entity, "Speed", new Vector2(velocity.X, velocity.Z).Length());
    }
}
03

Characters

CharacterController.GetVelocity is the motion that was asked for; GetAchievedVelocity is what the last fixed-step move achieved after collisions, slides and steps: zero against a wall. It stays constant between fixed steps, so it is smooth to read every frame and right for animation speed.

CharacterController.IsGrounded rides out ground-contact losses shorter than the Grounded Grace (0.1 s by default) that seams, curbs and bumps cause. HasGroundContact is the raw contact of the last step, for effects that must be exact.

04

Input in fixed steps

Input actions read in OnFixedUpdate report each press and release on exactly one fixed step, so a jump is never lost at a high frame rate and never doubled at a low one. In OnUpdate, they follow the rendered frame.

On the dedicated server there is no rendering: InterpolatedPosition equals WorldPosition and Time.FixedAlpha is 1.

KroysEngine 0.1.0 documentation, built from the engine repository by Tools/BuildDocsSite.py.