diff --git a/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/Capabilities/HungerCapability.cs b/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/Capabilities/HungerCapability.cs index bd604a8e..8deb46c7 100644 --- a/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/Capabilities/HungerCapability.cs +++ b/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/Capabilities/HungerCapability.cs @@ -4,10 +4,12 @@ using CrashKonijn.Goap.Demos.Complex.Factories.Extensions; using CrashKonijn.Goap.Demos.Complex.Goals; using CrashKonijn.Goap.Demos.Complex.Interfaces; +using CrashKonijn.Goap.Demos.Complex.Sensors.Multi; using CrashKonijn.Goap.Demos.Complex.Sensors.World; using CrashKonijn.Goap.Demos.Complex.Targets; using CrashKonijn.Goap.Demos.Complex.WorldKeys; using CrashKonijn.Goap.Runtime; +using TransformTarget = CrashKonijn.Goap.Demos.Complex.Targets.TransformTarget; namespace CrashKonijn.Goap.Demos.Complex.Factories.Capabilities { @@ -20,15 +22,15 @@ public override ICapabilityConfig Create() // Goals builder.AddGoal() .AddCondition(Comparison.SmallerThanOrEqual, 0); - + // Actions builder.AddAction() - .SetTarget() + .SetTarget() .AddEffect(EffectType.Decrease) .AddCondition>(Comparison.GreaterThanOrEqual, 1) .AddCondition(Comparison.GreaterThanOrEqual, 30) .SetValidateConditions(false); // We don't need to validate conditions for this action, or it will stop when becoming below 80 hunger - + builder.AddAction>() .SetTarget>() .AddEffect>(EffectType.Increase) @@ -36,22 +38,23 @@ public override ICapabilityConfig Create() .SetProperties(new GatherItemAction.Props { pickupItem = true, - timer = 3 + timer = 3, }); - + builder.AddPickupItemAction(); - + // Target sensors - builder.AddClosestItemTargetSensor(); builder.AddClosestSourceTargetSensor(); - + // World sensors builder.AddIsHoldingSensor(); - builder.AddIsInWorldSensor(); builder.AddWorldSensor() .SetKey(); + // Multi sensor + builder.AddMultiSensor>(); + return builder.Build(); } } -} \ No newline at end of file +} diff --git a/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/Extensions/TargetSensorExtensions.cs b/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/Extensions/TargetSensorExtensions.cs index d36aefaa..009ca5da 100644 --- a/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/Extensions/TargetSensorExtensions.cs +++ b/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/Extensions/TargetSensorExtensions.cs @@ -8,13 +8,6 @@ namespace CrashKonijn.Goap.Demos.Complex.Factories.Extensions { public static class TargetSensorExtensions { - public static void AddClosestItemTargetSensor(this CapabilityBuilder builder) - where T : class, IHoldable - { - builder.AddTargetSensor>() - .SetTarget>(); - } - public static void AddClosestObjectTargetSensor(this CapabilityBuilder builder) where T : MonoBehaviour { @@ -29,4 +22,4 @@ public static void AddClosestSourceTargetSensor(this CapabilityBuilder builde .SetTarget>(); } } -} \ No newline at end of file +} diff --git a/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/Extensions/WorldSensorExtensions.cs b/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/Extensions/WorldSensorExtensions.cs index a9efffd5..6be2501e 100644 --- a/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/Extensions/WorldSensorExtensions.cs +++ b/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/Extensions/WorldSensorExtensions.cs @@ -13,12 +13,5 @@ public static void AddIsHoldingSensor(this CapabilityBuilder builder) builder.AddWorldSensor>() .SetKey>(); } - - public static void AddIsInWorldSensor(this CapabilityBuilder builder) - where THoldable : IHoldable - { - builder.AddWorldSensor>() - .SetKey>(); - } } -} \ No newline at end of file +} diff --git a/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/MinerAgentTypeConfigFactory.cs b/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/MinerAgentTypeConfigFactory.cs index 972bedf6..bcf37dce 100644 --- a/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/MinerAgentTypeConfigFactory.cs +++ b/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/MinerAgentTypeConfigFactory.cs @@ -3,6 +3,7 @@ using CrashKonijn.Goap.Demos.Complex.Classes.Items; using CrashKonijn.Goap.Demos.Complex.Factories.Capabilities; using CrashKonijn.Goap.Demos.Complex.Factories.Extensions; +using CrashKonijn.Goap.Demos.Complex.Sensors.Multi; using CrashKonijn.Goap.Runtime; namespace CrashKonijn.Goap.Demos.Complex.Factories @@ -12,34 +13,32 @@ public class MinerAgentTypeConfigFactory : AgentTypeFactoryBase public override IAgentTypeConfig Create() { var builder = new AgentTypeBuilder(SetIds.Miner); - + builder.AddCapability(); builder.AddCapability(); builder.AddCapability(); - + builder.CreateCapability("MineCapability", (capability) => { capability.AddPickupItemGoal(); capability.AddGatherItemGoal(); - + capability.AddPickupItemAction(); - + capability.AddGatherItemAction(); capability.AddGatherItemSlowAction(); - - capability.AddClosestItemTargetSensor(); - capability.AddClosestItemTargetSensor(); - + capability.AddClosestSourceTargetSensor(); - + capability.AddIsHoldingSensor(); capability.AddIsHoldingSensor(); - - capability.AddIsInWorldSensor(); - capability.AddIsInWorldSensor(); + + // Multi sensor + capability.AddMultiSensor>(); + capability.AddMultiSensor>(); }); - + return builder.Build(); } } -} \ No newline at end of file +} diff --git a/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/SmithAgentTypeConfigFactory.cs b/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/SmithAgentTypeConfigFactory.cs index cfc1d00b..3e767f38 100644 --- a/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/SmithAgentTypeConfigFactory.cs +++ b/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/SmithAgentTypeConfigFactory.cs @@ -4,6 +4,7 @@ using CrashKonijn.Goap.Demos.Complex.Classes.Sources; using CrashKonijn.Goap.Demos.Complex.Factories.Capabilities; using CrashKonijn.Goap.Demos.Complex.Factories.Extensions; +using CrashKonijn.Goap.Demos.Complex.Sensors.Multi; using CrashKonijn.Goap.Runtime; namespace CrashKonijn.Goap.Demos.Complex.Factories @@ -13,7 +14,7 @@ public class SmithAgentTypeConfigFactory : AgentTypeFactoryBase public override IAgentTypeConfig Create() { var builder = new AgentTypeBuilder(SetIds.Smith); - + builder.AddCapability(); builder.AddCapability(); builder.AddCapability(); @@ -34,9 +35,6 @@ public override IAgentTypeConfig Create() // TargetSensors capability.AddClosestObjectTargetSensor(); - capability.AddClosestItemTargetSensor(); - capability.AddClosestItemTargetSensor(); - capability.AddClosestSourceTargetSensor(); capability.AddClosestSourceTargetSensor(); @@ -44,13 +42,14 @@ public override IAgentTypeConfig Create() capability.AddIsHoldingSensor(); capability.AddIsHoldingSensor(); - capability.AddIsInWorldSensor(); - capability.AddIsInWorldSensor(); - capability.AddIsInWorldSensor(); - capability.AddIsInWorldSensor(); + // Multi sensor + capability.AddMultiSensor>(); + capability.AddMultiSensor>(); + capability.AddMultiSensor>(); + capability.AddMultiSensor>(); }); return builder.Build(); } } -} \ No newline at end of file +} diff --git a/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/WoodCutterAgentTypeConfigFactory.cs b/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/WoodCutterAgentTypeConfigFactory.cs index b3647c24..02baaaa2 100644 --- a/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/WoodCutterAgentTypeConfigFactory.cs +++ b/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Factories/WoodCutterAgentTypeConfigFactory.cs @@ -3,6 +3,7 @@ using CrashKonijn.Goap.Demos.Complex.Classes.Items; using CrashKonijn.Goap.Demos.Complex.Factories.Capabilities; using CrashKonijn.Goap.Demos.Complex.Factories.Extensions; +using CrashKonijn.Goap.Demos.Complex.Sensors.Multi; using CrashKonijn.Goap.Runtime; namespace CrashKonijn.Goap.Demos.Complex.Factories @@ -12,38 +13,36 @@ public class WoodCutterAgentTypeConfigFactory : AgentTypeFactoryBase public override IAgentTypeConfig Create() { var builder = new AgentTypeBuilder(SetIds.WoodCutter); - + builder.AddCapability(); builder.AddCapability(); builder.AddCapability(); - + builder.CreateCapability("WoodCutterCapability", (capability) => { // Goals capability.AddPickupItemGoal(); capability.AddGatherItemGoal(); - + // Actions capability.AddPickupItemAction(); - + capability.AddGatherItemAction(); capability.AddGatherItemSlowAction(); - + // Target sensors - capability.AddClosestItemTargetSensor(); - capability.AddClosestItemTargetSensor(); - capability.AddClosestSourceTargetSensor(); - + // World sensors capability.AddIsHoldingSensor(); capability.AddIsHoldingSensor(); - - capability.AddIsInWorldSensor(); - capability.AddIsInWorldSensor(); + + // Multi sensor + capability.AddMultiSensor>(); + capability.AddMultiSensor>(); }); - + return builder.Build(); } } -} \ No newline at end of file +} diff --git a/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Sensors/Multi.meta b/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Sensors/Multi.meta new file mode 100644 index 00000000..f34fa480 --- /dev/null +++ b/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Sensors/Multi.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d360d904c1834f9c9bf72cf2c2b5e469 +timeCreated: 1730975891 \ No newline at end of file diff --git a/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Sensors/Multi/ItemSensors.cs b/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Sensors/Multi/ItemSensors.cs new file mode 100644 index 00000000..ddad2ab8 --- /dev/null +++ b/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Sensors/Multi/ItemSensors.cs @@ -0,0 +1,51 @@ +using System.Linq; +using CrashKonijn.Agent.Core; +using CrashKonijn.Goap.Core; +using CrashKonijn.Goap.Demos.Complex.Behaviours; +using CrashKonijn.Goap.Demos.Complex.Interfaces; +using CrashKonijn.Goap.Demos.Complex.Targets; +using CrashKonijn.Goap.Demos.Complex.WorldKeys; +using CrashKonijn.Goap.Runtime; +using UnityEngine; +using TransformTarget = CrashKonijn.Goap.Runtime.TransformTarget; + +namespace CrashKonijn.Goap.Demos.Complex.Sensors.Multi +{ + public class ItemSensor : MultiSensorBase + where T : class, IHoldable + { + private ItemCollection collection; + + public ItemSensor() + { + this.AddLocalTargetSensor>(this.SenseClosestTarget); + this.AddLocalWorldSensor>(this.SenseIsInWorld); + } + + public override void Created() + { + this.collection = Object.FindObjectOfType(); + } + + public override void Update() { } + + private ITarget SenseClosestTarget(IActionReceiver agent, IComponentReference references, ITarget target) + { + var closest = this.collection.GetFiltered(false, true, agent.Transform.gameObject).Cast().Closest(agent.Transform.position); + + if (closest == null) + return null; + + // Re-use the existing target if the target exists + if (target is TransformTarget targetTransform) + return targetTransform.SetTransform(closest.transform); + + return new TransformTarget(closest.transform); + } + + private SenseValue SenseIsInWorld(IActionReceiver agent, IComponentReference references) + { + return this.collection.GetFiltered(false, true, agent.Transform.gameObject).Length; + } + } +} diff --git a/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Sensors/Multi/ItemSensors.cs.meta b/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Sensors/Multi/ItemSensors.cs.meta new file mode 100644 index 00000000..8b7ef857 --- /dev/null +++ b/Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Sensors/Multi/ItemSensors.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6a4a0a2524ae44609cd761aeab66947f +timeCreated: 1730975891 \ No newline at end of file diff --git a/Demo/Assets/Docs/GettingStarted/Sensors/PearSensor.cs b/Demo/Assets/Docs/GettingStarted/Sensors/PearSensor.cs index 1870cbfe..5b741822 100644 --- a/Demo/Assets/Docs/GettingStarted/Sensors/PearSensor.cs +++ b/Demo/Assets/Docs/GettingStarted/Sensors/PearSensor.cs @@ -1,8 +1,8 @@ -using System; using System.Collections.Generic; using CrashKonijn.Docs.GettingStarted.Behaviours; using CrashKonijn.Goap.Runtime; using UnityEngine; +using Object = UnityEngine.Object; namespace CrashKonijn.Docs.GettingStarted.Sensors { @@ -11,9 +11,9 @@ public class PearSensor : MultiSensorBase // A cache of all the pears in the world private PearBehaviour[] pears; - // The Created method is called when the sensor is created - // You must use this method to register all the sensors - public override void Created() + // You must use the constructor to register all the sensors + // This can also be called outside of the gameplay loop to validate the configuration + public PearSensor() { this.AddLocalWorldSensor((agent, references) => { @@ -22,7 +22,7 @@ public override void Created() return data.pearCount; }); - + this.AddLocalWorldSensor((agent, references) => { // Get a cached reference to the DataBehaviour on the agent @@ -32,28 +32,32 @@ public override void Created() // We will lose the decimal values, but we don't need them for this example return (int) data.hunger; }); - + this.AddLocalTargetSensor((agent, references, target) => { // Use the cashed pears list to find the closest pear var closestPear = this.Closest(this.pears, agent.Transform.position); - + if (closestPear == null) return null; - + // If the target is a transform target, set the target to the closest pear if (target is TransformTarget transformTarget) return transformTarget.SetTransform(closestPear.transform); - + return new TransformTarget(closestPear.transform); }); } - + + // The Created method is called when the sensor is created + // This can be used to gather references to objects in the scene + public override void Created() { } + // 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. public override void Update() { - this.pears = GameObject.FindObjectsOfType(); + this.pears = Object.FindObjectsOfType(); } // Returns the closest item in a list @@ -66,10 +70,10 @@ private T Closest(IEnumerable list, Vector3 position) foreach (var item in list) { var distance = Vector3.Distance(item.gameObject.transform.position, position); - + if (!(distance < closestDistance)) continue; - + closest = item; closestDistance = distance; } diff --git a/Package/Documentation/Classes/Sensors.md b/Package/Documentation/Classes/Sensors.md index 41c97efa..d822aac6 100644 --- a/Package/Documentation/Classes/Sensors.md +++ b/Package/Documentation/Classes/Sensors.md @@ -172,9 +172,9 @@ namespace CrashKonijn.Docs.GettingStarted.Sensors // A cache of all the pears in the world private PearBehaviour[] pears; - // The Created method is called when the sensor is created - // You must use this method to register all the sensors - public override void Created() + // You must use the constructor to register all the sensors + // This can also be called outside of the gameplay loop to validate the configuration + public PearSensor() { this.AddLocalWorldSensor((agent, references) => { @@ -209,6 +209,12 @@ namespace CrashKonijn.Docs.GettingStarted.Sensors return new TransformTarget(closestPear.transform); }); } + + // The Created method is called when the sensor is created + // This can be used to gather references to objects in the scene + public override void Created() + { + } // 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. @@ -290,7 +296,7 @@ public class AgentSensor : LocalTargetSensorBase ```csharp public class PearSensor : MultiSensorBase { - public override void Created() + public PearSensor() { // You can set the timer for each sensor individually in the second parameter this.AddLocalWorldSensor((agent, references) => diff --git a/Package/Documentation/Introduction/Pears.md b/Package/Documentation/Introduction/Pears.md index a0da48bb..285e7459 100644 --- a/Package/Documentation/Introduction/Pears.md +++ b/Package/Documentation/Introduction/Pears.md @@ -73,9 +73,9 @@ namespace CrashKonijn.Docs.GettingStarted.Sensors // A cache of all the pears in the world private PearBehaviour[] pears; - // The Created method is called when the sensor is created - // You must use this method to register all the sensors - public override void Created() + // You must use the constructor to register all the sensors + // This can also be called outside of the gameplay loop to validate the configuration + public PearSensor() { this.AddLocalWorldSensor((agent, references) => { @@ -110,6 +110,12 @@ namespace CrashKonijn.Docs.GettingStarted.Sensors return new TransformTarget(closestPear.transform); }); } + + // The Created method is called when the sensor is created + // This can be used to gather references to objects in the scene + public override void Created() + { + } // 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. @@ -140,7 +146,6 @@ namespace CrashKonijn.Docs.GettingStarted.Sensors } } } - ``` {% endcode %} diff --git a/Package/Runtime/CrashKonijn.Goap.Runtime/Classes/Validators/DuplicateTargetSensorValidator.cs b/Package/Runtime/CrashKonijn.Goap.Runtime/Classes/Validators/DuplicateTargetSensorValidator.cs index 5611d0f7..1f765092 100644 --- a/Package/Runtime/CrashKonijn.Goap.Runtime/Classes/Validators/DuplicateTargetSensorValidator.cs +++ b/Package/Runtime/CrashKonijn.Goap.Runtime/Classes/Validators/DuplicateTargetSensorValidator.cs @@ -1,4 +1,5 @@ using System.Linq; +using CrashKonijn.Agent.Runtime; using CrashKonijn.Goap.Core; namespace CrashKonijn.Goap.Runtime @@ -34,7 +35,7 @@ private string[] GetMultiSensorKeys(IAgentTypeConfig agentTypeConfig) return temp .SelectMany(x => x.GetKeys()) - .Select(x => x.Name) + .Select(x => x.GetGenericTypeName()) .ToArray(); } } diff --git a/Package/Runtime/CrashKonijn.Goap.Runtime/Classes/Validators/DuplicateWorldSensorValidator.cs b/Package/Runtime/CrashKonijn.Goap.Runtime/Classes/Validators/DuplicateWorldSensorValidator.cs index 8ca7e9d0..101e8d77 100644 --- a/Package/Runtime/CrashKonijn.Goap.Runtime/Classes/Validators/DuplicateWorldSensorValidator.cs +++ b/Package/Runtime/CrashKonijn.Goap.Runtime/Classes/Validators/DuplicateWorldSensorValidator.cs @@ -1,4 +1,5 @@ using System.Linq; +using CrashKonijn.Agent.Runtime; using CrashKonijn.Goap.Core; namespace CrashKonijn.Goap.Runtime @@ -35,7 +36,7 @@ private string[] GetMultiSensorKeys(IAgentTypeConfig agentTypeConfig) return temp .SelectMany(x => x.GetKeys()) - .Select(x => x.Name) + .Select(x => x.GetGenericTypeName()) .Distinct() .ToArray(); } diff --git a/Package/Runtime/CrashKonijn.Goap.Runtime/Classes/Validators/TargetKeySensorsValidator.cs b/Package/Runtime/CrashKonijn.Goap.Runtime/Classes/Validators/TargetKeySensorsValidator.cs index aa5bdb73..ce422aea 100644 --- a/Package/Runtime/CrashKonijn.Goap.Runtime/Classes/Validators/TargetKeySensorsValidator.cs +++ b/Package/Runtime/CrashKonijn.Goap.Runtime/Classes/Validators/TargetKeySensorsValidator.cs @@ -1,4 +1,5 @@ using System.Linq; +using CrashKonijn.Agent.Runtime; using CrashKonijn.Goap.Core; namespace CrashKonijn.Goap.Runtime @@ -33,7 +34,7 @@ private string[] GetMultiSensorKeys(IAgentTypeConfig agentTypeConfig) return temp .SelectMany(x => x.GetKeys()) - .Select(x => x.Name) + .Select(x => x.GetGenericTypeName()) .Distinct() .ToArray(); } diff --git a/Package/Runtime/CrashKonijn.Goap.Runtime/Classes/Validators/WorldKeySensorsValidator.cs b/Package/Runtime/CrashKonijn.Goap.Runtime/Classes/Validators/WorldKeySensorsValidator.cs index 9a28843d..52cc7721 100644 --- a/Package/Runtime/CrashKonijn.Goap.Runtime/Classes/Validators/WorldKeySensorsValidator.cs +++ b/Package/Runtime/CrashKonijn.Goap.Runtime/Classes/Validators/WorldKeySensorsValidator.cs @@ -1,4 +1,5 @@ using System.Linq; +using CrashKonijn.Agent.Runtime; using CrashKonijn.Goap.Core; namespace CrashKonijn.Goap.Runtime @@ -29,11 +30,11 @@ private string[] GetWorldSensorKeys(IAgentTypeConfig agentTypeConfig) private string[] GetMultiSensorKeys(IAgentTypeConfig agentTypeConfig) { - var temp = new ClassResolver().Load(agentTypeConfig.MultiSensors); + var sensors = new ClassResolver().Load(agentTypeConfig.MultiSensors); - return temp + return sensors .SelectMany(x => x.GetKeys()) - .Select(x => x.Name) + .Select(x => x.GetGenericTypeName()) .Distinct() .ToArray(); } diff --git a/Package/Runtime/CrashKonijn.Goap.Runtime/Sensors/MultiSensorBase.cs b/Package/Runtime/CrashKonijn.Goap.Runtime/Sensors/MultiSensorBase.cs index c9543307..bebb44ef 100644 --- a/Package/Runtime/CrashKonijn.Goap.Runtime/Sensors/MultiSensorBase.cs +++ b/Package/Runtime/CrashKonijn.Goap.Runtime/Sensors/MultiSensorBase.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using CrashKonijn.Agent.Core; using CrashKonijn.Goap.Core; @@ -80,6 +82,8 @@ public void Sense(IWorldData data, IActionReceiver agent, IComponentReference re public void AddLocalWorldSensor(Func sense, ISensorTimer timer = null) where TKey : IWorldKey { + this.ValidateCalledFromConstructor(); + timer ??= SensorTimer.Always; this.LocalSensors.Add(typeof(TKey), new LocalSensor @@ -100,6 +104,8 @@ public void AddLocalWorldSensor(Func(Func sense, ISensorTimer timer = null) where TKey : IWorldKey { + this.ValidateCalledFromConstructor(); + timer ??= SensorTimer.Always; this.GlobalSensors.Add(typeof(TKey), new GlobalSensor @@ -120,6 +126,8 @@ public void AddGlobalWorldSensor(Func sense, ISensorTimer time public void AddLocalTargetSensor(Func sense, ISensorTimer timer = null) where TKey : ITargetKey { + this.ValidateCalledFromConstructor(); + timer ??= SensorTimer.Always; this.LocalSensors.Add(typeof(TKey), new LocalSensor @@ -140,6 +148,8 @@ public void AddLocalTargetSensor(Func(Func sense, ISensorTimer timer = null) where TKey : ITargetKey { + this.ValidateCalledFromConstructor(); + timer ??= SensorTimer.Always; this.GlobalSensors.Add(typeof(TKey), new GlobalSensor @@ -173,6 +183,23 @@ public string[] GetSensors() return sensors.ToArray(); } + + private void ValidateCalledFromConstructor() + { +#if UNITY_EDITOR + var stackTrace = new StackTrace(); + var frames = stackTrace.GetFrames(); + + // Check if any of the frames belong to the constructor of this class + var calledFromConstructor = frames != null && frames.Any(f => + f.GetMethod() is { IsConstructor: true } && + typeof(MultiSensorBase).IsAssignableFrom(f.GetMethod().DeclaringType) + ); + + if (!calledFromConstructor) + throw new InvalidOperationException("Multi sensor registration must be added from the constructor of the sensor, not the Created method."); +#endif + } } public class GlobalSensor : IGlobalSensor