Physics Queries

Summary

Shows how to use the different kinds of physics queries: raycasts, overlaps, and sweeps.

Query Parameters

Each type of physics query uses a physics query parameters object that holds all the parameters needed to make the query. Different types of queries uses different query parameter clases.

Raycasts

Raycast queries test which colliders are hit by a ray moving from a starting point to an end point, or a starting point along a direction vector for a specified distance. Raycasts use the ksRaycastParams object. The following example shows how to use raycast queries in a server script to check if anything was hit, find the nearest hit, and find all hits. This can also be used in a client script if you change all Physics references to Room.Physics (in client scripts, Physics refers to Unity's Physics class).

Raycast Script

    public void Raycast()
    {
        // Raycast upwards from the origin for a distance of 5.
        ksRaycastParams raycastParams = new ksRaycastParams(ksVector3.Zero, ksVector3.Up, 5f);

        // Check if anything was hit. Use this when you don't care what was hit.
        if (Physics.RaycastAny(raycastParams))
        {
            // We hit something!
            // Add logic here...
        }

        // Raycast from origin to end point.
        raycastParams.Origin = new ksVector3(1f, 0f, 0f);
        // Always set End after setting Origin. Setting Origin second will move the End point.
        raycastParams.End = new ksVector3(0f, 1f, 0f);

        // Find the nearest hit.
        ksRaycastResult nearestHit;
        if (Physics.RaycastNearest(raycastParams, out nearestHit))
        {
            // We hit something!
            ksLog.Info(this, "Raycast nearest hit point: " + nearestHit.Point + ", distance: " + nearestHit.Distance +
                ", normal: " + nearestHit.Normal);
            // Add logic here...
        }

        // Find all hits.
        foreach (ksRaycastResult hit in Physics.Raycast(raycastParams))
        {
            ksLog.Info(this, "Raycast hit point: " + hit.Point + ", distance: " + hit.Distance +
                ", normal: " + hit.Normal);
            // Add logic here...
        }
    }

The results of queries that return multiple hits are not sorted. The following example shows how to get the results of a Raycast sorted from nearest to farthest:

Raycast Script

    public void RaycastSorted()
    {
        // Raycast from (-1, 5, 0) to (0, 2, -2)
        ksRaycastParams raycastParams = new ksRaycastParams(new ksVector3(-1f, 5f, 0f), new ksVector3(0f, 2f, -2f));
        ksQueryHitResults<ksRaycastResult> hits = Physics.Raycast(raycastParams);
        hits.SortByDistance(); // Sort from nearest to farthest.
        foreach (ksRaycastResult hit in hits)
        {
            ksLog.Info(this, "Raycast hit point: " + hit.Point + ", distance: " + hit.Distance +
                ", normal: " + hit.Normal);
            // Add logic here...
        }
    }

Query Objects

Overlap and sweep queries use a query object. The query object can be one of three types:

Overlaps

Overlap queries test which colliders in a scene are overlapped by the query object geometry. Overlap queries use the ksOverlapParams object. It has two bools UseEntityPosition and UseEntityRotation that can be used to make the query use the entity's position and rotation when the query object is an entity or collider, instead of supplying an origin and rotation. These bools are set to true by the constructors that take an entity or collider without also taking origin or rotation parameters. The following example shows how to use overlap queries in a server script to check if anything is overlapping a sphere, and to find all colliders overlapping a box. Like the raycast example, this can also be used in a client script if you change the Physics references to Room.Physics.

Overlap Script

     public void Overlap()
    {
        // Overlap a sphere of radius 1, 10 units above the origin.
        ksOverlapParams overlapParams = new ksOverlapParams(new ksSphere(1f), new ksVector3(0f, 10f, 0f), 
            ksQuaternion.Identity);

        // Check if anything overlaps the sphere. Use this when you don't care what was overlapping.
        if (Physics.OverlapAny(overlapParams))
        {
            // There was an overlap!
            // Add logic here..
        }

        // Change the sphere to a 2x4x2 box centered on the origin.
        overlapParams.Shape = new ksBox(2f, 4f, 2f);
        overlapParams.Origin = ksVector3.Zero;

        // Find all overlaps.
        foreach (ksOverlapResult overlap in Physics.Overlap(overlapParams))
        {
            ksLog.Info(this, "Overlap with entity " + overlap.Entity.Id);
            // Add logic here...
        }
    }

The following example shows how to use ksUnityCollider to wrap a Unity collider in the ksICollider interface to use it in an overlap query in a client script:

Client Entity Script

using UnityEngine;
using KS.Reactor.Client.Unity;
using KS.Reactor;

public class ceColliderOverlap : ksEntityScript
{
    // Called after properties are initialized.
    public override void Initialize()
    {
        // Get Unity's capsule collider.
        CapsuleCollider capsule = GetComponent<CapsuleCollider>();
        if (capsule == null)
        {
            return;
        }

        // Wrap the capsule in a ksUnityCollider that implements ksICollider so it can be used in Reactor queries.
        ksUnityCollider collider = new ksUnityCollider(capsule);

        // Construct overlap params using the collider. This also sets UseEntityPosition and UseEntityRotation to true
        // and sets the ExcludeEntity to the collider's entity so it won't be included in results.
        ksOverlapParams overlapParams = new ksOverlapParams(collider);

        // Find all colliders on entities overlapping the capsule collider.
        foreach (ksOverlapResult overlap in Room.Physics.Overlap(overlapParams))
        {
            // Add logic here...
        }
    }
}

Sweeps

Sweep queries test which colliders are hit when a query object moves from a starting point to an end point, or a starting point along a direction vector for a specified distance. Sweep queries use the ksSweepParams object. It extends ksOverlapParams. The following example shows how to use sweep queries in a server script to check if an entity moving down will hit anything, find the nearest hit, and find all hits. Like the raycast and overlap examples, this can also be used in a client script if you change the Physics references to Room.Physics. Also like raycasts, sweeps that return multiple results are not sorted, but can be sorted from nearest to farthest by calling ksQueryHitResults<ksSweepResult>.SortByDistance().

Server Entity Script

    public void Sweep()
    {
        // Sweep downwards from the entity's position a distance of .1. This constructor sets UseEntityPosition and
        // UseEntityRotation to true.
        ksSweepParams sweepParams = new ksSweepParams(Entity, ksVector3.Down, .1f);

        // Check if anything was hit. Use this when you don't care what was hit.
        if (Physics.SweepAny(sweepParams))
        {
            // We hit something!
            // Add logic here...
        }

        // Sweep from (0, 5, 0) instead of the entity's position. We have to set UseEntityPosition to false before we
        // can change the Origin.
        sweepParams.UseEntityPosition = false;
        sweepParams.Origin = new ksVector3(0f, 5f, 0f);

        // Find the nearest hit.
        ksSweepResult nearestHit;
        if (Physics.SweepNearest(sweepParams, out nearestHit))
        {
            // We hit something!
            ksLog.Info(this, "Sweep nearest hit point: " + nearestHit.Point + ", distance: " + nearestHit.Distance +
                ", normal: " + nearestHit.Normal);
            // Add logic here...
        }

        // Find all hits.
        foreach (ksSweepResult hit in Physics.Sweep(sweepParams))
        {
            ksLog.Info(this, "Sweep hit point: " + hit.Point + ", distance: " + hit.Distance +
                ", normal: " + hit.Normal);
            // Add logic here...
        }
    }

Query Filters and Blocking vs Touching Hits

Query filters implement the ksIQueryFilter interface and are used to filter which colliders are included in the results. They also decide if a hit is blocking or touching. Sweeps and raycasts will return the closest blocking hit to the query origin and all touching hits between the origin and blocking hit. A use case for this would be a bullet that travels through a breaks glass but gets stopped when it hits a wall. Glass hits would be touching and wall hits would be blocking. By default if no query filter is provided, all hits are touching. Overlap queries that return multiple results will return all blocking and touching hits.

There are three types of query filters available you can use. You can also create your own filters.

The following example shows how to use a ksCollisionFilter with a raycast to shoot and distinguish between blocking and touching hits. The shot will destroy the touching-hit entities it passes through and call an RPC with the position of the blocking hit, which could be used on the client to render something where the shot hit. For this example script you will need to create a collision filter asset and assign it to the script in the inspector.

Server Entity Script

using KS.Reactor.Server;
using KS.Reactor;

public class seRaycastShoot : ksServerEntityScript
{
    // This is the collision filter used to determine which raycast hits are blocking and which are touching. Assign
    // this in the inspector. If left unassigned, all hits will be touching.
    [ksEditable]
    public ksCollisionFilter Filter;

    // How far the raycast travels.
    [ksEditable]
    public float ShootDistance = 100f;

    private ksRaycastParams m_raycastParams;

    // Called when the script is attached.
    public override void Initialize()
    {
        m_raycastParams = new ksRaycastParams();
        m_raycastParams.Filter = Filter;
        m_raycastParams.Distance = ShootDistance;
        m_raycastParams.ExcludeEntity = Entity; // Exclude this entity from being hit.
    }

    public void Shoot()
    {
        // Shoot from the entity position, in the direction the entity is facing.
        m_raycastParams.Origin = Transform.Position;
        m_raycastParams.Direction = Transform.Forward();
        ksQueryHitResults<ksRaycastResult> results = Physics.Raycast(m_raycastParams);

        // Destroy the entities from touching hits.
        foreach (ksRaycastResult hit in results.Touches)
        {
            hit.Entity.Destroy();
        }

        // Process the blocking hit if there is one.
        if (results.HasBlock)
        {
            // Call RPC 0 at the block position.
            ksRaycastResult block = results.Block;
            Room.CallRPC(0, block.Point);
        }
    }
}

The following example shows how to create your own query filter. This filter excludes entities that have property 0 set to true, and treats hits a touching if either the query collider or the hit collider is a trigger, otherwise hits will block. The query collider is the collider used to make the query. If the query object is a collider, it's that collider. If the query object is an entity, it's whichever of the entity's colliders was used in the query. For shape queries and raycasts, it is null. The hit collider is the collider being tested for a hit.

Query Filter Script

using KS.Reactor;

public class SampleQueryFilter : ksIQueryFilter
{
    public ksQueryHitTypes Filter(
        ksICollider queryCollider,
        ksICollider hitCollider,
        ksQueryResultTypes resultType,
        ksBaseQueryParams args)
    {
        // Ignore entities that have Property 0 set to true.
        if (hitCollider.Entity.Properties[0].Bool)
        {
            return ksQueryHitTypes.NONE;
        }
        // If the hit collider or the query collider is a trigger, it's a touching hit, otherwise it's a block. We have
        // to null check the query collider since it will be null for shape queries and raycasts.
        return hitCollider.IsTrigger || (queryCollider != null && queryCollider.IsTrigger) ?
            ksQueryHitTypes.TOUCH : ksQueryHitTypes.BLOCK;
    }
}

Query Flags

Query flags can also be used to affect what results are returned. The following flags are available:

Entity Query Collider Filters

Entity query collider filters implement the ksIEntityQueryColliderFilter interface and are used in entity queries to determine which of the entity's enabled colliders are used in the query. They are not used if the query object is a collider or a shape. ksSimulationFilter is an entity query collider filter that excludes non-simulation colliders. It also excludes triggers when ksQueryFlags.EXCLUDE_TOUCHES is set.

The following example shows how to use an entity sweep to detect if an entity is standing on walkable ground. If something is detected below the entity, a slop calculation is done to determine if the ground can be walked on. This code could also be used in a player controller.

Server Entity Script

using KS.Reactor.Server;
using KS.Reactor;

public class seGroundDetection : ksServerEntityScript
{
    [ksEditable]
    public float MaxWalkSlope = 1f;

    private ksSweepParams m_sweepParams;

    // Called when the script is attached.
    public override void Initialize()
    {
        // Create a sweep parameters object preconfigured to only check for objects the entity can collide with.
        m_sweepParams = Physics.CreateSimulationQueryParams(Entity);
        // Use the entity's rotation.
        m_sweepParams.UseEntityRotation = true;
        // Sweep down a short distance to detect ground.
        m_sweepParams.Direction = ksVector3.Down;
        m_sweepParams.Distance = .1f;
    }

    public bool DetectGround()
    {
        // Start the sweep slightly above the entity, because sweeps can miss detecting objects that start touching.
        m_sweepParams.Origin = Transform.Position + ksVector3.Up * m_sweepParams.Distance / 2f;
        // Get the nearest hit a short distance below the entity.
        ksSweepResult hit;
        if (!Physics.SweepNearest(m_sweepParams, out hit))
        {
            return false;
        }
        // Calculate slope from hit normal
        float horizontal = hit.Normal.XZ.Magnitude();
        if (horizontal == 0)
        {
            // If horizontal distance is 0, we're standing on flat ground.
            return true;
        }
        float slope = hit.Normal.Y / horizontal;
        return slope <= MaxWalkSlope;
    }
}

Physics.CreateSimulationQueryParams is a convenience function to create parameters configured to only return results an entity can collide with. You can pass false as a second optional argument to include colliders the entity would generate overlap events for in results. It is equivalent to doing the following:

        ksSweepParams sweepParams = new ksSweepParams();
        sweepParams.Entity = Entity;
        sweepParams.ExcludeEntity = Entity;
        // Use a simulation filter as both the query filter and the query entity collider filter.
        ksSimulationFilter simulationFilter = new ksSimulationFilter();
        sweepParams.Filter = simulationFilter;
        sweepParams.EntityColliderFilter = simulationFilter;
        // Exclude touches so we don't get hits with colliders the entity would receive overlap events from.
        sweepParams.Flags |= ksQueryFlags.EXCLUDE_TOUCHES;

The following example shows how to create your own entity query collider filter to exclude the entity's triggers.

Entity Query Collider Filter Script

using KS.Reactor;

public class SampleEntityQueryColliderFilter : ksIEntityQueryColliderFilter
{
    public bool Filter(ksICollider queryCollider, ksOverlapParams args)
    {
        // Exclude the collider if it is a trigger.
        return !queryCollider.IsTrigger;
    }
}