A Sensor is a class that reads the current state of the world and provides this information to the WorldState when it's needed. The Resolver uses this information to determine the best action to perform based on the current state of the world.
Sensors can provide the values for two types of data/keys:
WorldKey: A WorldKey references a value in the world. For example AppleCount. All values must be represented by ints.
TargetKey: A TargetKey references a position in the world. For example AppleTree. All positions must be represented by Vector3.
Sensors can work in two scopes: Global or Local.
Global: These sensors give information for all agents of an AgentType. For instance, IsDaytimeSensor checks if it's day or night for everyone.
Local: They give information for just one agent. For example, ClosestAppleSensor finds the nearest apple for a specific agent.
Local
Global
WorldKey
LocalWorldSensorBase
GlobalWorldSensorBase
TargetKey
LocalTargetSensorBase
GlobalTargetSensorBase
WorldSensor
WorldSensor checks the game's situation for an agent. It uses WorldKey to show each situation. The Planner uses this to pick the best action.
Examples:
IsHungrySensor checks if the agent is hungry.
HasAppleSensor checks if the agent has an apple.
Example
To create a new WorldSensor, create a new class that inherits from LocalWorldSensorBase or GlobalWorldSensorBase and implement its Sense method.
usingCrashKonijn.Agent.Core;usingCrashKonijn.Goap.Runtime;usingUnityEngine;namespaceCrashKonijn.Goap.Demos.Simple.Goap.Sensors.Target{ [GoapId("Simple-WanderTargetSensor")]publicclassWanderTargetSensor:LocalTargetSensorBase {privatestaticreadonlyVector2 Bounds =newVector2(15,8);publicoverridevoidCreated() { }publicoverridevoidUpdate() { }publicoverrideITargetSense(IActionReceiver agent,IComponentReference references,ITarget target) {var random =this.GetRandomPosition(agent);returnnewPositionTarget(random); }privateVector3GetRandomPosition(IActionReceiver agent) {var random =Random.insideUnitCircle*5f;var position =agent.Transform.position+newVector3(random.x,0f,random.y);if (position.x>-Bounds.x&&position.x<Bounds.x&&position.z>-Bounds.y&&position.z<Bounds.y)return position;returnthis.GetRandomPosition(agent); } }}
MultiSensor
MultiSensor is a sensor that combines multiple sensors. It can be used to combine multiple sensors into one sensor class. This can make it easier to manage multiple values that come from the same source.
Example
MultiSensor.cs
usingSystem;usingSystem.Collections.Generic;usingCrashKonijn.Docs.GettingStarted.Behaviours;usingCrashKonijn.Goap.Runtime;usingUnityEngine;namespaceCrashKonijn.Docs.GettingStarted.Sensors{publicclassPearSensor:MultiSensorBase { // A cache of all the pears in the worldprivatePearBehaviour[] pears; // You must use the constructor to register all the sensors // This can also be called outside of the gameplay loop to validate the configurationpublicPearSensor() {this.AddLocalWorldSensor<PearCount>((agent, references) => { // Get a cached reference to the DataBehaviour on the agentvar data =references.GetCachedComponent<DataBehaviour>();returndata.pearCount; });this.AddLocalWorldSensor<Hunger>((agent, references) => { // Get a cached reference to the DataBehaviour on the agentvar data =references.GetCachedComponent<DataBehaviour>(); // We need to cast the float to an int, because the hunger is an int // We will lose the decimal values, but we don't need them for this examplereturn (int) data.hunger; });this.AddLocalTargetSensor<ClosestPear>((agent, references, target) => { // Use the cashed pears list to find the closest pearvar closestPear =this.Closest(this.pears,agent.Transform.position);if (closestPear ==null)returnnull; // If the target is a transform target, set the target to the closest pearif (target isTransformTarget transformTarget)returntransformTarget.SetTransform(closestPear.transform);returnnewTransformTarget(closestPear.transform); }); } // The Created method is called when the sensor is created // This can be used to gather references to objects in the scenepublicoverridevoidCreated() { } // This method is equal to the Update method of a local sensor. // It can be used to cache data, like gathering a list of all pears in the scene.publicoverridevoidUpdate() {this.pears=GameObject.FindObjectsOfType<PearBehaviour>(); } // Returns the closest item in a listprivateTClosest<T>(IEnumerable<T> list,Vector3 position)whereT:MonoBehaviour {T closest =null;var closestDistance =float.MaxValue; // Start with the largest possible distanceforeach (var item in list) {var distance =Vector3.Distance(item.gameObject.transform.position, position);if (!(distance < closestDistance))continue; closest = item; closestDistance = distance; }return closest; } }}
Sensor Timer
You can set a timer for a sensor to update at a specific interval. This can be useful when you want to update a sensor every few seconds instead of every frame, or when you want to update a sensor just a single time.
By default the following timers are provided, but custom implementations of ISensorTimer can be made.
publicclassAgentSensor:LocalTargetSensorBase{ // Set the timer to update the sensor oncepublicoverrideISensorTimer Timer { get; } =SensorTimer.Once;publicoverridevoidCreated() { }publicoverridevoidUpdate() { }publicoverrideITargetSense(IActionReceiver agent,IComponentReference references,ITarget target) {returnnewTransformTarget(agent.Transform); }}
Multi Sensors
SensorTimer.cs
publicclassPearSensor:MultiSensorBase{publicPearSensor() { // You can set the timer for each sensor individually in the second parameterthis.AddLocalWorldSensor<PearCount>((agent, references) => {return0; },SensorTimer.Once); }}