A toolbox of generic sensor components that are like eyeballs for GameObjects. Give your games the power of ‘seeing’ with a reusable set of lightweight sensor components.

Game behaviour often requires awareness of the game world:

  • Is the ground beneath my feet?
  • Is there an enemy in my line of sight?
  • Where is some cover?
These are simple problems to state, but not always straightforward to implement with the base Unity components. Sensor Toolkit is designed to answer these questions in a tidy and consistent manner.

Sensors Overview

Core Concepts

Sensors detect GameObjects and maintain a list of detected GameObjects. Objects are added to this list when they are newly detected by the sensor, and remain on the list up until the sensor loses tracking of them, for example if the object moved out of range. How they detect GameObjects depends on the sensors implementation and your configurations. They provide methods to query and filter which GameObjects are detected, and events for when new detections are made or existing detections are lost. To this end each sensor type extends a common base component: SensorToolkit.Sensor.


Here's how you might write a MonoBehaviour that uses a sensor. This sensor could be placed at the end of a maze to check if the player has reached the last room and escaped.

using UnityEngine;
using System.Collections;
using SensorToolkit;

public class EscapeRoom : MonoBehaviour
{
    Sensor sensor;
    bool hasEscaped = false;

    void Awake()
    {
        // RaySensor, RangeSensor and TriggerSensor all extend Sensor
        sensor = GetComponent<Sensor>();
    }

    void Update()
    {
        // GetNearestByName returns the nearest detected GameObject with the specified name, or null if there is none detected.
        if (sensor.GetNearestByName("Player") != null)
        {
            hasEscaped = true;
        }
    }
}

Sensor Pulses

Sensors update their list of detected GameObjects by being pulsed, that is by having their Pulse() method called. This will cause the sensor to perform its 'sensing' routine that adds and removes GameObjects from its list of detected objects. Each of the sensors can be configured to automatically pulse themselves at fixed intervals or every frame. Sometimes however you may want precise control over when a sensor is pulsed, for example a bullet may want to pulse its sensor just before it determines whether it has hit anything. In this example you could disable automatic pulsing on the bullet's sensor and manually call Pulse() when needed.


Detection Mode

Each of the sensors detect GameObjects by their colliders, but that doesn't mean the collider itself will appear in the sensors list of detected objects. Each sensor has two detection modes: Colliders or RigidBodies.


  • If the sensor is in 'Colliders' mode then the detected objects will be the collider GameObjects as expected. If a GameObject has multiple colliders it will still only be detected once.

  • If the sensor is in 'RigidBodies' mode then the detected objects will be the attached RigidBody GameObjects for all the sensed colliders. This will produce a different list if the colliders are on child GameObjects to their attached RigidBody components. Again a GameObject will only appear once in the list, even if it has multiple colliders. If a detected collider doesn't have an attached RigidBody then there is no detected GameObject.

SensorToolkit.Sensor

Base behaviour extended by all sensors.


Properties

List<GameObject> DetectedObjects
Returns a list of all GameObjects detected by the Sensor in no particular order. The same list object is returned each time.
List<GameObject> DetectedObjectsOrderedByDistance
Returns an list of all GameObjects detected by the Sensor in order of distance from the Sensor. The same list object is returned each time.

Events

SensorEventHandler OnDetected
Invoked when a new GameObject is detected.
SensorEventHandler OnLostDetection
Invoked when a GameObject that was detected is no longer detected.

Methods

bool IsDetected(GameObject go)
Returns true if the GameObject is detected by the Sensor.
float GetVisibility(GameObject go)
Only relevant when line of sight testing is enabled. Returns the computed visibility of a GameObject between 0 and 1.
void Pulse()
Causes the sensor to perform it's 'sensing' routine, so that its list of detected objects is updated. Each sensor can be configured to pulse automatically. If you need more control over when the sensor updates then you can call this method manually.
List<GameObject> GetDetected()
List<T> GetDetectedByComponent<T>()
List<GameObject> GetDetectedByName(string name)
List<GameObject> GetDetectedByTag(string tag)
Returns a list of all Detected GameObjects ordered by distance with various filters such as by name, tag or component. More methods exist then shown here for combining filters.
GameObject GetNearest()
T GetNearestByComponent<T>()
GameObject GetNearestByName(string name)
GameObject GetNearestByTag(string tag)
Returns the nearest Detected GameObject filtered by name, tag and component or null if there isn't one. Like above more methods exist then shown here for combining filters.
GameObject GetNearestToPoint(Vector3 p)
T GetNearestToPointByComponent<T>(Vector3 p)
GameObject GetNearestToPointByName(Vector3 p, string name)
GameObject GetNearestToPointByTag(Vector3 p, string tag)
Returns the nearest detected GameObject to a given world position filtered by name, tag and component. Like above more methods exist then shown here for combining filters.

Ray Sensor

A Ray Sensor detects GameObjects along a straight line. It uses either a Physics.Raycast or Physics.SphereCast test dependong on its configurations. A Ray Sensor is defined by its length, radius, the physics layers that it detects GameObjects on, and the physics layers that blocks it's path.



Editor Properties

Length The length of the ray in world units.
Radius The radius of the ray in world units. If greater than 0 the sensor will perform a sphere cast.
Ignore List Any GameObject in this list will not show as a detected object. Useful to stop GameObjects detecting themselves.
Enable Tag Filter When checked the sensor will only detect objects with specific tags.
Allowed Tags If tag filtering is enabled the sensor will only detect objects with a tag in this list.
Obstructed By Layers The Ray Sensor will be obstructed by any collider in these layers.
Detects On Layers The Ray Sensor detects GameObjects on these layers.
Detection Mode
  • Collider: If the ray detects a collider then it's GameObject will be added to the list of detected objects.

  • Rigid Bodies: If the ray detects a collider then adds the attached RigidBody GameObject to the list of detected objects. If there isn't an attached RigidBody then it is ignored.
Direction What direction the ray sensor is pointing.
World Space Is the direction parameter in world space or local space.
Sensor Update Mode
  • Each Frame: The sensor automatically updates each frame during its Update() invocation.

  • Manual: It's up to you to pulse the sensor by calling it's Pulse() method in a script or PlayMaker action. Useful if you need the sensor to be refreshed exactly at the time of calling.
Initial Buffer Size To avoid memory allocations the ray uses Physics.RaycastNonAlloc and Physic.SphereCastNonAlloc. This is the initial size of the buffer used to store results. If you know a sensor will detect many objects you may want to increase this.
Dynamically Increase Buffer Size When true the buffer size will be extended when a Raycast or SphereCast is performed and the buffer is not sufficently large to store all results. The buffer size is doubled and the raycast is performed again. If this is false the buffer size is fixed.

SensorToolkit.RaySensor

The RaySensor behaviour has some extra functionality on top of the SensorToolkit.Sensor behaviour.


Properties

List<RaycastHit> DetectedObjectRayHits
Returns a list of all the RaycastHit objects for all the detected GameObjects. There is only one hit per GameObject and they're returned in no particular order.
Collider ObstructedBy
Returns the collider that obstructed the Ray Sensor if there is one, null otherwise.
RaycastHit ObstructionRayHit
Returns the RaycastHit object for the raycast result that obstructed the RaySensor.
bool IsObstructed
Returns true if something obstructed the Ray Sensor.

Events

UnityEvent OnObstruction
Event fired when the ray sensor becomes obstructed.
UnityEvent OnClear
Event fired when the ray sensor becomes clear of obstructions.

Methods

RaycastHit GetRayHit(GameObject detectedGameObject)
Returns the RaycastHit object for a detected GameObject.

Range Sensor

A Range Sensor detects GameObjects within a specified distance from the sensor. It uses the Physics.OverlapSphereNonAlloc method to detect colliders on the configured physics layers. It can be set to pulse automatically at fixed intervals and has optional support for line of sight testing.


Line Of Sight

The Range Sensor has optional support for line of sight testing. When enabled a GameObject must additionally pass a line of sight test in order to be detected. A number of raycasts will be performed from the sensors origin towards test points on the GameObject. The fraction of rays that are unobstructed determines the GameObjects visibility. When the visibility is greater then a configured threshold then the GameObject is detected.

By default the test points on a GameObject are randomly generated, but you can choose the test points on a GameObject yourself by giving it a LOSTargets component.



Warning
Any GameObject in the IgnoreList array may still block line of sight tests.

Editor Properties

Sensor Range The range of the sensor in world units.
Detects On Layers The Range Sensor detects colliders on these layers.
Ignore List Any GameObject in this list will not show as a detected object. Useful to stop GameObjects detecting themselves.
Enable Tag Filter When checked the sensor will only detect objects with specific tags.
Allowed Tags If tag filtering is enabled then the sensor will only detect objects whose tags are in this list.
Detection Mode
  • Collider: If the range sensor detects a collider then its GameObject will be added to its list of detected objects. Will not add duplicates if a GameObject has multiple colliders.

  • Rigid Bodies: If the range sensor detects a collider then it fetches the attached RigidBody GameObject and adds that to the list of detected objects. If there isn't an attached RigidBody then it is ignored. Will not add duplicates if a RigidBody has multiple colliders.
Sensor Update Mode
  • Fixed Interval: The sensor automatically pulses at fixed intervals during its Update() invocation.

  • Manual: It's up to you to pulse the sensor by calling it's Pulse() method in a script or PlayMaker action. Useful if you need the sensor to be updated at a specific time.
Check Interval If set to pulse at a fixed interval this is the time in seconds between updates.
Requires Line Of Sight When actived then GameObjects must also pass a line of sight test to be detected.
Blocks Line Of Sight The physics layers that block line of sight tests.
Test LOS Targets Only When checked the sensor will only perform line of sight tests on object with an LOSTargets component, if the object doesn't have this component then it won't be detected. If unchecked the sensor will generate random test points on objects that don't have a LOSTargets component.
Number of Rays The number of raycast test points that are generated on objects for performing line of sight tests. Only relevant on objects without a LOSTarget component.
Minimum Visibility The fraction of rays that must be unobstructed for the GameObject to be detected.
Initial Buffer Size To avoid memory allocations the sensor uses Physics.OverlapSphereNonAlloc. This is the initial size of the buffer used to store results. If you know a sensor will detect many objects you may want to increase this.
Dynamically Increase Buffer Size When true the buffer size will be extended when a pulse is performed and the buffer is not sufficently large to store all results. The buffer size is doubled and sensor pulsed again. If this is false the buffer size is fixed.

SensorToolkit.RangeSensor

The Range Sensor behaviour has some extra functionality on top of the SensorToolkit.Sensor behaviour.


Methods

List<Transform> GetVisibleTransforms(GameObject go)
Returns a list of all test transforms on the specified GameObject that passed line of sight tests. These test transforms are taken from the objects LOSTargets component, therefore it must have a LOSTargets component.
List<Position> GetVisiblePositions(GameObject go)
Returns a list of all test positions on the specified GameObject that passed line of sight tests. These test positions were either taken from the GameObjects LOSTargets component if it has one, or they were randomly generated by the sensor.

Trigger Sensor

Trigger Sensors use trigger colliders to detect GameObjects making them the most robust sensor solution but requiring the most care to set up, otherwise you may find them detecting things you don't want them to. They listen for OnTriggerEnter and OnTriggerExit events to detect GameObjects as soon as their colliders intersect with the sensor. They can use any trigger collider or even multiple trigger colliders to make up their sensor volume.



Warning
If a detected GameObject is deactivated then an OnTriggerExit event won't be invoked, even if the GameObject is activated again outside the sensor. This commonly happens with pooling systems, which deactivate the GameObject when returning it to the pool. The result is that the sensor will continue to show the GameObject is detected when it shouldn't. This is a quirk in Unity which I havent found a way to handle. To avoid this issue set the GameObjects position somewhere far away before deactivating it.

Line Of Sight

The Trigger Sensor also has optional support for line of sight testing. When enabled a GameObject must additionally pass a line of sight test in order to be detected. A number of raycasts will be performed from the sensors origin towards test points on the GameObject. The fraction of rays that are unobstructed determines the GameObjects visibility. When the visibility is greater then a configured threshold then the GameObject is detected.

By default the test points on a GameObject are randomly generated, but you can choose the test points on a GameObject yourself by giving it a LOSTargets component.


Note

When LOS is disabled the Pulse method does nothing because GameObjects are detected immediately when they intersect the sensor. If LOS is enabled then the sensor must be pulsed in order to perform line of sight tests.


Setup Recommendations

To get the most out of Trigger Sensors it's important to set up your physics layers correctly. I recommend creating a new physics layer just for sensors that can collide with your existing game layers, but cannot collide with itself. Assign your Trigger Sensors to this layer, set it on the GameObject which holds the Trigger Sensor component.


Editor Properties

Ignore List Any GameObject in this list will not show as a detected object. Useful to stop GameObjects detecting themselves.
Enable Tag Filter When checked the sensor will only detect objects with specific tags.
Allowed Tags If tag filtering is enabled then the sensor will only detect objects whose tags are in this list.
Detection Mode
  • Collider: If the sensor detects a collider then it's GameObject will be added it's list of detected objects. Will not add duplicates if a GameObject has multiple colliders.

  • Rigid Bodies: If the sensor detects a collider then it fetches the attached RigidBody GameObject and adds that to the list of detected objects. If there isn't an attached RigidBody then it is ignored. Will not add duplicates if a RigidBody has multiple colliders.
Requires Line Of Sight When actived then GameObjects must also pass a line of sight test to be detected.
Blocks Line Of Sight The physics layers that block line of sight tests.
Line Of Sight Update Mode
  • Fixed Interval: The sensor automatically refreshes line of sight tests at fixed intervals during its Update() invocation.

  • Manual: It's up to you to pulse the sensor by calling it's Pulse() method in a script or PlayMaker action. Useful if you need the sensor to be refreshed exactly at the time of calling.
Check Line Of Sight Interval If set to refresh line of sight at a fixed interval this is the time in seconds between updates.
Test LOS Targets Only When checked the sensor will only perform line of sight tests on object with an LOSTargets component, if the object doesn't have this component then it won't be detected. If unchecked the sensor will generate random test points on objects that don't have a LOSTargets component.
Number of Rays The number of raycast test points that are generated on objects for performing line of sight tests. Only relevant on objects without a LOSTarget component.
Minimum Visibility The fraction of rays that must be unobstructed for the GameObject to be detected.

SensorToolkit.TriggerSensor

The Trigger Sensor behaviour has some extra functionality on top of the SensorToolkit.Sensor behaviour.


Methods

List<Transform> GetVisibleTransforms(GameObject go)
Returns a list of all test transforms on the specified GameObject that passed line of sight tests. These test transforms are taken from the objects LOSTargets component, therefore it must have a LOSTargets component.
List<Position> GetVisiblePositions(GameObject go)
Returns a list of all test positions on the specified GameObject that passed line of sight tests. These test positions were either taken from the GameObjects LOSTargets component if it has one, or they were randomly generated by the sensor.

Steering Rig

A steering rig is used to compute steering vectors, so that gameobjects are able to navigate around obstacles. Assume you have a character that wants to move in some direction, the steering rig will push that direction depending on the obstacles around it, resulting in a vector that should be in a similar general direction, but clear of nearby obstacles.

The rig computes its steering vectors using a set of ray sensor components on child transforms. You are free to create as many ray sensors as you'd like, and you may orient them how you like. There are some premade steering rig prefabs under the SensorToolkit/SteeringRigPrefabs/ folder, I recommend using one of these as a template.




The steering rig has two modes of operation: you can use it as a simple movement system by assigning a rigid body to control, or you can use it for calculating steering vectors only by leaving the rigid body field blank. If you leave it blank then you are using only the 'sensing' capability of the steering rig whilst retaining full control over your characters movement. Have your character tell the sensor what direction it wants to move, and the sensor will calculate the direction it should move instead so that it avoids nearby obstacles. The Examples/Action example uses the steering rig in this way.

If you use the rig as a movement system then you can give it a destination and the rig will try to move there while avoiding obstacles. Both the Examples/Stealth and Examples/Space use the rig as a movement system as well.


Note

You are free to orient the ray sensors composing the steering rig however you like. For example you could add some sensors at head level on the character steering rig above in order to avoid obstacles on the ceiling.


Configuring Ray Sensors

The steering rig is composed of ray sensors for calulating steered vectors. When any of these ray sensors are obstructed it will affect the direction of the steered vector. You must configure these ray sensors to control what objects the steering rig will avoid.


Editor Properties

Ignore List GameObjects in this list are injected into the ignore lists of the child ray sensors, so they won't affect the steering rig.
Avoidance Sensitivity At higher values the steering rig will try to keep a larger distance from obstacles.
Max Avoidance Length Controls how far a direction can be pushed when the rigs ray sensors are obstructed by obstacles.
Y Axis Whether the steering rig should steer across all three axes or be confined to the x-z plane. This should be turned off for characters that are confined to walk along the ground. This should be turned on for flying units.
Rotate Towards Target When enabled the rig will rotate to face the target direction before determining if its ray sensors are obstructed. This is useful for making asymmetric steering rigs where the majority of ray sensors are facing forwards. All of the included steering rig prefabs have this enabled.
RB *Optional* attach a rigid body here and the steering rig will move it for you. Supports kinematic and non-kinematic rigid bodies.
Turn Force/Turn Speed If a rigid body is attached this is the maximum force/speed to turn it.
Move Force/Move Speed If a rigid body is attached this is the maximum force/speed to move it in a forwards direction.
Strafe Force/Strafe Speed If a rigid body is attached this is the maximum force/speed to move it in any direction other than forwards.
Stopping Distance If a rigid body is attached this is the minimum distance to reach a destination position.
Destination Transform If a rigid body is attached the rig will try to move it towards this transform.
Face Towards Transform If a rigid body is attached the rig will turn it to face this transform. This may cause it to strafe if its moving in a different direction.

SensorToolkit.SteeringRig

The public interface of the steering rig component.


Properties

Vector3 Destination
The destination position that the steering rig is seeking towards.
bool IsSeeking
True when the steering rig is moving its attached rigid body towards its destination.
Vector3 DirectionToFace
The direction that the steering rig should face towards. If none is set this returns Vector3.zero.
bool IsDirectionToFaceAssigned
True when the steering rig is instructed to face a particular direction.

Methods

Vector3 GetSteeredDirection(Vector3 targetDirection)
Steers the targetDirection vector and returns a vector in the same general direction but clear of obstacles. If you haven't attached a rigid body because you want to control movement yourself then this is the only method you need to call.
void ClearDirectionToFace()
Clears the DirectionToFace requirement, enabling the default behaviour where the rig turns its rigid body to face towards its destination.

Field Of View Collider

The FOVCollider is a parametric collider shape designed to create field of view cones for the trigger sensor. You can change the shape of the collider by adjusting it's values in the editor.



Editor Properties

Length The radius of the field of view arc.
Base Size The size of the base.
FOV Angle The field of view angle.
Resolution The number of vertices used to approximate the field of view arc. Ideally this should be as low as possible.

Line Of Sight Targets

The LOSTargets component lets you specify your own points on a target object that should be used for line of sight testing. When a sensor detects a GameObject it first checks for this component and performs a ray test against each user defined entry. If the Sensor doesn't find a LOSTargets component then it reverts to randomly generating points on the object.



Playmaker Integration

Sensor Toolkit ships with custom playmaker actions to help you create beautifully simple FSMs.



The custom actions have similar functionality to the methods described on the sensor pages. Just search for actions in the 'Sensor' category.

Contact

If you have questions, feature requests or would like to report a bug then please email me at micosmogames@gmail.com