KROYSDOCS
HomeWhat's newv0.1.0
MANUAL / Scripting / Physics and characters

Physics and characters

Rigid bodies, colliders, triggers and character controllers simulate in fixed steps. Scripts query the physics world, react to contacts and steer characters.

ComponentsRigid Body, Collider, Character Controller
ScriptingPhysics, RigidBody, CharacterController
01

Bodies and colliders

A Collider gives an entity a shape (box, sphere, capsule, mesh); a Rigid Body makes it move. Body types: Static never moves, Kinematic moves only where it is put, Dynamic is simulated with mass, gravity and damping. Mark a collider Trigger to detect overlaps without collisions.

Scripts read and change bodies with the component class: velocities, mass, gravity scale, sleep, and axis locks.

JumpPad.cs
public sealed class JumpPad : EntityScript
{
    public float LaunchSpeed = 12.0f;

    protected override void OnTriggerEnter(Entity other)
    {
        // Dynamic bodies get launched; static and kinematic ones are left alone.
        if (!RigidBody.Has(other) || RigidBody.GetBodyType(other) != BodyType.Dynamic) return;
        Vector3 velocity = RigidBody.GetLinearVelocity(other);
        velocity.Y = LaunchSpeed;
        RigidBody.SetLinearVelocity(other, velocity);
        RigidBody.SetAwake(other, true);
    }

    protected override void OnCollisionEnter(Collision collision)
    {
        // Contacts arrive after the fixed step in which the bodies touched.
        Debug.Log($"{collision.Other.Name} hit the pad at {collision.Point}");
    }
}
02

Contacts and triggers

Contact callbacks arrive after the fixed step in which they happened: OnCollisionEnter, OnCollisionStay and OnCollisionExit with the other entity, the contact point, normal and depth; OnTriggerEnter and OnTriggerExit with the other entity.

Hazard.cs
public sealed class Hazard : EntityScript
{
    public float DamagePerSecond = 20.0f;
    private readonly HashSet<Entity> inside = new();

    protected override void OnTriggerEnter(Entity other) => inside.Add(other);
    protected override void OnTriggerExit(Entity other) => inside.Remove(other);

    protected override void OnFixedUpdate(float fixedDeltaTime)
    {
        foreach (Entity victim in inside)
        {
            if (victim.IsAlive && Health.Has(victim))
                Health.ApplyDamage(victim, DamagePerSecond * fixedDeltaTime);
        }
    }

    protected override void OnCollisionEnter(Collision collision)
    {
        Debug.Log($"{collision.Other.Name} bumped into the hazard ({collision.PenetrationDepth:0.000} m deep)");
    }
}
03

Queries

Physics.Raycast, SphereCast, CapsuleCast, BoxCast and the Overlap queries ask the physics world of the running scene. Filter with gameplay layers (Physics.LayerMask) and collision layers; triggers are ignored unless a query asks for them. For many rays at once, RaycastBatch with a reused RaycastBatchBuffer allocates nothing.

LaserPointer.cs
public sealed class LaserPointer : EntityScript
{
    public float Range = 50.0f;

    protected override void OnFixedUpdate(float fixedDeltaTime)
    {
        if (!Input.IsActionHeld("Fire")) return;
        Transform transform = Transform;
        Vector3 origin = transform.WorldPosition + transform.WorldForward * 0.5f;

        // Only the gameplay layers that matter; triggers are ignored.
        uint mask = Physics.LayerMask(GameplayLayer.Default, GameplayLayer.Enemy);
        if (Physics.Raycast(origin, transform.WorldForward, Range, mask, out RaycastHit hit))
            Debug.Log($"hit {hit.Entity.Name} at {hit.Distance:0.0} m, normal {hit.Normal}");

        // Everything within 3 m of the hit point, for an area effect.
        foreach (Entity nearby in Physics.OverlapSphere(origin + transform.WorldForward * hit.Distance, 3.0f, mask))
            Debug.Log($"near: {nearby.Name}");
    }
}
04

Character controllers

A Character Controller is a capsule that walks, runs, jumps, climbs steps and slopes and rides moving ground, in fixed steps. Players drive it with the input map; scripts drive it with script motion: SetScriptMotion(entity, true) and a direction each step with SetScriptMove. Walk speed, run multiplier, jump speed, gravity, air control, coyote time and jump buffering are component settings, readable and writable from scripts.

The same character step runs on the dedicated server and in client prediction, with the settings of the level's character, so networked players move exactly like the single-player character.

PatrolGuard.cs
public sealed class PatrolGuard : EntityScript
{
    public Vector3 PointA = new(0.0f, 0.0f, 0.0f);
    public Vector3 PointB = new(10.0f, 0.0f, 0.0f);
    private bool toB = true;

    protected override void OnStart()
    {
        // Script motion: the character ignores player input and walks where the script says.
        CharacterController.SetScriptMotion(Entity, true);
        CharacterController.SetWalkSpeed(Entity, 2.5f);
    }

    protected override void OnFixedUpdate(float fixedDeltaTime)
    {
        Vector3 target = toB ? PointB : PointA;
        Vector3 toTarget = target - Transform.WorldPosition;
        toTarget.Y = 0.0f;
        if (toTarget.Length() < 0.3f)
        {
            toB = !toB;
            return;
        }
        CharacterController.SetScriptMove(Entity, Vector3.Normalize(toTarget));

        // Achieved velocity is what the move really did after collisions: zero against a wall.
        if (CharacterController.IsGrounded(Entity) && CharacterController.GetAchievedVelocity(Entity).Length() < 0.05f)
            CharacterController.SetJumpPending(Entity, true);   // stuck: try a hop
    }
}

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