Table of Contents

This lesson covers how to cast rays using Physics and interpret the results.

Learning Objectives

  • Understanding rays, lines, and line segments
  • Learning how to cast various geometric primitives
  • Learning how to interpret and use the results of a cast

Level Setup

  • Command : New Project

  • Create a new project using the {nav icon=clone, name=Empty 2D Project} template

  • Command : CreateSprite

  • In the Properties Window

  • Rename Sprite object to Square

  • Under Transform

  • Set Translation to [-4, 0, 0]

  • Create three sprite objects with the following properties:

Name Transform>Translation Sprite>VertexColor Sprite>SpriteSource
RedCircle object [-1.5, 0, 0] [R:255, G:0, B:0, A:1.00] Circle
GreenCircle object [0, 0, 0] [R:0, G:255, B:0, A:1.00] Circle
BlueCircle object [1.5, 0, 0] [R:0, G:0, B:255, A:1.00] Circle

RaycastSetup

The property values should look like this

  • Create a NadaScript resource using the Component template template and name it MoveUpAndDown
  • In the MoveUpAndDown script
  • Update the MoveUpAndDown class to the following:
class MoveUpAndDown : NadaComponent
{
  [Dependency] var Transform : Transform;
  
  [Property] 
  var StartPosition : Real3 = Real3(0,1,0);
  
  [Property] 
  var EndPosition : Real3 = Real3(0,-1,0);
  
  [Property]
  var MoveDuration : Real = 1.0;
  
  function Initialize(init : CogInitializer)
  {
    this.Transform.Translation = this.StartPosition;
    this.MoveToEnd();
  }
  
  function MoveToEnd()
  {
    var seq = Actions.Sequence(this.Owner.Actions);
    Actions.Property(seq, @this.Transform.Translation, this.EndPosition, this.MoveDuration, Ease.Linear);
    Actions.Call(seq, this.MoveToStart);
  }
  
  function MoveToStart()
  {
    var seq = Actions.Sequence(this.Owner.Actions);
    Actions.Property(seq, @this.Transform.Translation, this.StartPosition, this.MoveDuration, Ease.Linear);
    Actions.Call(seq, this.MoveToEnd);
  }
}
Name StartPosition EndPosition MoveDuration
RedCircle object [-1.5, 1, 0] [-1.5, -1, 0] 1
GreenCricle object [0, 1, 0] [0, -1, 0] 2
BlueCircle object [1.5, 1, 0] [1.5, -1, 0] 3

RaycastSetup

The circles should move like this

Rays

A ray is a portion of a line that starts at a given location and extends infinitely far in a given direction. In Zilch Engine, you can create rays and cast them in order to find what it intersects with. This can be used for a variety of applications, including:

  • Simulating laser beams
  • Simulating high velocity projectiles
  • Checking for line of sight
  • Detecting walls
  • Detecting distance to an object

NOTE: It's worth noting the difference between rays, segments and lines. Rays are infinitely long, defined by a start point and a direction. Segments are defined by a start and end point. Lines are defined by two points and extend infinitely. Ray Segment Line

Creating and Casting

The first step to raycasting is to build the ray with the desired parameters. Let's take a look.

  • Command : Add Resource
  • Create a NadaScript resource using the Component template template and name it RayCasterLogic
  • In the RayCasterLogic script
  • Update the RayCasterLogic class to the following:
class RayCasterLogic : NadaComponent
{
  [Dependency] var Transform : Transform;
  
  [Property]
  var Direction : Real3 = Real3(1.0, 0.0, 0.0);
  
  [Property]
  var DebugDrawOn : Boolean = true;
  
  [Property]
  var MaxDebugLineLength : Real = 8.0;
  
  function Initialize(init : CogInitializer)
  {
    Zilch.Connect(this.Space, Events.LogicUpdate, this.OnLogicUpdate);
  }

  function OnLogicUpdate(event : UpdateEvent)
  {
    this.FindFirstObject();
  }
  
  function FindFirstObject()
  {
    //Create ray
    var ray = Ray();
    ray.Start = this.Transform.Translation;
    ray.Direction = this.Direction;
    
    //Cast ray
    var result = this.Space.PhysicsSpace.CastRayFirst(ray);
    
    //Interpret results
    if(result.ObjectHit != null)
    {
      //Set our owner's color to match the color of the object we hit
      this.Owner.Sprite.VertexColor = result.ObjectHit.Sprite.VertexColor;
      Console.WriteLine("Ray found: `result.ObjectHit.Name`");
    }
    else
    {
      //If no objects were hit, set our owner's color to white
      this.Owner.Sprite.VertexColor = Colors.White;
      Console.WriteLine("No object found!");
    }
    
    //Visual Debug
    if(this.DebugDrawOn)
    {
      var debugRay = DebugLine();
      debugRay.Start = ray.Start;
      debugRay.End = ray.Start + Real3.XAxis * Math.Min(result.Distance, this.MaxDebugLineLength);
      debugRay.HeadSize = 0.2;
      DebugDraw.Add(debugRay);
    }   
  }
}

RaycastFirst

We can see the ray starting at the Square object and extending to the right. Upon intersecting with one of the circles, the color of the square is changed to match it.

Raycasting always consists of 3 steps:

  1. Create the Ray
  2. Request the PhysicsSpace to cast it (storing the results)
  3. Interpret the CastResult

If the ray intersects an object, you can retrieve the following information from the CastResult:

CastResult
Collider The collider intersected by the ray
Distance Distance from the ray start to the point of intersection
ObjectHit The cog hit by the cast
Normal The normal of the surface at the intersection point
WorldPosition World-space position where the object was hit

Multiple Results

It is also possible to cast a Ray and retrieve the resulting intersections with multiple objects by using the PhysicsSpace.CastRay() function instead.

  • Add the following function to the RayCasterLogic class:
  function FindSeveralObjects()
  {
    //Create Ray
    var ray = Ray();
    ray.Start = this.Transform.Translation;
    ray.Direction = this.Direction;
    
    //Cast Ray
    var results = this.Space.PhysicsSpace.CastRay(ray, 3);
    
    this.Owner.Sprite.VertexColor = Real4(0,0,0,1);
    
    //Interpret Results
    foreach(var result in results)
    {
      //If the object has a sprite component
      if(result.ObjectHit.Sprite != null)
      {
        //Add that object's color to our owner's color
        this.Owner.Sprite.VertexColor += result.ObjectHit.Sprite.VertexColor;
      }
    }
    
    //Visual Debug
    if(this.DebugDrawOn)
    {
      var debugRay = DebugLine();
      debugRay.Start = ray.Start;
      debugRay.End = ray.Start + Real3.XAxis * this.MaxDebugLineLength;
      debugRay.HeadSize = 0.2;
      DebugDraw.Add(debugRay);
    }
  }
  • Replace the OnLogicUpdate function in the RayCasterLogic class with the following:
  function OnLogicUpdate(event : UpdateEvent)
  {
      //this.FindFirstObject();
      this.FindSeveralObjects();
  }

RaycastMultiple

The CastRay function allows you to specify the ray and a maximum number of objects to detect. It returns a range of CastResults that contains all of the objects intersected (up to the specified number) in order of distance (closest to farthest).

Cast Filters

CastFilters allow you to customize which objects the raycast process should hit and which should be ignored. The filter can then be passed as an optional third parameter to the CastRay function.

  • Replace the FindSeveralObjects function in the RayCasterLogic class with the following :
  function FindSeveralObjects()
  {
    //Create Filter
    var filter = CastFilter();
    filter.IgnoreGhost = true;
    
    //Create Ray
    var ray = Ray();
    ray.Start = this.Transform.Translation;
    ray.Direction = this.Direction;
    
    //Cast Ray
    var results = this.Space.PhysicsSpace.CastRay(ray, 3, filter);
    
    this.Owner.Sprite.VertexColor = Real4(0,0,0,1);
    
    //Interpret Results
    foreach(var result in results)
    {
      //If the object has a sprite component
      if(result.ObjectHit.Sprite != null)
      {
        //Add that object's color to our owner's color
        this.Owner.Sprite.VertexColor += result.ObjectHit.Sprite.VertexColor;
      }
    }
    
    //Visual Debug
    if(this.DebugDrawOn)
    {
      var debugRay = DebugLine();
      debugRay.Start = ray.Start;
      debugRay.End = ray.Start + Real3.XAxis * this.MaxDebugLineLength;
      debugRay.HeadSize = 0.2;
      DebugDraw.Add(debugRay);
    }
  }

RaycastMultipleFiltered

Here are some useful properties you can set on the CastFilter that allow you to further customize which objects to ignore.

Cast Filter
IgnoreCog Cog A given specific Cog to ignore
IgnoreDynamic boolean Ignores all objects marked as Dynamic
IgnoreGhost boolean Ignores all objects marked as Ghost
IgnoreKinematic boolean Ignores all objects marked as Kinematic
IgnoreStatic boolean Ignores all objects marked as Static
CollisionGroup collisiongroup Makes the Ray behave as part of a collision group, using the current collisiontable

Other Types of Casting

In addition to Rays, Zilch Engine allows you to cast other types of shapes, including:

They all follow the same basic steps described in this tutorial: define the shape, request PhysicsSpace to cast it (with the optional inclusion of a CastFilter) and then interpret the results. You can read more about them in the PhysicsCasting Manual Page.

Related Materials

Manual

Tutorial

Code Reference

Classes

Enums

Commands

Development Task

  • T1188