新增SensorToolkit
This commit is contained in:
429
Assets/SensorToolkit/Sensors/ArcSensor.cs
Normal file
429
Assets/SensorToolkit/Sensors/ArcSensor.cs
Normal file
@@ -0,0 +1,429 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
/**
|
||||
* The Arc Sensor detects objects that intersect a curve. It works just like the Ray Sensor in that it detects all
|
||||
* objects up to the first obstruction. The arc is broken up into line segments and a raycast is done on each.
|
||||
*/
|
||||
[AddComponentMenu("Sensors/Arc Sensor")]
|
||||
[HelpURL("https://micosmo.com/sensortoolkit2/docs/manual/sensors/arc")]
|
||||
public class ArcSensor : Sensor, IRayCastingSensor, IPulseRoutine {
|
||||
|
||||
#region Configurations
|
||||
public enum ParameterisationType { Bezier, Ballistic }
|
||||
|
||||
[Tooltip("Choose how the arc is represented.")]
|
||||
public ParameterisationType Parameterisation;
|
||||
|
||||
// Configurations for Bezier type arc
|
||||
public BezierCurve Bezier = new BezierCurve(new Vector3(0, 0, 0), new Vector3(0, 0, 1), new Vector3(0, 1, 1), 10);
|
||||
|
||||
// Configurations for Ballistic type arc
|
||||
public BallisticCurve Ballistic = new BallisticCurve(new Vector3(0, 0, 5), new Vector3(0, -10, 0), 1f, 10);
|
||||
|
||||
[Tooltip("Is the curve parameterised in world space or local space.")]
|
||||
public bool WorldSpace = false;
|
||||
|
||||
[Tooltip("A layer mask specifying which physics layers objects will be detected on.")]
|
||||
public LayerMask DetectsOnLayers;
|
||||
|
||||
[Tooltip("A layer mask specifying which physics layers objects will obstruct the ray on.")]
|
||||
public LayerMask ObstructedByLayers;
|
||||
|
||||
[Tooltip("In Collider mode the sensor detects GameObjects attached to colliders. In RigidBody mode it detects the Collider.AttachedRigidbody.")]
|
||||
public DetectionModes DetectionMode;
|
||||
|
||||
[SerializeField]
|
||||
SignalFilter signalFilter = new SignalFilter();
|
||||
|
||||
[Tooltip("Ignores all trigger colliders. Will not detect them or be obstructed by them.")]
|
||||
public bool IgnoreTriggerColliders;
|
||||
|
||||
[SerializeField]
|
||||
PulseRoutine pulseRoutine = new PulseRoutine();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
[SerializeField]
|
||||
ObstructionEvent onObstruction;
|
||||
public ObstructionEvent OnObstruction {
|
||||
get => onObstruction;
|
||||
set => onObstruction = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
ObstructionEvent onClear;
|
||||
public ObstructionEvent OnClear {
|
||||
get => onClear;
|
||||
set => onClear = value;
|
||||
}
|
||||
|
||||
public override event Action OnPulsed;
|
||||
#endregion
|
||||
|
||||
#region Public
|
||||
// Edit the IgnoreList at runtime. Anything in the list will not be detected
|
||||
public List<GameObject> IgnoreList => signalFilter.IgnoreList;
|
||||
|
||||
// Enable/Disable the tag filtering at runtime
|
||||
public bool EnableTagFilter {
|
||||
get => signalFilter.EnableTagFilter;
|
||||
set => signalFilter.EnableTagFilter = value;
|
||||
}
|
||||
|
||||
// Change the allowed tags at runtime
|
||||
public string[] AllowedTags {
|
||||
get => signalFilter.AllowedTags;
|
||||
set => signalFilter.AllowedTags = value;
|
||||
}
|
||||
|
||||
// Change the pulse mode at runtime
|
||||
public PulseRoutine.Modes PulseMode {
|
||||
get => pulseRoutine.Mode.Value;
|
||||
set => pulseRoutine.Mode.Value = value;
|
||||
}
|
||||
|
||||
// Change the pulse interval at runtime
|
||||
public float PulseInterval {
|
||||
get => pulseRoutine.Interval.Value;
|
||||
set => pulseRoutine.Interval.Value = value;
|
||||
}
|
||||
|
||||
// Change at runtime if the sensor will pulse in Update or FixedUpdate
|
||||
public PulseRoutine.UpdateFunctions PulseUpdateFunction {
|
||||
get => pulseRoutine.UpdateFunction;
|
||||
set => pulseRoutine.UpdateFunction = value;
|
||||
}
|
||||
|
||||
// The array size allocated for storing results from Physics.RaycastNonAlloc. Will automatically
|
||||
// be doubled in size when more space is needed.
|
||||
public int CurrentBufferSize => physics != null ? physics.Buffer.Length : 0;
|
||||
|
||||
// Returns true if the ray sensor is being obstructed and false otherwise
|
||||
public bool IsObstructed => GetObstructionRayHit().IsObstructing;
|
||||
|
||||
public List<Vector3> UnobstructedArcPoints => arcPoints;
|
||||
public List<Vector3> ObstructedArcPoints => obstructedArcPoints;
|
||||
|
||||
public override void PulseAll() => Pulse();
|
||||
|
||||
public override void Clear() {
|
||||
base.Clear();
|
||||
clearDetectedObjects();
|
||||
SendObstructionEvents();
|
||||
SampleArc();
|
||||
}
|
||||
|
||||
// Returns the RayHit data associated with the detected GameObject
|
||||
public RayHit GetDetectionRayHit(GameObject detectedGameObject) {
|
||||
var result = RayHit.None;
|
||||
goWorkList.Clear();
|
||||
signalPipeline.GetInputObjects(detectedGameObject, goWorkList);
|
||||
foreach (var input in goWorkList) {
|
||||
RaycastHit hit;
|
||||
if (detectedObjectHits.TryGetValue(input, out hit)) {
|
||||
if (result.Equals(RayHit.None) || result.Distance > hit.distance) {
|
||||
result = new RayHit() {
|
||||
IsObstructing = false,
|
||||
Point = hit.point,
|
||||
Normal = hit.normal,
|
||||
Distance = hit.distance,
|
||||
DistanceFraction = hit.distance / totalLength,
|
||||
Collider = hit.collider
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns the RayHit data associated with the obstructing GameObject
|
||||
public RayHit GetObstructionRayHit() {
|
||||
if (!isObstructed || obstructionRayHit.collider == null) {
|
||||
return RayHit.None;
|
||||
}
|
||||
return new RayHit() {
|
||||
IsObstructing = true,
|
||||
Point = obstructionRayHit.point,
|
||||
Normal = obstructionRayHit.normal,
|
||||
Distance = obstructionRayHit.distance,
|
||||
DistanceFraction = obstructionRayHit.distance / totalLength,
|
||||
Collider = obstructionRayHit.collider
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internals
|
||||
List<Vector3> arcPoints = new List<Vector3>();
|
||||
List<Vector3> obstructedArcPoints = new List<Vector3>();
|
||||
|
||||
bool isObstructed = false;
|
||||
RaycastHit obstructionRayHit;
|
||||
Dictionary<GameObject, RaycastHit> detectedObjectHits = new Dictionary<GameObject, RaycastHit>();
|
||||
List<Signal> workList = new List<Signal>();
|
||||
float totalLength = 0f;
|
||||
List<GameObject> goWorkList = new List<GameObject>();
|
||||
|
||||
static ArcSegmentTest arcSegmentTestInstance = new ArcSegmentTest();
|
||||
PhysicsNonAlloc<RaycastHit> physics;
|
||||
|
||||
protected override PulseJob GetPulseJob() {
|
||||
if (physics == null) {
|
||||
physics = new PhysicsNonAlloc<RaycastHit>();
|
||||
}
|
||||
|
||||
MapToRigidBody.Configure(DetectionMode, false);
|
||||
|
||||
TestArc();
|
||||
|
||||
UpdateAllSignals(workList);
|
||||
SendObstructionEvents();
|
||||
|
||||
OnPulsed?.Invoke();
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
protected override void InitialiseSignalProcessors() {
|
||||
base.InitialiseSignalProcessors();
|
||||
MapToSignalProxy.Configure(true);
|
||||
SignalFilter = signalFilter;
|
||||
}
|
||||
|
||||
protected override List<Collider> GetInputColliders(GameObject InputObject, List<Collider> storeIn) {
|
||||
RaycastHit hit;
|
||||
if (detectedObjectHits.TryGetValue(InputObject, out hit)) {
|
||||
storeIn.Add(hit.collider);
|
||||
}
|
||||
return storeIn;
|
||||
}
|
||||
|
||||
Coroutine StartEachFrame(IEnumerator routine) => StartCoroutine(PulseEachFrame(routine));
|
||||
IEnumerator PulseEachFrame(IEnumerator routine) {
|
||||
while (routine.MoveNext()) {
|
||||
yield return routine.Current;
|
||||
}
|
||||
}
|
||||
Coroutine StartFixedInterval(IEnumerator routine) => StartCoroutine(PulseFixedInterval(routine));
|
||||
IEnumerator PulseFixedInterval(IEnumerator routine) {
|
||||
while (routine.MoveNext()) {
|
||||
yield return routine.Current;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void Awake() {
|
||||
base.Awake();
|
||||
|
||||
if (onObstruction == null) {
|
||||
onObstruction = new ObstructionEvent();
|
||||
}
|
||||
|
||||
if (onClear == null) {
|
||||
onClear = new ObstructionEvent();
|
||||
}
|
||||
|
||||
if (pulseRoutine == null) {
|
||||
pulseRoutine = new PulseRoutine();
|
||||
}
|
||||
pulseRoutine.Awake(this, StartEachFrame, StartFixedInterval);
|
||||
}
|
||||
|
||||
void OnEnable() {
|
||||
clearDetectedObjects();
|
||||
pulseRoutine.OnEnable();
|
||||
}
|
||||
|
||||
protected override void OnDisable() {
|
||||
base.OnDisable();
|
||||
pulseRoutine.OnDisable();
|
||||
}
|
||||
|
||||
void OnValidate() {
|
||||
pulseRoutine?.OnValidate();
|
||||
}
|
||||
|
||||
bool isSingleResult() {
|
||||
var layerMaskIsSubsset = ((DetectsOnLayers | ObstructedByLayers) & (~ObstructedByLayers)) == 0;
|
||||
return layerMaskIsSubsset && signalFilter.IsNull();
|
||||
}
|
||||
|
||||
List<RaycastHit> hits = new List<RaycastHit>();
|
||||
void TestArc() {
|
||||
clearDetectedObjects();
|
||||
|
||||
SampleArc();
|
||||
|
||||
var accLength = 0f;
|
||||
var segmentIsObstructed = false;
|
||||
for (var i = 0; i < arcPoints.Count - 1; i++) {
|
||||
var p1 = arcPoints[i];
|
||||
var p2 = arcPoints[i + 1];
|
||||
var delta = p2 - p1;
|
||||
var length = delta.magnitude;
|
||||
var dir = delta / length;
|
||||
|
||||
arcSegmentTestInstance.SegmentRay = new Ray(p1, dir);
|
||||
arcSegmentTestInstance.SegmentLength = length;
|
||||
var numberOfHits = physics.PerformTest(this, arcSegmentTestInstance);
|
||||
|
||||
hits.Clear();
|
||||
for (int j = 0; j < numberOfHits; j++) {
|
||||
var hit = physics.Buffer[j];
|
||||
hit.distance += accLength;
|
||||
hits.Add(hit);
|
||||
}
|
||||
|
||||
hits.Sort(RaycastHitComparison);
|
||||
|
||||
foreach (var hit in hits) {
|
||||
if ((1 << hit.collider.gameObject.layer & DetectsOnLayers) != 0) {
|
||||
if (signalFilter.TestCollider(hit.collider)) {
|
||||
addRayHit(hit);
|
||||
}
|
||||
}
|
||||
if ((1 << hit.collider.gameObject.layer & ObstructedByLayers) != 0) {
|
||||
if (signalFilter.TestCollider(hit.collider)) {
|
||||
obstructionRayHit = hit;
|
||||
segmentIsObstructed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (segmentIsObstructed) {
|
||||
var obspoint = obstructionRayHit.point;
|
||||
obstructedArcPoints.Add(obspoint);
|
||||
for (var j = i+1; j < arcPoints.Count; j++) {
|
||||
obstructedArcPoints.Add(arcPoints[j]);
|
||||
}
|
||||
arcPoints.RemoveRange(i+1, obstructedArcPoints.Count - 1);
|
||||
arcPoints.Add(obspoint);
|
||||
break;
|
||||
}
|
||||
|
||||
accLength += length;
|
||||
}
|
||||
}
|
||||
|
||||
void SampleArc() {
|
||||
obstructedArcPoints.Clear();
|
||||
|
||||
if (Parameterisation == ParameterisationType.Bezier) {
|
||||
arcPoints = Bezier.Sample(
|
||||
transform.position,
|
||||
WorldSpace ? Quaternion.identity : transform.rotation,
|
||||
arcPoints
|
||||
);
|
||||
} else if (Parameterisation == ParameterisationType.Ballistic) {
|
||||
arcPoints = Ballistic.Sample(
|
||||
transform.position,
|
||||
WorldSpace ? Quaternion.identity : transform.rotation,
|
||||
arcPoints
|
||||
);
|
||||
}
|
||||
|
||||
totalLength = 0f;
|
||||
for (var i = 0; i < arcPoints.Count-1; i++) {
|
||||
totalLength += (arcPoints[i + 1] - arcPoints[i]).magnitude;
|
||||
}
|
||||
}
|
||||
|
||||
void SendObstructionEvents() {
|
||||
if (isObstructed && obstructionRayHit.collider == null) {
|
||||
isObstructed = false;
|
||||
OnClear.Invoke(this);
|
||||
} else if (!isObstructed && obstructionRayHit.collider != null) {
|
||||
isObstructed = true;
|
||||
OnObstruction.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
void addRayHit(RaycastHit hit) {
|
||||
var go = hit.collider.gameObject;
|
||||
if (!detectedObjectHits.ContainsKey(go)) {
|
||||
detectedObjectHits.Add(go, hit);
|
||||
workList.Add(new Signal() {
|
||||
Object = go,
|
||||
Strength = 1f,
|
||||
Bounds = new Bounds(hit.point, Vector3.zero)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void clearDetectedObjects() {
|
||||
obstructionRayHit = new RaycastHit();
|
||||
detectedObjectHits.Clear();
|
||||
workList.Clear();
|
||||
}
|
||||
|
||||
static Comparison<RaycastHit> RaycastHitComparison = new Comparison<RaycastHit>(CompareRaycastHits);
|
||||
|
||||
static int CompareRaycastHits(RaycastHit x, RaycastHit y) {
|
||||
if (x.distance < y.distance) {
|
||||
return -1;
|
||||
} else if (x.distance > y.distance) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected override void OnDrawGizmosSelected() {
|
||||
//base.OnDrawGizmosSelected();
|
||||
|
||||
if (!IsRunning()) {
|
||||
SampleArc();
|
||||
}
|
||||
|
||||
if (IsObstructed && IsRunning()) {
|
||||
Gizmos.color = STPrefs.CastingBlockedRayColour;
|
||||
} else {
|
||||
Gizmos.color = STPrefs.CastingRayColour;
|
||||
}
|
||||
for (int i = 0; i < arcPoints.Count - 1; i++) {
|
||||
var p1 = arcPoints[i];
|
||||
var p2 = arcPoints[i + 1];
|
||||
Gizmos.DrawLine(p1, p2);
|
||||
}
|
||||
|
||||
if (ShowDetectionGizmos) {
|
||||
foreach (var detection in GetDetections()) {
|
||||
var hit = GetDetectionRayHit(detection);
|
||||
SensorGizmos.RaycastHitGizmo(hit.Point, hit.Normal, false);
|
||||
}
|
||||
if (IsObstructed) {
|
||||
SensorGizmos.RaycastHitGizmo(GetObstructionRayHit().Point, GetObstructionRayHit().Normal, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IsRunning() {
|
||||
return Application.isPlaying || ShowDetectionGizmos;
|
||||
}
|
||||
|
||||
class ArcSegmentTest : ITestNonAlloc<ArcSensor, RaycastHit> {
|
||||
public Ray SegmentRay;
|
||||
public float SegmentLength;
|
||||
|
||||
public int Test(ArcSensor sensor, RaycastHit[] results) {
|
||||
var queryTriggerInteraction = sensor.IgnoreTriggerColliders ? QueryTriggerInteraction.Ignore : QueryTriggerInteraction.Collide;
|
||||
LayerMask combinedLayers = sensor.DetectsOnLayers | sensor.ObstructedByLayers;
|
||||
if (sensor.isSingleResult()) {
|
||||
RaycastHit hit;
|
||||
if (Physics.Raycast(SegmentRay, out hit, SegmentLength, combinedLayers, queryTriggerInteraction)) {
|
||||
results[0] = hit;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
return Physics.RaycastNonAlloc(SegmentRay, results, SegmentLength, combinedLayers, queryTriggerInteraction);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
11
Assets/SensorToolkit/Sensors/ArcSensor.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/ArcSensor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a705c069e7064afa9e7affdc2a1c06a1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: dc570dcc74309cd49b84904737d77fd5, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
436
Assets/SensorToolkit/Sensors/ArcSensor2D.cs
Normal file
436
Assets/SensorToolkit/Sensors/ArcSensor2D.cs
Normal file
@@ -0,0 +1,436 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
/**
|
||||
* The Arc Sensor detects objects that intersect a curve. It works just like the Ray Sensor in that it detects all
|
||||
* objects up to the first obstruction. The arc is broken up into line segments and a raycast is done on each.
|
||||
*/
|
||||
[AddComponentMenu("Sensors/2D Arc Sensor")]
|
||||
[HelpURL("https://micosmo.com/sensortoolkit2/docs/manual/sensors/arc")]
|
||||
public class ArcSensor2D : Sensor, IRayCastingSensor, IPulseRoutine {
|
||||
|
||||
#region Configurations
|
||||
public enum ParameterisationType { Bezier, Ballistic }
|
||||
|
||||
[Tooltip("Choose how the arc is represented.")]
|
||||
public ParameterisationType Parameterisation;
|
||||
|
||||
// Configurations for Bezier type arc
|
||||
public BezierCurve2D Bezier = new BezierCurve2D(new Vector2(0, 0), new Vector2(1, 0), new Vector2(1, 1), 10);
|
||||
|
||||
// Configurations for Ballistic type arc
|
||||
public BallisticCurve2D Ballistic = new BallisticCurve2D(new Vector2(5, 0), new Vector3(0, -10), 1f, 10);
|
||||
|
||||
[Tooltip("Is the curve parameterised in world space or local space.")]
|
||||
public bool WorldSpace = false;
|
||||
|
||||
[Tooltip("A layer mask specifying which physics layers objects will be detected on.")]
|
||||
public LayerMask DetectsOnLayers;
|
||||
|
||||
[Tooltip("A layer mask specifying which physics layers objects will obstruct the ray on.")]
|
||||
public LayerMask ObstructedByLayers;
|
||||
|
||||
[Tooltip("In Collider mode the sensor detects GameObjects attached to colliders. In RigidBody mode it detects the Collider.AttachedRigidbody.")]
|
||||
public DetectionModes DetectionMode;
|
||||
|
||||
[SerializeField]
|
||||
SignalFilter signalFilter = new SignalFilter();
|
||||
|
||||
[Tooltip("Ignores all trigger colliders. Will not detect them or be obstructed by them.")]
|
||||
public bool IgnoreTriggerColliders;
|
||||
|
||||
[SerializeField]
|
||||
PulseRoutine pulseRoutine = new PulseRoutine();
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
[SerializeField]
|
||||
ObstructionEvent onObstruction;
|
||||
public ObstructionEvent OnObstruction {
|
||||
get => onObstruction;
|
||||
set => onObstruction = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
ObstructionEvent onClear;
|
||||
public ObstructionEvent OnClear {
|
||||
get => onClear;
|
||||
set => onClear = value;
|
||||
}
|
||||
|
||||
public override event Action OnPulsed;
|
||||
#endregion
|
||||
|
||||
#region Public
|
||||
// Edit the IgnoreList at runtime. Anything in the list will not be detected
|
||||
public List<GameObject> IgnoreList => signalFilter.IgnoreList;
|
||||
|
||||
// Enable/Disable the tag filtering at runtime
|
||||
public bool EnableTagFilter {
|
||||
get => signalFilter.EnableTagFilter;
|
||||
set => signalFilter.EnableTagFilter = value;
|
||||
}
|
||||
|
||||
// Change the allowed tags at runtime
|
||||
public string[] AllowedTags {
|
||||
get => signalFilter.AllowedTags;
|
||||
set => signalFilter.AllowedTags = value;
|
||||
}
|
||||
|
||||
// Change the pulse mode at runtime
|
||||
public PulseRoutine.Modes PulseMode {
|
||||
get => pulseRoutine.Mode.Value;
|
||||
set => pulseRoutine.Mode.Value = value;
|
||||
}
|
||||
|
||||
// Change the pulse interval at runtime
|
||||
public float PulseInterval {
|
||||
get => pulseRoutine.Interval.Value;
|
||||
set => pulseRoutine.Interval.Value = value;
|
||||
}
|
||||
|
||||
// Change at runtime if the sensor will pulse in Update or FixedUpdate
|
||||
public PulseRoutine.UpdateFunctions PulseUpdateFunction {
|
||||
get => pulseRoutine.UpdateFunction;
|
||||
set => pulseRoutine.UpdateFunction = value;
|
||||
}
|
||||
|
||||
// The array size allocated for storing results from Physics.RaycastNonAlloc. Will automatically
|
||||
// be doubled in size when more space is needed.
|
||||
public int CurrentBufferSize => physics != null ? physics.Buffer.Length : 0;
|
||||
|
||||
// Returns true if the ray sensor is being obstructed and false otherwise
|
||||
public bool IsObstructed => GetObstructionRayHit().IsObstructing;
|
||||
|
||||
public List<Vector2> UnobstructedArcPoints => arcPoints;
|
||||
public List<Vector2> ObstructedArcPoints => obstructedArcPoints;
|
||||
|
||||
public override void PulseAll() => Pulse();
|
||||
|
||||
public override void Clear() {
|
||||
base.Clear();
|
||||
clearDetectedObjects();
|
||||
SendObstructionEvents();
|
||||
SampleArc();
|
||||
}
|
||||
|
||||
// Returns the RayHit data associated with the detected GameObject
|
||||
public RayHit GetDetectionRayHit(GameObject detectedGameObject) {
|
||||
var result = RayHit.None;
|
||||
goWorkList.Clear();
|
||||
signalPipeline.GetInputObjects(detectedGameObject, goWorkList);
|
||||
foreach (var input in goWorkList) {
|
||||
RaycastHit2D hit;
|
||||
if (detectedObjectHits.TryGetValue(input, out hit)) {
|
||||
if (result.Equals(RayHit.None) || result.Distance > hit.distance) {
|
||||
result = new RayHit() {
|
||||
IsObstructing = false,
|
||||
Point = hit.point,
|
||||
Normal = hit.normal,
|
||||
Distance = hit.distance,
|
||||
DistanceFraction = hit.distance / totalLength,
|
||||
Collider2D = hit.collider
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns the RayHit data associated with the obstructing GameObject
|
||||
public RayHit GetObstructionRayHit() {
|
||||
if (!isObstructed || obstructionRayHit.collider == null) {
|
||||
return RayHit.None;
|
||||
}
|
||||
return new RayHit {
|
||||
IsObstructing = true,
|
||||
Point = obstructionRayHit.point,
|
||||
Normal = obstructionRayHit.normal,
|
||||
Distance = obstructionRayHit.distance,
|
||||
DistanceFraction = obstructionRayHit.distance / totalLength,
|
||||
Collider2D = obstructionRayHit.collider
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internals
|
||||
List<Vector2> arcPoints = new List<Vector2>();
|
||||
List<Vector2> obstructedArcPoints = new List<Vector2>();
|
||||
|
||||
bool isObstructed = false;
|
||||
RaycastHit2D obstructionRayHit;
|
||||
Dictionary<GameObject, RaycastHit2D> detectedObjectHits = new Dictionary<GameObject, RaycastHit2D>();
|
||||
List<Signal> workList = new List<Signal>();
|
||||
float totalLength = 0f;
|
||||
List<GameObject> goWorkList = new List<GameObject>();
|
||||
|
||||
static ArcSegmentTest arcSegmentTestInstance = new ArcSegmentTest();
|
||||
PhysicsNonAlloc<RaycastHit2D> physics;
|
||||
|
||||
protected override PulseJob GetPulseJob() {
|
||||
if (physics == null) {
|
||||
physics = new PhysicsNonAlloc<RaycastHit2D>();
|
||||
}
|
||||
|
||||
MapToRigidBody.Configure(DetectionMode, true);
|
||||
|
||||
TestArc();
|
||||
|
||||
UpdateAllSignals(workList);
|
||||
SendObstructionEvents();
|
||||
|
||||
OnPulsed?.Invoke();
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
protected override void InitialiseSignalProcessors() {
|
||||
base.InitialiseSignalProcessors();
|
||||
MapToSignalProxy.Configure(true);
|
||||
SignalFilter = signalFilter;
|
||||
}
|
||||
|
||||
protected override List<Collider2D> GetInputColliders(GameObject InputObject, List<Collider2D> storeIn) {
|
||||
RaycastHit2D hit;
|
||||
if (detectedObjectHits.TryGetValue(InputObject, out hit)) {
|
||||
storeIn.Add(hit.collider);
|
||||
}
|
||||
return storeIn;
|
||||
}
|
||||
|
||||
Coroutine StartEachFrame(IEnumerator routine) => StartCoroutine(PulseEachFrame(routine));
|
||||
IEnumerator PulseEachFrame(IEnumerator routine) {
|
||||
while (routine.MoveNext()) {
|
||||
yield return routine.Current;
|
||||
}
|
||||
}
|
||||
Coroutine StartFixedInterval(IEnumerator routine) => StartCoroutine(PulseFixedInterval(routine));
|
||||
IEnumerator PulseFixedInterval(IEnumerator routine) {
|
||||
while (routine.MoveNext()) {
|
||||
yield return routine.Current;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void Awake() {
|
||||
base.Awake();
|
||||
|
||||
if (onObstruction == null) {
|
||||
onObstruction = new ObstructionEvent();
|
||||
}
|
||||
|
||||
if (onClear == null) {
|
||||
onClear = new ObstructionEvent();
|
||||
}
|
||||
|
||||
if (pulseRoutine == null) {
|
||||
pulseRoutine = new PulseRoutine();
|
||||
}
|
||||
pulseRoutine.Awake(this, StartEachFrame, StartFixedInterval);
|
||||
}
|
||||
|
||||
void OnEnable() {
|
||||
clearDetectedObjects();
|
||||
pulseRoutine.OnEnable();
|
||||
}
|
||||
|
||||
protected override void OnDisable() {
|
||||
base.OnDisable();
|
||||
pulseRoutine.OnDisable();
|
||||
}
|
||||
|
||||
void OnValidate() {
|
||||
pulseRoutine?.OnValidate();
|
||||
}
|
||||
|
||||
bool isSingleResult() {
|
||||
var layerMaskIsSubsset = ((DetectsOnLayers | ObstructedByLayers) & (~ObstructedByLayers)) == 0;
|
||||
return layerMaskIsSubsset && signalFilter.IsNull();
|
||||
}
|
||||
|
||||
List<RaycastHit2D> hits = new List<RaycastHit2D>();
|
||||
void TestArc() {
|
||||
clearDetectedObjects();
|
||||
|
||||
var saveQHT = Physics2D.queriesHitTriggers;
|
||||
Physics2D.queriesHitTriggers = !IgnoreTriggerColliders; // Must keep this, Raycast with single result doesn't support ContactFilter
|
||||
SampleArc();
|
||||
Physics2D.queriesHitTriggers = saveQHT;
|
||||
|
||||
var accLength = 0f;
|
||||
var segmentIsObstructed = false;
|
||||
for (var i = 0; i < arcPoints.Count - 1; i++) {
|
||||
var p1 = arcPoints[i];
|
||||
var p2 = arcPoints[i + 1];
|
||||
var delta = p2 - p1;
|
||||
var length = delta.magnitude;
|
||||
var dir = delta / length;
|
||||
|
||||
arcSegmentTestInstance.SegmentRay = new Ray(p1, dir);
|
||||
arcSegmentTestInstance.SegmentLength = length;
|
||||
var numberOfHits = physics.PerformTest(this, arcSegmentTestInstance);
|
||||
|
||||
hits.Clear();
|
||||
for (int j = 0; j < numberOfHits; j++) {
|
||||
var hit = physics.Buffer[j];
|
||||
hit.distance += accLength;
|
||||
hits.Add(hit);
|
||||
}
|
||||
|
||||
hits.Sort(RaycastHitComparison);
|
||||
|
||||
foreach (var hit in hits) {
|
||||
if ((1 << hit.collider.gameObject.layer & DetectsOnLayers) != 0) {
|
||||
if (signalFilter.TestCollider(hit.collider)) {
|
||||
addRayHit(hit);
|
||||
}
|
||||
}
|
||||
if ((1 << hit.collider.gameObject.layer & ObstructedByLayers) != 0) {
|
||||
if (signalFilter.TestCollider(hit.collider)) {
|
||||
obstructionRayHit = hit;
|
||||
segmentIsObstructed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (segmentIsObstructed) {
|
||||
var obspoint = obstructionRayHit.point;
|
||||
obstructedArcPoints.Add(obspoint);
|
||||
for (var j = i + 1; j < arcPoints.Count; j++) {
|
||||
obstructedArcPoints.Add(arcPoints[j]);
|
||||
}
|
||||
arcPoints.RemoveRange(i + 1, obstructedArcPoints.Count - 1);
|
||||
arcPoints.Add(obspoint);
|
||||
break;
|
||||
}
|
||||
|
||||
accLength += length;
|
||||
}
|
||||
}
|
||||
|
||||
void SampleArc() {
|
||||
obstructedArcPoints.Clear();
|
||||
|
||||
if (Parameterisation == ParameterisationType.Bezier) {
|
||||
arcPoints = Bezier.Sample(
|
||||
transform.position,
|
||||
WorldSpace ? Quaternion.identity : transform.rotation,
|
||||
arcPoints
|
||||
);
|
||||
} else if (Parameterisation == ParameterisationType.Ballistic) {
|
||||
arcPoints = Ballistic.Sample(
|
||||
transform.position,
|
||||
WorldSpace ? Quaternion.identity : transform.rotation,
|
||||
arcPoints
|
||||
);
|
||||
}
|
||||
|
||||
totalLength = 0f;
|
||||
for (var i = 0; i < arcPoints.Count - 1; i++) {
|
||||
totalLength += (arcPoints[i + 1] - arcPoints[i]).magnitude;
|
||||
}
|
||||
}
|
||||
|
||||
void SendObstructionEvents() {
|
||||
if (isObstructed && obstructionRayHit.collider == null) {
|
||||
isObstructed = false;
|
||||
OnClear.Invoke(this);
|
||||
} else if (!isObstructed && obstructionRayHit.collider != null) {
|
||||
isObstructed = true;
|
||||
OnObstruction.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
void addRayHit(RaycastHit2D hit) {
|
||||
var go = hit.collider.gameObject;
|
||||
if (!detectedObjectHits.ContainsKey(go)) {
|
||||
detectedObjectHits.Add(go, hit);
|
||||
workList.Add(new Signal() {
|
||||
Object = go,
|
||||
Strength = 1f,
|
||||
Bounds = new Bounds(hit.point, Vector3.zero)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void clearDetectedObjects() {
|
||||
obstructionRayHit = new RaycastHit2D();
|
||||
detectedObjectHits.Clear();
|
||||
workList.Clear();
|
||||
}
|
||||
|
||||
static Comparison<RaycastHit2D> RaycastHitComparison = new Comparison<RaycastHit2D>(CompareRaycastHits);
|
||||
|
||||
static int CompareRaycastHits(RaycastHit2D x, RaycastHit2D y) {
|
||||
if (x.distance < y.distance) {
|
||||
return -1;
|
||||
} else if (x.distance > y.distance) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected override void OnDrawGizmosSelected() {
|
||||
//base.OnDrawGizmosSelected();
|
||||
|
||||
if (!IsRunning()) {
|
||||
SampleArc();
|
||||
}
|
||||
|
||||
if (IsObstructed && IsRunning()) {
|
||||
Gizmos.color = STPrefs.CastingBlockedRayColour;
|
||||
} else {
|
||||
Gizmos.color = STPrefs.CastingRayColour;
|
||||
}
|
||||
for (int i = 0; i < arcPoints.Count - 1; i++) {
|
||||
var p1 = arcPoints[i];
|
||||
var p2 = arcPoints[i + 1];
|
||||
Gizmos.DrawLine(p1, p2);
|
||||
}
|
||||
|
||||
if (ShowDetectionGizmos) {
|
||||
foreach (var detection in GetDetections()) {
|
||||
var hit = GetDetectionRayHit(detection);
|
||||
SensorGizmos.RaycastHitGizmo(hit.Point, hit.Normal, false);
|
||||
}
|
||||
if (IsObstructed) {
|
||||
SensorGizmos.RaycastHitGizmo(GetObstructionRayHit().Point, GetObstructionRayHit().Normal, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IsRunning() {
|
||||
return Application.isPlaying || ShowDetectionGizmos;
|
||||
}
|
||||
|
||||
class ArcSegmentTest : ITestNonAlloc<ArcSensor2D, RaycastHit2D> {
|
||||
public Ray SegmentRay;
|
||||
public float SegmentLength;
|
||||
|
||||
public int Test(ArcSensor2D sensor, RaycastHit2D[] results) {
|
||||
var queryTriggerInteraction = sensor.IgnoreTriggerColliders ? QueryTriggerInteraction.Ignore : QueryTriggerInteraction.Collide;
|
||||
LayerMask combinedLayers = sensor.DetectsOnLayers | sensor.ObstructedByLayers;
|
||||
if (sensor.isSingleResult()) {
|
||||
var hit = Physics2D.Raycast(SegmentRay.origin, SegmentRay.direction, SegmentLength, combinedLayers);
|
||||
if (hit.collider != null) {
|
||||
results[0] = hit;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
var filter = new ContactFilter2D {
|
||||
useLayerMask = true,
|
||||
layerMask = combinedLayers,
|
||||
useTriggers = !sensor.IgnoreTriggerColliders,
|
||||
};
|
||||
return Physics2D.Raycast(SegmentRay.origin, SegmentRay.direction, filter, results, SegmentLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
11
Assets/SensorToolkit/Sensors/ArcSensor2D.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/ArcSensor2D.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 425a559dad984a39ba714c30f4f07a4c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: b98859db70782b341b422023ea3f3375, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
211
Assets/SensorToolkit/Sensors/BooleanSensor.cs
Normal file
211
Assets/SensorToolkit/Sensors/BooleanSensor.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
|
||||
/**
|
||||
* The Boolean Sensor is another Compound Sensor that combines the signals of it's input sensors. It merges the
|
||||
* signals of each sensor via logical And or logical Or.
|
||||
*/
|
||||
[AddComponentMenu("Sensors/Boolean Sensor")]
|
||||
[HelpURL("https://micosmo.com/sensortoolkit2/docs/manual/sensors/boolean")]
|
||||
public class BooleanSensor : Sensor {
|
||||
|
||||
#region Configurations
|
||||
public enum OperationType { And, Or, ExclusiveOr }
|
||||
|
||||
[System.Serializable]
|
||||
public class ObservableOperationType : Observable<OperationType> { }
|
||||
|
||||
[Tooltip("The list of input sensors. Changing the list will cause this sensor to immediately re-evaluate it's output signals.")]
|
||||
public ObservableSensorList InputSensors = new ObservableSensorList();
|
||||
|
||||
[SerializeField]
|
||||
ObservableOperationType operation = new ObservableOperationType() { Value = OperationType.And };
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
public override event System.Action OnPulsed;
|
||||
#endregion
|
||||
|
||||
#region Public
|
||||
// Change the operation type at runtime
|
||||
public OperationType Operation {
|
||||
get => operation.Value;
|
||||
set => operation.Value = value;
|
||||
}
|
||||
|
||||
public override void PulseAll() {
|
||||
foreach (var sensor in InputSensors) {
|
||||
if (sensor != null) {
|
||||
sensor.PulseAll();
|
||||
}
|
||||
}
|
||||
Pulse();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internals
|
||||
Dictionary<GameObject, int> detectionCounts = new Dictionary<GameObject, int>();
|
||||
List<Signal> workList = new List<Signal>();
|
||||
int sensorCount;
|
||||
|
||||
// In reality you shouldn't need to pulse this sensor manually. It subscribes to the OnSignalChange events of
|
||||
// the input sensors and updates as soon as the input sensors update.
|
||||
protected override PulseJob GetPulseJob() {
|
||||
RedoFromScratch();
|
||||
|
||||
workList.Clear();
|
||||
foreach (var detectionCount in detectionCounts) {
|
||||
if (SatisfiesBooleanOp(detectionCount.Value)) {
|
||||
workList.Add(CombineInputSignals(detectionCount.Key));
|
||||
}
|
||||
}
|
||||
|
||||
UpdateAllSignals(workList);
|
||||
|
||||
OnPulsed?.Invoke();
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
protected override void Awake() {
|
||||
base.Awake();
|
||||
|
||||
InputSensors.OnChanged += InputsChangedHandler;
|
||||
InputSensors.ItemAdded += SensorAddedHandler;
|
||||
InputSensors.ItemRemoved += SensorRemovedHandler;
|
||||
operation.OnChanged += InputsChangedHandler;
|
||||
|
||||
foreach (var sensor in InputSensors) {
|
||||
SensorAddedHandler(sensor);
|
||||
}
|
||||
}
|
||||
|
||||
void OnDestroy() {
|
||||
InputSensors.OnChanged -= InputsChangedHandler;
|
||||
InputSensors.ItemAdded -= SensorAddedHandler;
|
||||
InputSensors.ItemRemoved -= SensorRemovedHandler;
|
||||
operation.OnChanged -= InputsChangedHandler;
|
||||
|
||||
foreach (var sensor in InputSensors) {
|
||||
SensorRemovedHandler(sensor);
|
||||
}
|
||||
}
|
||||
|
||||
void OnValidate() {
|
||||
if (InputSensors != null) {
|
||||
InputSensors.OnValidate();
|
||||
}
|
||||
if (operation != null) {
|
||||
operation.OnValidate();
|
||||
}
|
||||
}
|
||||
|
||||
void SensorAddedHandler(Sensor sensor) {
|
||||
if (sensor != null) {
|
||||
sensor.OnDetected.AddListener(OnDetectionHandler);
|
||||
sensor.OnLostDetection.AddListener(OnLostDetectionHandler);
|
||||
sensor.OnSignalChanged += OnSignalChangedHandler;
|
||||
}
|
||||
sensorCount += 1;
|
||||
}
|
||||
|
||||
void SensorRemovedHandler(Sensor sensor) {
|
||||
if (sensor != null) {
|
||||
sensor.OnDetected.RemoveListener(OnDetectionHandler);
|
||||
sensor.OnLostDetection.RemoveListener(OnLostDetectionHandler);
|
||||
sensor.OnSignalChanged -= OnSignalChangedHandler;
|
||||
}
|
||||
sensorCount -= 1;
|
||||
}
|
||||
|
||||
void OnDetectionHandler(GameObject go, Sensor sensor) {
|
||||
if (SatisfiesBooleanOp(IncrementDetectionCount(go))) {
|
||||
UpdateSignalImmediate(CombineInputSignals(go));
|
||||
}
|
||||
}
|
||||
|
||||
void OnSignalChangedHandler(Signal signal, Sensor sensor) {
|
||||
if (IsDetected(signal.Object)) {
|
||||
UpdateSignalImmediate(CombineInputSignals(signal.Object));
|
||||
}
|
||||
}
|
||||
|
||||
void OnLostDetectionHandler(GameObject go, Sensor sensor) {
|
||||
if (!SatisfiesBooleanOp(DecrementDetectionCount(go))) {
|
||||
LostSignalImmediate(go);
|
||||
}
|
||||
}
|
||||
|
||||
int IncrementDetectionCount(GameObject forObject) {
|
||||
var n = 0;
|
||||
detectionCounts.TryGetValue(forObject, out n);
|
||||
n += 1;
|
||||
detectionCounts[forObject] = n;
|
||||
return n;
|
||||
}
|
||||
|
||||
int DecrementDetectionCount(GameObject forObject) {
|
||||
var n = detectionCounts[forObject];
|
||||
n -= 1;
|
||||
if (n > 0) {
|
||||
detectionCounts[forObject] = n;
|
||||
} else {
|
||||
detectionCounts.Remove(forObject);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
Signal CombineInputSignals(GameObject forObject) {
|
||||
Signal signal;
|
||||
Signal combinedSignal = new Signal(forObject);
|
||||
bool isFirst = true;
|
||||
foreach (var input in InputSensors) {
|
||||
if (input.TryGetSignal(forObject, out signal)) {
|
||||
if (isFirst) {
|
||||
combinedSignal.Shape = signal.Shape;
|
||||
isFirst = false;
|
||||
} else {
|
||||
combinedSignal.Shape.Encapsulate(signal.Shape);
|
||||
}
|
||||
combinedSignal.Strength = Mathf.Max(combinedSignal.Strength, signal.Strength);
|
||||
}
|
||||
}
|
||||
return combinedSignal;
|
||||
}
|
||||
|
||||
void InputsChangedHandler() {
|
||||
Pulse();
|
||||
}
|
||||
|
||||
void RedoFromScratch() {
|
||||
detectionCounts.Clear();
|
||||
|
||||
sensorCount = 0;
|
||||
|
||||
foreach (var sensor in InputSensors) {
|
||||
if (sensor == null) {
|
||||
continue;
|
||||
}
|
||||
sensorCount += 1;
|
||||
foreach (var detection in sensor.Detections) {
|
||||
IncrementDetectionCount(detection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SatisfiesBooleanOp(int n) {
|
||||
if (Operation == OperationType.And) {
|
||||
return n == sensorCount;
|
||||
} else if (Operation == OperationType.Or) {
|
||||
return n > 0;
|
||||
} else {
|
||||
return n == 1;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
11
Assets/SensorToolkit/Sensors/BooleanSensor.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/BooleanSensor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f53f8d119af4887ab3ce0bfbba6fc45
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 456a677bfd32e404b8990d9a71d155b5, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
164
Assets/SensorToolkit/Sensors/FOVCollider.cs
Normal file
164
Assets/SensorToolkit/Sensors/FOVCollider.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace Micosmo.SensorToolkit
|
||||
{
|
||||
/*
|
||||
* A paramemtric shape for creating field of view cones that work with the trigger sensor. Requires a MeshCollider
|
||||
* component on the same gameobject. When the script starts it will dynamically create a mesh for the fov cone and
|
||||
* assign it to this MeshCollider component.
|
||||
*/
|
||||
[RequireComponent(typeof(MeshCollider))]
|
||||
[ExecuteInEditMode]
|
||||
[HelpURL("https://micosmo.com/sensortoolkit2/docs/manual/fov")]
|
||||
public class FOVCollider : MonoBehaviour {
|
||||
[Tooltip("The length of the field of view cone in world units.")]
|
||||
public float Length = 5f;
|
||||
|
||||
[Tooltip("The distance to the near plane of the frustum.")]
|
||||
public float NearDistance = 0.1f;
|
||||
|
||||
[Range(1f, 180f), Tooltip("The arc angle of the fov cone.")]
|
||||
public float FOVAngle = 90f;
|
||||
|
||||
[Range(1f, 180f), Tooltip("The elevation angle of the cone.")]
|
||||
public float ElevationAngle = 90f;
|
||||
|
||||
[Range(0, 8), Tooltip("The number of vertices used to approximate the arc of the fov cone. Ideally this should be as low as possible.")]
|
||||
public int Resolution = 0;
|
||||
|
||||
// Returns the generated collider mesh so that it can be rendered.
|
||||
public Mesh FOVMesh => mesh;
|
||||
|
||||
Mesh mesh;
|
||||
MeshCollider mc;
|
||||
Vector3[] pts;
|
||||
int[] triangles;
|
||||
|
||||
void Awake() {
|
||||
mc = GetComponent<MeshCollider>();
|
||||
CreateCollider();
|
||||
}
|
||||
|
||||
void OnValidate() {
|
||||
Length = Mathf.Max(0f, Length);
|
||||
NearDistance = Mathf.Clamp(NearDistance, 0f, Length);
|
||||
if (mc != null) {
|
||||
CreateCollider();
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateCollider() {
|
||||
pts = new Vector3[4 + (2+Resolution)*(2+Resolution)];
|
||||
// There are 2 triangles on the base
|
||||
var baseTriangleIndices = 2 * 3;
|
||||
// The arc is (Resolution+2) vertices to each side, making (Resolution+1)*(Resolution+1) boxes of 2 tris each
|
||||
var arcTriangleIndices = (Resolution + 1) * (Resolution + 1) * 2 * 3;
|
||||
// There are 4 sides to the cone, and each side has Resolution+2 triangles
|
||||
var sideTriangleIndices = (Resolution + 2) * 3;
|
||||
triangles = new int[baseTriangleIndices + arcTriangleIndices + sideTriangleIndices*4];
|
||||
|
||||
// Base points
|
||||
pts[0] = Quaternion.Euler(-ElevationAngle / 2f, -FOVAngle / 2f, 0f) * Vector3.forward * NearDistance; // Top Left
|
||||
pts[1] = Quaternion.Euler(ElevationAngle / 2f, -FOVAngle / 2f, 0f) * Vector3.forward * NearDistance; // Bottom Left
|
||||
pts[2] = Quaternion.Euler(ElevationAngle / 2f, FOVAngle / 2f, 0f) * Vector3.forward * NearDistance; // Bottom Right
|
||||
pts[3] = Quaternion.Euler(-ElevationAngle / 2f, FOVAngle / 2f, 0f) * Vector3.forward * NearDistance; // Top Right
|
||||
triangles[0] = 2; triangles[1] = 1; triangles[2] = 0; triangles[3] = 3; triangles[4] = 2; triangles[5] = 0;
|
||||
|
||||
for (int y = 0; y < 2+Resolution; y++) {
|
||||
for (int x = 0; x < 2+Resolution; x++) {
|
||||
int i = 4 + y * (2 + Resolution) + x;
|
||||
float ay = Mathf.Lerp(-FOVAngle / 2f, FOVAngle / 2f, (float)x / (float)(Resolution + 1));
|
||||
float ax = Mathf.Lerp(-ElevationAngle / 2f, ElevationAngle / 2f, (float)y / (float)(Resolution + 1));
|
||||
Vector3 p = Quaternion.Euler(ax, ay, 0f) * Vector3.forward * Length;
|
||||
pts[i] = p;
|
||||
|
||||
if (x < (1+Resolution) && y < (1+Resolution)) {
|
||||
var ti = baseTriangleIndices + (y * (Resolution + 1) + x) * 3 * 2;
|
||||
triangles[ti] = i + 1 + (2 + Resolution); // top right
|
||||
triangles[ti + 1] = i + 1; // bottom right
|
||||
triangles[ti + 2] = i; // bottom left
|
||||
triangles[ti + 3] = i + (2 + Resolution); // top left
|
||||
triangles[ti + 4] = i + (2 + Resolution) + 1; // top right
|
||||
triangles[ti + 5] = i; // bottom left
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Top and bottom side triangles
|
||||
for (int x = 0; x < 2+Resolution; x++) {
|
||||
var iTop = 4 + x;
|
||||
var iBottom = 4 + (1 + Resolution) * (2 + Resolution) + x;
|
||||
|
||||
var tiTop = baseTriangleIndices + arcTriangleIndices + x*3;
|
||||
var tiBottom = tiTop + sideTriangleIndices;
|
||||
if (x == 0) {
|
||||
triangles[tiTop] = 3;
|
||||
triangles[tiTop+1] = 0;
|
||||
triangles[tiTop + 2] = iTop;
|
||||
|
||||
triangles[tiBottom] = 1;
|
||||
triangles[tiBottom + 1] = 2;
|
||||
triangles[tiBottom + 2] = iBottom;
|
||||
} else {
|
||||
triangles[tiTop] = iTop;
|
||||
triangles[tiTop + 1] = 3;
|
||||
triangles[tiTop + 2] = iTop-1;
|
||||
|
||||
triangles[tiBottom] = 2;
|
||||
triangles[tiBottom + 1] = iBottom;
|
||||
triangles[tiBottom + 2] = iBottom-1;
|
||||
}
|
||||
}
|
||||
|
||||
// Left and right side triangles
|
||||
var yIncr = 2 + Resolution;
|
||||
for (int y = 0; y < 2 + Resolution; y++) {
|
||||
var iLeft = 4 + y*(2+Resolution);
|
||||
var iRight = iLeft + (1+Resolution);
|
||||
|
||||
var tiLeft = baseTriangleIndices + arcTriangleIndices + sideTriangleIndices*2 + y*3;
|
||||
var tiRight = tiLeft + sideTriangleIndices;
|
||||
if (y == 0) {
|
||||
triangles[tiLeft] = 0;
|
||||
triangles[tiLeft + 1] = 1;
|
||||
triangles[tiLeft + 2] = iLeft;
|
||||
|
||||
triangles[tiRight] = 2;
|
||||
triangles[tiRight + 1] = 3;
|
||||
triangles[tiRight + 2] = iRight;
|
||||
} else {
|
||||
triangles[tiLeft] = 1;
|
||||
triangles[tiLeft + 1] = iLeft;
|
||||
triangles[tiLeft + 2] = iLeft - yIncr;
|
||||
|
||||
triangles[tiRight] = iRight;
|
||||
triangles[tiRight + 1] = 2;
|
||||
triangles[tiRight + 2] = iRight - yIncr;
|
||||
}
|
||||
}
|
||||
|
||||
releaseMesh();
|
||||
mesh = new Mesh();
|
||||
mesh.vertices = pts;
|
||||
mesh.triangles = triangles;
|
||||
mesh.name = "FOVColliderPoints";
|
||||
mc.sharedMesh = mesh;
|
||||
mc.convex = true;
|
||||
mc.isTrigger = true;
|
||||
}
|
||||
|
||||
void releaseMesh() {
|
||||
if (mc.sharedMesh != null && mc.sharedMesh == mesh) {
|
||||
DestroyImmediate(mc.sharedMesh, true);
|
||||
}
|
||||
}
|
||||
|
||||
void OnDrawGizmosSelected() {
|
||||
Gizmos.color = Color.green;
|
||||
foreach(Vector3 p in pts) {
|
||||
Gizmos.DrawSphere(transform.TransformPoint(p), 0.02f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/SensorToolkit/Sensors/FOVCollider.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/FOVCollider.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e307e64634ea457bab5dca4083c71e14
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: f477a99c1c60b6c498b80c318fa046c8, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
95
Assets/SensorToolkit/Sensors/FOVCollider2D.cs
Normal file
95
Assets/SensorToolkit/Sensors/FOVCollider2D.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace Micosmo.SensorToolkit
|
||||
{
|
||||
/*
|
||||
* A paramemtric shape for creating field of view cones that work with the trigger sensor. Requires a PolygonCollider2D
|
||||
* component on the same gameobject. When the script starts it will dynamically create a mesh for the fov cone and
|
||||
* assign it to this PolygonCollider2D component.
|
||||
*/
|
||||
[RequireComponent(typeof(PolygonCollider2D))]
|
||||
[ExecuteInEditMode]
|
||||
[HelpURL("https://micosmo.com/sensortoolkit2/docs/manual/fov")]
|
||||
public class FOVCollider2D : MonoBehaviour {
|
||||
[Tooltip("The length of the field of view cone in world units.")]
|
||||
public float Length = 5f;
|
||||
|
||||
[Tooltip("The distance to the near plane of the frustum.")]
|
||||
public float NearDistance = 0.1f;
|
||||
|
||||
[Range(1f, 360f), Tooltip("The arc angle of the fov cone.")]
|
||||
public float FOVAngle = 90f;
|
||||
|
||||
[Range(0, 16), Tooltip("The number of vertices used to approximate the arc of the fov cone. Ideally this should be as low as possible.")]
|
||||
public int Resolution = 0;
|
||||
|
||||
// Returns the generated collider mesh so that it can be rendered.
|
||||
public Mesh FOVMesh => mesh;
|
||||
|
||||
PolygonCollider2D pc;
|
||||
Vector2[] pts;
|
||||
Mesh mesh;
|
||||
|
||||
void Awake() {
|
||||
pc = GetComponent<PolygonCollider2D>();
|
||||
CreateCollider();
|
||||
}
|
||||
|
||||
void OnValidate() {
|
||||
Length = Mathf.Max(0f, Length);
|
||||
NearDistance = Mathf.Clamp(NearDistance, 0f, Length);
|
||||
if (pc != null) {
|
||||
CreateCollider();
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateCollider() {
|
||||
pts = new Vector2[4 + Resolution];
|
||||
|
||||
// Base points
|
||||
pts[0] = Quaternion.AngleAxis(FOVAngle / 2f, Vector3.forward) * Vector2.up * NearDistance; // Bottom Left
|
||||
pts[1] = Quaternion.AngleAxis(-FOVAngle / 2f, Vector3.forward) * Vector2.up * NearDistance; // Bottom Right
|
||||
|
||||
for (int i = 0; i <= 1+Resolution; i++) {
|
||||
float a = -FOVAngle / 2f + FOVAngle * ((float)i / (1 + Resolution));
|
||||
Vector2 pt = Quaternion.AngleAxis(a, Vector3.forward) * (Vector2.up * Length);
|
||||
pts[i + 2] = pt;
|
||||
}
|
||||
|
||||
pc.points = pts;
|
||||
|
||||
// Create a mesh, in case we want to render fov cone
|
||||
var pts3D = new Vector3[4 + Resolution];
|
||||
for (int i = 0; i < pts3D.Length; i++) {
|
||||
pts3D[i] = pts[i];
|
||||
}
|
||||
|
||||
var triangles = new int[(2 + Resolution) * 3];
|
||||
for (int i = 0; i < (2+Resolution); i++) {
|
||||
var ti = i * 3;
|
||||
if (i == 0) {
|
||||
triangles[ti] = 1;
|
||||
triangles[ti + 1] = 0;
|
||||
triangles[ti + 2] = 2;
|
||||
} else {
|
||||
triangles[ti] = i+1;
|
||||
triangles[ti + 1] = 0;
|
||||
triangles[ti + 2] = i + 2;
|
||||
}
|
||||
}
|
||||
|
||||
mesh = new Mesh();
|
||||
mesh.vertices = pts3D;
|
||||
mesh.triangles = triangles;
|
||||
mesh.name = "FOV2DColliderPoints";
|
||||
}
|
||||
|
||||
void OnDrawGizmosSelected() {
|
||||
Gizmos.color = Color.green;
|
||||
foreach(Vector3 p in pts) {
|
||||
Gizmos.DrawSphere(transform.TransformPoint(p), 0.02f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/SensorToolkit/Sensors/FOVCollider2D.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/FOVCollider2D.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3bd07e63ad254b1e93d9f68dd20093b3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 23093f856ec93014bb207c831c595156, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
59
Assets/SensorToolkit/Sensors/LOSColliderOwner.cs
Normal file
59
Assets/SensorToolkit/Sensors/LOSColliderOwner.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
|
||||
/*
|
||||
* Put this component on an object to define explicitely what colliders it 'owns'. A line of sight
|
||||
* test performed on the object will not be blocked by any colliders that belong to it.
|
||||
*
|
||||
* This component will be useful for example if you're doing LOS tests on a character set up like
|
||||
* a ragdoll. If the character has Rigidbodies and Colliders on each of its limbs then those limbs
|
||||
* may block the LOS rays targeting the character. Each limb with a Rigidbody will 'own' any Colliders
|
||||
* on child objects, but the root of the character doesn't 'own' the limbs.
|
||||
*
|
||||
* To fix this you can put this component on the character and assign each limb into the list. You can
|
||||
* put either the GameObjects with the Rigidbodies or the GameObjects with the Colliders into the list,
|
||||
* either will work.
|
||||
*/
|
||||
[AddComponentMenu("Sensors/LOS Collider Owner")]
|
||||
public class LOSColliderOwner : MonoBehaviour {
|
||||
public List<GameObject> Colliders;
|
||||
|
||||
public bool IsColliderOwner(Collider c) {
|
||||
if (Colliders == null) {
|
||||
return false;
|
||||
}
|
||||
if (c == null) {
|
||||
return false;
|
||||
}
|
||||
if (Colliders.Contains(c.gameObject)) {
|
||||
return true;
|
||||
}
|
||||
var rb = c.attachedRigidbody;
|
||||
if (rb != null && Colliders.Contains(rb.gameObject)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsColliderOwner(Collider2D c) {
|
||||
if (Colliders == null) {
|
||||
return false;
|
||||
}
|
||||
if (c == null) {
|
||||
return false;
|
||||
}
|
||||
if (Colliders.Contains(c.gameObject)) {
|
||||
return true;
|
||||
}
|
||||
var rb = c.attachedRigidbody;
|
||||
if (rb != null && Colliders.Contains(rb.gameObject)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
11
Assets/SensorToolkit/Sensors/LOSColliderOwner.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/LOSColliderOwner.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de335064d8721d041a39e9a566cec492
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
353
Assets/SensorToolkit/Sensors/LOSSensor.cs
Normal file
353
Assets/SensorToolkit/Sensors/LOSSensor.cs
Normal file
@@ -0,0 +1,353 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
/**
|
||||
* The LOS Sensor detects objects that have a visibility percentage above some threshold. It determines visibility by
|
||||
* casting rays at the object and calculating the ratio that are obstructed. It is a Compound Sensor, meaning it
|
||||
* requires another sensor as input. The Signals from the input sensor are each tested for line of sight.
|
||||
*/
|
||||
[AddComponentMenu("Sensors/LOS Sensor")]
|
||||
[HelpURL("https://micosmo.com/sensortoolkit2/docs/manual/sensors/los")]
|
||||
public class LOSSensor : Sensor, IPulseRoutine {
|
||||
|
||||
#region Configurations
|
||||
[SerializeField]
|
||||
[Tooltip("A Sensor whose Signals will be tested for line of sight.")]
|
||||
ObservableSensor inputSensor = new ObservableSensor();
|
||||
|
||||
[SerializeField]
|
||||
PulseRoutine pulseRoutine = new PulseRoutine();
|
||||
|
||||
[Tooltip("Layermask for which physics layers block line of sight rays.")]
|
||||
public LayerMask BlocksLineOfSight;
|
||||
|
||||
[Tooltip("The line of sight will not be blocked by Trigger Colliders when this is true.")]
|
||||
public bool IgnoreTriggerColliders;
|
||||
|
||||
[Tooltip("How are test points randomly generated. Fast method randomly samples the Signal.Bounds volume. Quality will triangulate the bounds and slice it by the frustum planes.")]
|
||||
public PointSamplingMethod PointSamplingMethod;
|
||||
|
||||
[Tooltip("If this is true the sensor will only attempt line of sight tests on objects that have a LOSTargets component.")]
|
||||
public bool TestLOSTargetsOnly;
|
||||
|
||||
[Range(1, 20), Tooltip("The number of randomly generated raycast targets to test on each object. Does nothing if that object has a LOSTargets component.")]
|
||||
public int NumberOfRays = 1;
|
||||
|
||||
[Range(0f, 1f), Tooltip("The ratio of unobstructed raycasts must exceed this value for the object to be detected by this sensor.")]
|
||||
public float MinimumVisibility = 0.5f;
|
||||
|
||||
[Tooltip("Enables smoothing of visibility values over multiple frames.")]
|
||||
public bool MovingAverageEnabled = false;
|
||||
|
||||
[Tooltip("The number of frames to smooth visibility values over.")]
|
||||
public int MovingAverageWindowSize = 10;
|
||||
|
||||
[Tooltip("Enables the Distance Limits feature")]
|
||||
public bool LimitDistance = false;
|
||||
|
||||
[Tooltip("When LimitDistance is true an object must be within this distance for it to be detected.")]
|
||||
public float MaxDistance = 1f;
|
||||
|
||||
// A struct specifying how visibility is scaled as a function of distance. Choices are Step, Linear Decay or a Curve.
|
||||
public ScalingFunction VisibilityByDistance = ScalingFunction.Default();
|
||||
|
||||
[Tooltip("Enables the Angle Limits feature when this is true.")]
|
||||
public bool LimitViewAngle;
|
||||
|
||||
[Tooltip("When LimitViewAngle is true an object must be within this horizontal view angle to be detected.")]
|
||||
[Range(0f, 180f)]
|
||||
public float MaxHorizAngle = 45;
|
||||
|
||||
// A struct specifying how visibility is scaled as a function of horizontal view angle. Choices are Step, Linear Decay or a Curve.
|
||||
public ScalingFunction VisibilityByHorizAngle = ScalingFunction.Default();
|
||||
|
||||
[Tooltip("When LimitViewAngle is true an object must be within this vertical view angle to be detected.")]
|
||||
[Range(0f, 90f)]
|
||||
public float MaxVertAngle = 45;
|
||||
|
||||
// A struct specifying how visibility is scaled as a function of vertical view angle. Choices are Step, Linear Decay or a Curve.
|
||||
public ScalingFunction VisibilityByVertAngle = ScalingFunction.Default();
|
||||
|
||||
[Tooltip("Determines how the distance and view angle are calculated. 'BoundingBox' mode will calculate it to the nearest point on the targets Bounding Box. 'PerRay' will calculate it separately for each ray cast at the target. For most use cases you want this set to 'BoundingBox'")]
|
||||
public FOVConstraintMethod FOVConstraintMethod;
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
public override event System.Action OnPulsed;
|
||||
#endregion
|
||||
|
||||
#region Public
|
||||
// Change the input sensor at runtime
|
||||
public Sensor InputSensor {
|
||||
get => inputSensor.Value;
|
||||
set => inputSensor.Value = value;
|
||||
}
|
||||
|
||||
// Change the pulse mode at runtime
|
||||
public PulseRoutine.Modes PulseMode {
|
||||
get => pulseRoutine.Mode.Value;
|
||||
set => pulseRoutine.Mode.Value = value;
|
||||
}
|
||||
|
||||
// Change the pulse interval at runtime
|
||||
public float PulseInterval {
|
||||
get => pulseRoutine.Interval.Value;
|
||||
set => pulseRoutine.Interval.Value = value;
|
||||
}
|
||||
|
||||
// Change at runtime if the sensor will pulse in Update or FixedUpdate
|
||||
public PulseRoutine.UpdateFunctions PulseUpdateFunction {
|
||||
get => pulseRoutine.UpdateFunction;
|
||||
set => pulseRoutine.UpdateFunction = value;
|
||||
}
|
||||
|
||||
// Displays the results of line of sight tests during OnDrawGizmosSelected for objects in this set.
|
||||
// Used by the editor classes. You shouldn't need to touch this.
|
||||
public HashSet<GameObject> ShowRayCastDebug;
|
||||
|
||||
/**
|
||||
* Returns a data-object with details about the line of sight test for a given GameObject. The ILOSResult instances
|
||||
* are cached by the sensor and reused each time it pulses. Don't hold onto this reference for long, it will
|
||||
* be invalid after the next pulse.
|
||||
*/
|
||||
public ILOSResult GetResult(GameObject forObject) {
|
||||
if (losTests.TryGetValue(forObject, out var result)) {
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ILOSResult> resultsList = new List<ILOSResult>();
|
||||
// Returns a list of ILOSResult data-objects with line of sight results for all of the objects tested.
|
||||
public List<ILOSResult> GetAllResults() {
|
||||
resultsList.Clear();
|
||||
foreach (var tester in losTests) {
|
||||
resultsList.Add(tester.Value);
|
||||
}
|
||||
return resultsList;
|
||||
}
|
||||
|
||||
public override void PulseAll() {
|
||||
if (InputSensor != null) {
|
||||
InputSensor.PulseAll();
|
||||
}
|
||||
Pulse();
|
||||
}
|
||||
|
||||
public override void Clear() {
|
||||
base.Clear();
|
||||
DisposeAllLOSTests();
|
||||
}
|
||||
|
||||
List<Collider> clist = new List<Collider>();
|
||||
/**
|
||||
* Immediately test line of sight for a given signal and return the results. This gives you full control to test
|
||||
* line of sight when ever you need. Just keep in mind that the ILOSResult is stored until the next Pulse and
|
||||
* then returned to a cache.
|
||||
*/
|
||||
public ILOSResult TestSignal(Signal inputSignal) {
|
||||
clist.Clear();
|
||||
var tester = getLOSTest(inputSignal.Object);
|
||||
|
||||
var config = tester.Config;
|
||||
|
||||
config.InputSignal = inputSignal;
|
||||
config.OwnedColliders = GetInputColliders(inputSignal.Object, clist);
|
||||
config.Frame = frame;
|
||||
config.MinimumVisibility = MinimumVisibility;
|
||||
config.BlocksLineOfSight = BlocksLineOfSight;
|
||||
config.IgnoreTriggerColliders = IgnoreTriggerColliders;
|
||||
config.PointSamplingMethod = PointSamplingMethod;
|
||||
config.TestLOSTargetsOnly = TestLOSTargetsOnly;
|
||||
config.NumberOfRays = NumberOfRays;
|
||||
config.MovingAverageEnabled = MovingAverageEnabled;
|
||||
config.MovingAverageWindowSize = MovingAverageWindowSize;
|
||||
config.LimitDistance = LimitDistance;
|
||||
config.MaxDistance = MaxDistance;
|
||||
config.VisibilityByDistance = VisibilityByDistance;
|
||||
config.LimitViewAngle = LimitViewAngle;
|
||||
config.MaxHorizAngle = MaxHorizAngle;
|
||||
config.VisibilityByHorizAngle = VisibilityByHorizAngle;
|
||||
config.MaxVertAngle = MaxVertAngle;
|
||||
config.VisibilityByVertAngle = VisibilityByVertAngle;
|
||||
config.FOVConstraintMethod = FOVConstraintMethod;
|
||||
tester.PerformTest();
|
||||
|
||||
return tester;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internals
|
||||
ReferenceFrame frame => ReferenceFrame.From(transform);
|
||||
|
||||
// Maps a GameObject to a list of raycast target positions for calculating line of sight
|
||||
Dictionary<GameObject, LOSTest3D> losTests = new Dictionary<GameObject, LOSTest3D>();
|
||||
|
||||
Dictionary<GameObject, LOSTest3D> prevLosTests = new Dictionary<GameObject, LOSTest3D>();
|
||||
|
||||
List<Signal> workList = new List<Signal>();
|
||||
|
||||
static ObjectCache<LOSTest3D> losTestCache = new ObjectCache<LOSTest3D>();
|
||||
|
||||
// Recalculates line of sight visibility for each Signal in the input sensor.
|
||||
protected override PulseJob GetPulseJob() {
|
||||
SwapLosTestBuffers();
|
||||
|
||||
workList.Clear();
|
||||
|
||||
if (InputSensor != null) {
|
||||
foreach (var signal in InputSensor.Signals) {
|
||||
var result = TestSignal(signal);
|
||||
if (result.IsVisible) {
|
||||
workList.Add(result.OutputSignal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UpdateAllSignals(workList);
|
||||
OnPulsed?.Invoke();
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
protected override List<Collider> GetInputColliders(GameObject inputObject, List<Collider> storeIn)
|
||||
=> InputSensor.GetDetectedColliders(inputObject, storeIn);
|
||||
|
||||
Coroutine StartEachFrame(IEnumerator routine) => StartCoroutine(PulseEachFrame(routine));
|
||||
IEnumerator PulseEachFrame(IEnumerator routine) {
|
||||
while (routine.MoveNext()) {
|
||||
yield return routine.Current;
|
||||
}
|
||||
}
|
||||
Coroutine StartFixedInterval(IEnumerator routine) => StartCoroutine(PulseFixedInterval(routine));
|
||||
IEnumerator PulseFixedInterval(IEnumerator routine) {
|
||||
while (routine.MoveNext()) {
|
||||
yield return routine.Current;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void Awake() {
|
||||
base.Awake();
|
||||
|
||||
if (inputSensor == null) {
|
||||
inputSensor = new ObservableSensor();
|
||||
}
|
||||
|
||||
if (pulseRoutine == null) {
|
||||
pulseRoutine = new PulseRoutine();
|
||||
}
|
||||
pulseRoutine.Awake(this, StartEachFrame, StartFixedInterval);
|
||||
}
|
||||
|
||||
protected virtual void OnEnable() {
|
||||
inputSensor.OnChangedValues += InputSensorChangedHandler;
|
||||
InputSensorChangedHandler(null, InputSensor);
|
||||
pulseRoutine.OnEnable();
|
||||
}
|
||||
|
||||
protected override void OnDisable() {
|
||||
base.OnDisable();
|
||||
inputSensor.OnChangedValues -= InputSensorChangedHandler;
|
||||
pulseRoutine.OnDisable();
|
||||
}
|
||||
|
||||
protected virtual void OnValidate() {
|
||||
inputSensor?.OnValidate();
|
||||
pulseRoutine?.OnValidate();
|
||||
}
|
||||
|
||||
void InputSensorChangedHandler(Sensor prev, Sensor next) {
|
||||
if (prev != null) {
|
||||
prev.OnDetected.RemoveListener(InputOnDetectedHandler);
|
||||
prev.OnLostDetection.RemoveListener(InputLostDetectionHandler);
|
||||
}
|
||||
if (next != null) {
|
||||
next.OnDetected.AddListener(InputOnDetectedHandler);
|
||||
next.OnLostDetection.AddListener(InputLostDetectionHandler);
|
||||
}
|
||||
}
|
||||
|
||||
void InputOnDetectedHandler(GameObject detection, Sensor sensor) {
|
||||
var signal = sensor.GetSignal(detection);
|
||||
var results = TestSignal(signal);
|
||||
if (results.IsVisible) {
|
||||
UpdateSignalImmediate(results.OutputSignal);
|
||||
}
|
||||
}
|
||||
|
||||
void InputLostDetectionHandler(GameObject lost, Sensor sensor) {
|
||||
DisposeLOSTest(lost);
|
||||
LostSignalImmediate(lost);
|
||||
}
|
||||
|
||||
void SwapLosTestBuffers() {
|
||||
var temp = prevLosTests;
|
||||
prevLosTests = losTests;
|
||||
losTests = temp;
|
||||
DisposeAllLOSTests();
|
||||
}
|
||||
|
||||
void DisposeLOSTest(GameObject go) {
|
||||
LOSTest3D losTest;
|
||||
if (losTests.TryGetValue(go, out losTest)) {
|
||||
losTestCache.Dispose(losTest);
|
||||
losTests.Remove(go);
|
||||
}
|
||||
}
|
||||
|
||||
void DisposeAllLOSTests() {
|
||||
var losTestEnumerator = losTests.GetEnumerator();
|
||||
while (losTestEnumerator.MoveNext()) {
|
||||
var losTest = losTestEnumerator.Current.Value;
|
||||
losTestCache.Dispose(losTest);
|
||||
}
|
||||
losTests.Clear();
|
||||
}
|
||||
|
||||
LOSTest3D getLOSTest(GameObject go) {
|
||||
LOSTest3D losTest;
|
||||
if (prevLosTests.TryGetValue(go, out losTest)) {
|
||||
losTests[go] = losTest;
|
||||
prevLosTests.Remove(go);
|
||||
return losTest;
|
||||
} else if (losTests.TryGetValue(go, out losTest)) {
|
||||
return losTest;
|
||||
} else {
|
||||
losTest = losTestCache.Get();
|
||||
losTest.Reset();
|
||||
losTests[go] = losTest;
|
||||
}
|
||||
return losTest;
|
||||
}
|
||||
|
||||
protected override void OnDrawGizmosSelected() {
|
||||
base.OnDrawGizmosSelected();
|
||||
|
||||
if (LimitDistance && !LimitViewAngle) {
|
||||
SensorGizmos.PushColor(STPrefs.LOSFovColour);
|
||||
SensorGizmos.SphereGizmo(transform.position, MaxDistance);
|
||||
SensorGizmos.PopColor();
|
||||
}
|
||||
|
||||
if (LimitViewAngle) {
|
||||
FOVRange.Of(MaxHorizAngle, MaxVertAngle, LimitDistance ? MaxDistance : float.PositiveInfinity).DrawGizmos(frame);
|
||||
}
|
||||
|
||||
if (!ShowDetectionGizmos || ShowRayCastDebug == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var debug in ShowRayCastDebug) {
|
||||
LOSTest3D test;
|
||||
if (losTests.TryGetValue(debug, out test)) {
|
||||
test.DrawGizmos();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
11
Assets/SensorToolkit/Sensors/LOSSensor.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/LOSSensor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 37d0f18f006f45eabdf63f3b9421bdb8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: a8328f113a33e1e43960ab1a1d2ee490, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
345
Assets/SensorToolkit/Sensors/LOSSensor2D.cs
Normal file
345
Assets/SensorToolkit/Sensors/LOSSensor2D.cs
Normal file
@@ -0,0 +1,345 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
/**
|
||||
* The LOS Sensor detects objects that have a visibility percentage above some threshold. It determines visibility by
|
||||
* casting rays at the object and calculating the ratio that are obstructed. It is a Compound Sensor, meaning it
|
||||
* requires another sensor as input. The Signals from the input sensor are each tested for line of sight.
|
||||
*/
|
||||
[AddComponentMenu("Sensors/2D LOS Sensor")]
|
||||
[HelpURL("https://micosmo.com/sensortoolkit2/docs/manual/sensors/los")]
|
||||
public class LOSSensor2D : Sensor, IPulseRoutine {
|
||||
|
||||
#region Configurations
|
||||
[SerializeField]
|
||||
[Tooltip("A Sensor whose Signals will be tested for line of sight.")]
|
||||
ObservableSensor inputSensor = new ObservableSensor();
|
||||
|
||||
[SerializeField]
|
||||
PulseRoutine pulseRoutine = new PulseRoutine();
|
||||
|
||||
[Tooltip("Layermask for which physics layers block line of sight rays.")]
|
||||
public LayerMask BlocksLineOfSight;
|
||||
|
||||
[Tooltip("The line of sight will not be blocked by Trigger Colliders when this is true.")]
|
||||
public bool IgnoreTriggerColliders;
|
||||
|
||||
[Tooltip("How are test points randomly generated. Fast method randomly samples the Signal.Bounds volume. Quality will triangulate the bounds and slice it by the frustum planes.")]
|
||||
public PointSamplingMethod PointSamplingMethod;
|
||||
|
||||
[Tooltip("If this is true the sensor will only attempt line of sight tests on objects that have a LOSTargets component.")]
|
||||
public bool TestLOSTargetsOnly;
|
||||
|
||||
[Range(1, 20), Tooltip("The number of randomly generated raycast targets to test on each object. Does nothing if that object has a LOSTargets component.")]
|
||||
public int NumberOfRays = 1;
|
||||
|
||||
[Range(0f, 1f), Tooltip("The ratio of unobstructed raycasts must exceed this value for the object to be detected by this sensor.")]
|
||||
public float MinimumVisibility = 0.5f;
|
||||
|
||||
[Tooltip("Enables smoothing of visibility values over multiple frames.")]
|
||||
public bool MovingAverageEnabled = false;
|
||||
|
||||
[Tooltip("The number of frames to smooth visibility values over.")]
|
||||
public int MovingAverageWindowSize = 10;
|
||||
|
||||
[Tooltip("Enables the Distance Limits feature")]
|
||||
public bool LimitDistance = false;
|
||||
|
||||
[Tooltip("When LimitDistance is true an object must be within this distance for it to be detected.")]
|
||||
public float MaxDistance = 1f;
|
||||
|
||||
// A struct specifying how visibility is scaled as a function of distance. Choices are Step, Linear Decay or a Curve.
|
||||
public ScalingFunction VisibilityByDistance = ScalingFunction.Default();
|
||||
|
||||
[Tooltip("Enables the Angle Limits feature when this is true.")]
|
||||
public bool LimitViewAngle;
|
||||
|
||||
[Tooltip("When LimitViewAngle is true an object must be within this horizontal view angle to be detected.")]
|
||||
[Range(0f, 180f)]
|
||||
public float MaxViewAngle = 45;
|
||||
|
||||
// A struct specifying how visibility is scaled as a function of horizontal view angle. Choices are Step, Linear Decay or a Curve.
|
||||
public ScalingFunction VisibilityByViewAngle = ScalingFunction.Default();
|
||||
|
||||
[Tooltip("Determines how the distance and view angle are calculated. 'BoundingBox' mode will calculate it to the nearest point on the targets Bounding Box. 'PerRay' will calculate it separately for each ray cast at the target. For most use cases you want this set to 'BoundingBox'")]
|
||||
public FOVConstraintMethod FOVConstraintMethod;
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
public override event System.Action OnPulsed;
|
||||
#endregion
|
||||
|
||||
#region Public
|
||||
// Change the input sensor at runtime
|
||||
public Sensor InputSensor {
|
||||
get => inputSensor.Value;
|
||||
set => inputSensor.Value = value;
|
||||
}
|
||||
|
||||
// Change the pulse mode at runtime
|
||||
public PulseRoutine.Modes PulseMode {
|
||||
get => pulseRoutine.Mode.Value;
|
||||
set => pulseRoutine.Mode.Value = value;
|
||||
}
|
||||
|
||||
// Change the pulse interval at runtime
|
||||
public float PulseInterval {
|
||||
get => pulseRoutine.Interval.Value;
|
||||
set => pulseRoutine.Interval.Value = value;
|
||||
}
|
||||
|
||||
// Change at runtime if the sensor will pulse in Update or FixedUpdate
|
||||
public PulseRoutine.UpdateFunctions PulseUpdateFunction {
|
||||
get => pulseRoutine.UpdateFunction;
|
||||
set => pulseRoutine.UpdateFunction = value;
|
||||
}
|
||||
|
||||
// Displays the results of line of sight tests during OnDrawGizmosSelected for objects in this set.
|
||||
// Used by the editor classes. You shouldn't need to touch this.
|
||||
public HashSet<GameObject> ShowRayCastDebug;
|
||||
|
||||
/**
|
||||
* Returns a data-object with details about the line of sight test for a given GameObject. The ILOSResult instances
|
||||
* are cached by the sensor and reused each time it pulses. Don't hold onto this reference for long, it will
|
||||
* be invalid after the next pulse.
|
||||
*/
|
||||
public ILOSResult GetResult(GameObject forObject) {
|
||||
if (losTests.TryGetValue(forObject, out var result)) {
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ILOSResult> resultsList = new List<ILOSResult>();
|
||||
// Returns a list of ILOSResult data-objects with line of sight results for all of the objects tested.
|
||||
public List<ILOSResult> GetAllResults() {
|
||||
resultsList.Clear();
|
||||
foreach (var tester in losTests) {
|
||||
resultsList.Add(tester.Value);
|
||||
}
|
||||
return resultsList;
|
||||
}
|
||||
|
||||
public override void PulseAll() {
|
||||
if (InputSensor != null) {
|
||||
InputSensor.PulseAll();
|
||||
}
|
||||
Pulse();
|
||||
}
|
||||
|
||||
public override void Clear() {
|
||||
base.Clear();
|
||||
DisposeAllLOSTests();
|
||||
}
|
||||
|
||||
List<Collider2D> clist = new List<Collider2D>();
|
||||
/**
|
||||
* Immediately test line of sight for a given signal and return the results. This gives you full control to test
|
||||
* line of sight when ever you need. Just keep in mind that the ILOSResult is stored until the next Pulse and
|
||||
* then returned to a cache.
|
||||
*/
|
||||
public ILOSResult TestSignal(Signal inputSignal) {
|
||||
clist.Clear();
|
||||
var tester = getLOSTest(inputSignal.Object);
|
||||
|
||||
var config = tester.Config;
|
||||
|
||||
config.InputSignal = inputSignal;
|
||||
config.OwnedCollider2Ds = GetInputColliders(inputSignal.Object, clist);
|
||||
config.Frame = frame;
|
||||
config.MinimumVisibility = MinimumVisibility;
|
||||
config.BlocksLineOfSight = BlocksLineOfSight;
|
||||
config.IgnoreTriggerColliders = IgnoreTriggerColliders;
|
||||
config.PointSamplingMethod = PointSamplingMethod;
|
||||
config.TestLOSTargetsOnly = TestLOSTargetsOnly;
|
||||
config.NumberOfRays = NumberOfRays;
|
||||
config.MovingAverageEnabled = MovingAverageEnabled;
|
||||
config.MovingAverageWindowSize = MovingAverageWindowSize;
|
||||
config.LimitDistance = LimitDistance;
|
||||
config.MaxDistance = MaxDistance;
|
||||
config.VisibilityByDistance = VisibilityByDistance;
|
||||
config.LimitViewAngle = LimitViewAngle;
|
||||
config.MaxHorizAngle = MaxViewAngle;
|
||||
config.VisibilityByHorizAngle = VisibilityByViewAngle;
|
||||
config.MaxVertAngle = 360;
|
||||
config.VisibilityByVertAngle = new ScalingFunction() { Mode = ScalingMode.Step };
|
||||
config.FOVConstraintMethod = FOVConstraintMethod;
|
||||
tester.PerformTest();
|
||||
|
||||
return tester;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internals
|
||||
ReferenceFrame frame => new ReferenceFrame(transform.position, transform.up, transform.right, -transform.forward);
|
||||
|
||||
// Maps a GameObject to a list of raycast target positions for calculating line of sight
|
||||
Dictionary<GameObject, LOSTest2D> losTests = new Dictionary<GameObject, LOSTest2D>();
|
||||
|
||||
Dictionary<GameObject, LOSTest2D> prevLosTests = new Dictionary<GameObject, LOSTest2D>();
|
||||
|
||||
List<Signal> workList = new List<Signal>();
|
||||
|
||||
static ObjectCache<LOSTest2D> losTestCache = new ObjectCache<LOSTest2D>();
|
||||
|
||||
// Recalculates line of sight visibility for each Signal in the input sensor.
|
||||
protected override PulseJob GetPulseJob() {
|
||||
SwapLosTestBuffers();
|
||||
|
||||
workList.Clear();
|
||||
|
||||
if (InputSensor != null) {
|
||||
foreach (var signal in InputSensor.Signals) {
|
||||
var result = TestSignal(signal);
|
||||
if (result.IsVisible) {
|
||||
workList.Add(result.OutputSignal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UpdateAllSignals(workList);
|
||||
OnPulsed?.Invoke();
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
protected override List<Collider2D> GetInputColliders(GameObject inputObject, List<Collider2D> storeIn)
|
||||
=> InputSensor.GetDetectedColliders(inputObject, storeIn);
|
||||
|
||||
Coroutine StartEachFrame(IEnumerator routine) => StartCoroutine(PulseEachFrame(routine));
|
||||
IEnumerator PulseEachFrame(IEnumerator routine) {
|
||||
while (routine.MoveNext()) {
|
||||
yield return routine.Current;
|
||||
}
|
||||
}
|
||||
Coroutine StartFixedInterval(IEnumerator routine) => StartCoroutine(PulseFixedInterval(routine));
|
||||
IEnumerator PulseFixedInterval(IEnumerator routine) {
|
||||
while (routine.MoveNext()) {
|
||||
yield return routine.Current;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void Awake() {
|
||||
base.Awake();
|
||||
|
||||
if (inputSensor == null) {
|
||||
inputSensor = new ObservableSensor();
|
||||
}
|
||||
|
||||
if (pulseRoutine == null) {
|
||||
pulseRoutine = new PulseRoutine();
|
||||
}
|
||||
pulseRoutine.Awake(this, StartEachFrame, StartFixedInterval);
|
||||
}
|
||||
|
||||
protected virtual void OnEnable() {
|
||||
inputSensor.OnChangedValues += InputSensorChangedHandler;
|
||||
InputSensorChangedHandler(null, InputSensor);
|
||||
pulseRoutine.OnEnable();
|
||||
}
|
||||
|
||||
protected override void OnDisable() {
|
||||
base.OnDisable();
|
||||
inputSensor.OnChangedValues -= InputSensorChangedHandler;
|
||||
pulseRoutine.OnDisable();
|
||||
}
|
||||
|
||||
protected virtual void OnValidate() {
|
||||
inputSensor?.OnValidate();
|
||||
pulseRoutine?.OnValidate();
|
||||
}
|
||||
|
||||
void InputSensorChangedHandler(Sensor prev, Sensor next) {
|
||||
if (prev != null) {
|
||||
prev.OnDetected.RemoveListener(InputOnDetectedHandler);
|
||||
prev.OnLostDetection.RemoveListener(InputLostDetectionHandler);
|
||||
}
|
||||
if (next != null) {
|
||||
next.OnDetected.AddListener(InputOnDetectedHandler);
|
||||
next.OnLostDetection.AddListener(InputLostDetectionHandler);
|
||||
}
|
||||
}
|
||||
|
||||
void InputOnDetectedHandler(GameObject detection, Sensor sensor) {
|
||||
var signal = sensor.GetSignal(detection);
|
||||
var results = TestSignal(signal);
|
||||
if (results.IsVisible) {
|
||||
UpdateSignalImmediate(results.OutputSignal);
|
||||
}
|
||||
}
|
||||
|
||||
void InputLostDetectionHandler(GameObject lost, Sensor sensor) {
|
||||
DisposeLOSTest(lost);
|
||||
LostSignalImmediate(lost);
|
||||
}
|
||||
|
||||
void SwapLosTestBuffers() {
|
||||
var temp = prevLosTests;
|
||||
prevLosTests = losTests;
|
||||
losTests = temp;
|
||||
DisposeAllLOSTests();
|
||||
}
|
||||
|
||||
void DisposeLOSTest(GameObject go) {
|
||||
LOSTest2D losTest;
|
||||
if (losTests.TryGetValue(go, out losTest)) {
|
||||
losTestCache.Dispose(losTest);
|
||||
losTests.Remove(go);
|
||||
}
|
||||
}
|
||||
|
||||
void DisposeAllLOSTests() {
|
||||
var losTestEnumerator = losTests.GetEnumerator();
|
||||
while (losTestEnumerator.MoveNext()) {
|
||||
var losTest = losTestEnumerator.Current.Value;
|
||||
losTestCache.Dispose(losTest);
|
||||
}
|
||||
losTests.Clear();
|
||||
}
|
||||
|
||||
LOSTest2D getLOSTest(GameObject go) {
|
||||
LOSTest2D losTest;
|
||||
if (prevLosTests.TryGetValue(go, out losTest)) {
|
||||
losTests[go] = losTest;
|
||||
prevLosTests.Remove(go);
|
||||
return losTest;
|
||||
} else if (losTests.TryGetValue(go, out losTest)) {
|
||||
return losTest;
|
||||
} else {
|
||||
losTest = losTestCache.Get();
|
||||
losTest.Reset();
|
||||
losTests[go] = losTest;
|
||||
}
|
||||
return losTest;
|
||||
}
|
||||
|
||||
protected override void OnDrawGizmosSelected() {
|
||||
base.OnDrawGizmosSelected();
|
||||
|
||||
if (LimitDistance && !LimitViewAngle) {
|
||||
SensorGizmos.PushColor(STPrefs.LOSFovColour);
|
||||
SensorGizmos.SphereGizmo(transform.position, MaxDistance);
|
||||
SensorGizmos.PopColor();
|
||||
}
|
||||
|
||||
if (LimitViewAngle) {
|
||||
FOVRange2D.Of(MaxViewAngle, LimitDistance ? MaxDistance : float.PositiveInfinity).DrawGizmos(frame);
|
||||
}
|
||||
|
||||
if (!ShowDetectionGizmos || ShowRayCastDebug == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var debug in ShowRayCastDebug) {
|
||||
LOSTest2D test;
|
||||
if (losTests.TryGetValue(debug, out test)) {
|
||||
test.DrawGizmos();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
11
Assets/SensorToolkit/Sensors/LOSSensor2D.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/LOSSensor2D.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6700ab74db1541ada368b8f42c2c6693
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 8495cb8b9113d6a47a5c21e285632042, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
22
Assets/SensorToolkit/Sensors/LOSTargets.cs
Normal file
22
Assets/SensorToolkit/Sensors/LOSTargets.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
[AddComponentMenu("Sensors/LOS Targets")]
|
||||
[HelpURL("https://micosmo.com/sensortoolkit2/docs/manual/sensors/los#test-points")]
|
||||
public class LOSTargets : MonoBehaviour {
|
||||
public List<Transform> Targets;
|
||||
|
||||
protected static readonly Color GizmoColor = new Color(51 / 255f, 255 / 255f, 255 / 255f);
|
||||
public virtual void OnDrawGizmosSelected() {
|
||||
if (Targets == null) return;
|
||||
|
||||
Gizmos.color = GizmoColor;
|
||||
foreach (Transform t in Targets) {
|
||||
if (t == null) return;
|
||||
Gizmos.DrawCube(t.position, Vector3.one * 0.1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/SensorToolkit/Sensors/LOSTargets.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/LOSTargets.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 33e07d4c892e462aac602282831698d2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: c2c3a63d9ea6fb24c98f6473b53ab1b6, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"name": "Micosmo.SensorToolkit"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9d61b92870877a459c95c25c7d15074
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
236
Assets/SensorToolkit/Sensors/NavMeshSensor.cs
Normal file
236
Assets/SensorToolkit/Sensors/NavMeshSensor.cs
Normal file
@@ -0,0 +1,236 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
|
||||
public class NavMeshMaskAttribute : PropertyAttribute { }
|
||||
|
||||
/**
|
||||
* The NavMesh Sensor is a simple component that can detect certain features in a Unity NavMesh. It's a simple
|
||||
* wrapper around the built-in navmesh functions: NavMesh.Raycast, NavMesh.FindClosestEdge and
|
||||
* NavMesh.SamplePosition. It doesn't detect Signals and therefore it's not derived from Sensor.
|
||||
*/
|
||||
[AddComponentMenu("Sensors/NavMesh Sensor")]
|
||||
[HelpURL("https://micosmo.com/sensortoolkit2/docs/manual/sensors/navmesh")]
|
||||
public class NavMeshSensor : BasePulsableSensor, IRayCastingSensor, IPulseRoutine {
|
||||
|
||||
#region Configurations
|
||||
public enum TestType { Ray, Sample, ClosestEdge }
|
||||
|
||||
[Tooltip("Which nav mesh function to use.")]
|
||||
public TestType Test;
|
||||
|
||||
// Configurations for ray-shaped navmesh tests
|
||||
public RayShape Ray = new RayShape(5f, Vector3.forward, false);
|
||||
|
||||
// Configurations for sphere-shaped navmesh tests
|
||||
public SphereShape Sphere = new SphereShape(4f);
|
||||
|
||||
[NavMeshMask]
|
||||
[Tooltip("Bitmask over the navmesh area ids.")]
|
||||
public int AreaMask;
|
||||
|
||||
[SerializeField]
|
||||
PulseRoutine pulseRoutine = new PulseRoutine();
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
[SerializeField]
|
||||
ObstructionEvent onObstruction;
|
||||
public ObstructionEvent OnObstruction => onObstruction;
|
||||
|
||||
[SerializeField]
|
||||
ObstructionEvent onClear;
|
||||
public ObstructionEvent OnClear => onClear;
|
||||
|
||||
public override event System.Action OnPulsed;
|
||||
#endregion
|
||||
|
||||
#region Public
|
||||
// Change the pulse mode at runtime
|
||||
public PulseRoutine.Modes PulseMode {
|
||||
get => pulseRoutine.Mode.Value;
|
||||
set => pulseRoutine.Mode.Value = value;
|
||||
}
|
||||
|
||||
// Change the pulse interval at runtime
|
||||
public float PulseInterval {
|
||||
get => pulseRoutine.Interval.Value;
|
||||
set => pulseRoutine.Interval.Value = value;
|
||||
}
|
||||
|
||||
// Boolean specifying if the ray is currently obstructed.
|
||||
public bool IsObstructed => GetObstructionRayHit().IsObstructing;
|
||||
|
||||
// Will always return RayHit.None since this sensor doesn't detect objects. Use GetObstructionRayHit() instead.
|
||||
public RayHit GetDetectionRayHit(GameObject detectedGameObject) => RayHit.None;
|
||||
|
||||
// Returns RayHit data for the current obstruction.
|
||||
public RayHit GetObstructionRayHit() {
|
||||
if (!isObstructed) {
|
||||
return RayHit.None;
|
||||
}
|
||||
return new RayHit() {
|
||||
IsObstructing = true,
|
||||
Point = hit.position,
|
||||
Normal = hit.normal,
|
||||
Distance = hit.distance,
|
||||
DistanceFraction = hit.distance / (Test == TestType.Ray ? Ray.Length : Sphere.Radius)
|
||||
};
|
||||
}
|
||||
|
||||
// Returns the raw NavMeshHit, useful to get at the NavMeshHit.mask property
|
||||
public NavMeshHit GetNavMeshHit() {
|
||||
return hit;
|
||||
}
|
||||
|
||||
public override void PulseAll() => Pulse();
|
||||
|
||||
public override void Clear() {
|
||||
hit = default;
|
||||
if (isObstructed) {
|
||||
isObstructed = false;
|
||||
OnClear.Invoke(this);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internals
|
||||
// Query the navmesh and update the obstruction RayHit data
|
||||
protected override PulseJob GetPulseJob() {
|
||||
var prevIsObstructed = isObstructed;
|
||||
if (Test == TestType.Ray) {
|
||||
isObstructed = TestRay();
|
||||
} else if (Test == TestType.Sample) {
|
||||
isObstructed = TestSample();
|
||||
} else {
|
||||
isObstructed = TestClosestEdge();
|
||||
}
|
||||
|
||||
if (isObstructed && !prevIsObstructed) {
|
||||
OnObstruction.Invoke(this);
|
||||
} else if (!isObstructed && prevIsObstructed) {
|
||||
OnClear.Invoke(this);
|
||||
}
|
||||
|
||||
OnPulsed?.Invoke();
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
Vector3 direction => Ray.WorldSpace
|
||||
? Ray.Direction.normalized
|
||||
: transform.rotation * Ray.Direction.normalized;
|
||||
|
||||
NavMeshHit hit;
|
||||
bool isObstructed;
|
||||
|
||||
bool TestRay() => NavMesh.Raycast(
|
||||
transform.position,
|
||||
transform.position + direction * Ray.Length,
|
||||
out hit,
|
||||
AreaMask);
|
||||
|
||||
bool TestSample() => NavMesh.SamplePosition(
|
||||
transform.position,
|
||||
out hit,
|
||||
Sphere.Radius,
|
||||
AreaMask);
|
||||
|
||||
bool TestClosestEdge() => NavMesh.FindClosestEdge(
|
||||
transform.position,
|
||||
out hit,
|
||||
AreaMask);
|
||||
|
||||
Coroutine StartEachFrame(IEnumerator routine) => StartCoroutine(PulseEachFrame(routine));
|
||||
IEnumerator PulseEachFrame(IEnumerator routine) {
|
||||
while (routine.MoveNext()) {
|
||||
yield return routine.Current;
|
||||
}
|
||||
}
|
||||
Coroutine StartFixedInterval(IEnumerator routine) => StartCoroutine(PulseFixedInterval(routine));
|
||||
IEnumerator PulseFixedInterval(IEnumerator routine) {
|
||||
while (routine.MoveNext()) {
|
||||
yield return routine.Current;
|
||||
}
|
||||
}
|
||||
|
||||
void Awake() {
|
||||
if (onObstruction == null) {
|
||||
onObstruction = new ObstructionEvent();
|
||||
}
|
||||
|
||||
if (onClear == null) {
|
||||
onClear = new ObstructionEvent();
|
||||
}
|
||||
|
||||
if (pulseRoutine == null) {
|
||||
pulseRoutine = new PulseRoutine();
|
||||
}
|
||||
pulseRoutine.Awake(this, StartEachFrame, StartFixedInterval);
|
||||
}
|
||||
|
||||
void OnEnable() {
|
||||
pulseRoutine.OnEnable();
|
||||
}
|
||||
|
||||
protected override void OnDisable() {
|
||||
base.OnDisable();
|
||||
pulseRoutine.OnDisable();
|
||||
}
|
||||
|
||||
void OnValidate() {
|
||||
pulseRoutine?.OnValidate();
|
||||
}
|
||||
|
||||
void OnDrawGizmosSelected() {
|
||||
if (Test == TestType.Ray) {
|
||||
DrawRayGizmo();
|
||||
} else if (Test == TestType.Sample) {
|
||||
DrawSphereGizmo();
|
||||
} else if (Test == TestType.ClosestEdge) {
|
||||
DrawClosestEdgeGizmo();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawRayGizmo() {
|
||||
if (ShowDetectionGizmos && isObstructed) {
|
||||
Gizmos.color = STPrefs.CastingBlockedRayColour;
|
||||
Vector3 endPosition = transform.position + direction * hit.distance;
|
||||
Gizmos.DrawLine(transform.position, endPosition);
|
||||
SensorGizmos.RaycastHitGizmo(hit.position, hit.normal, true);
|
||||
} else {
|
||||
Gizmos.color = STPrefs.CastingRayColour;
|
||||
Vector3 endPosition = transform.position + direction * Ray.Length;
|
||||
Gizmos.DrawLine(transform.position, endPosition);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawSphereGizmo() {
|
||||
SensorGizmos.PushColor(STPrefs.CastingRayColour);
|
||||
SensorGizmos.SphereGizmo(transform.position, Sphere.Radius);
|
||||
SensorGizmos.PopColor();
|
||||
if (ShowDetectionGizmos && isObstructed) {
|
||||
var obs = GetObstructionRayHit();
|
||||
SensorGizmos.PushColor(STPrefs.CastingBlockedRayColour);
|
||||
Gizmos.DrawLine(transform.position, obs.Point);
|
||||
Gizmos.DrawCube(obs.Point, Vector3.one * 0.1f);
|
||||
SensorGizmos.PopColor();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawClosestEdgeGizmo() {
|
||||
if (ShowDetectionGizmos && isObstructed) {
|
||||
var obs = GetObstructionRayHit();
|
||||
SensorGizmos.PushColor(STPrefs.CastingBlockedRayColour);
|
||||
Gizmos.DrawLine(transform.position, obs.Point);
|
||||
SensorGizmos.RaycastHitGizmo(hit.position, obs.Normal, true);
|
||||
SensorGizmos.PopColor();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
11
Assets/SensorToolkit/Sensors/NavMeshSensor.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/NavMeshSensor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bc698a6621304a75b848a9d7a714e79a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 1ada2199784bed045b2ad65cfa5e6ad9, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
219
Assets/SensorToolkit/Sensors/RangeSensor.cs
Normal file
219
Assets/SensorToolkit/Sensors/RangeSensor.cs
Normal file
@@ -0,0 +1,219 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
|
||||
/*
|
||||
* The Range Sensor detects objects that are inside its detection volume. It uses the family of Overlap functions inside Physics or Physics2D.
|
||||
* A detected object will have one or more Collider that overlaps the detection volume.
|
||||
*/
|
||||
[AddComponentMenu("Sensors/Range Sensor")]
|
||||
[HelpURL("https://micosmo.com/sensortoolkit2/docs/manual/sensors/range")]
|
||||
public class RangeSensor : BaseVolumeSensor, IPulseRoutine {
|
||||
|
||||
#region Configurations
|
||||
public enum Shapes { Sphere, Box, Capsule }
|
||||
|
||||
// Determines which shape of Physics.Overlap[...] function to use
|
||||
public Shapes Shape;
|
||||
|
||||
// Configurations for Sphere shape.
|
||||
public SphereShape Sphere = new SphereShape(1f);
|
||||
// Configurations for Box shape.
|
||||
public BoxShape Box = new BoxShape(Vector3.one * .5f);
|
||||
// Configurations for Capsule shape.
|
||||
public CapsuleShape Capsule = new CapsuleShape(.5f, 1f);
|
||||
|
||||
[Tooltip("A layer mask specifying which physics layers objects will be detected on.")]
|
||||
public LayerMask DetectsOnLayers;
|
||||
|
||||
[Tooltip("The sensor will not detect trigger colliders when this is set to true.")]
|
||||
public bool IgnoreTriggerColliders;
|
||||
|
||||
[SerializeField]
|
||||
PulseRoutine pulseRoutine = new PulseRoutine();
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
public override event Action OnPulsed;
|
||||
#endregion
|
||||
|
||||
#region Public
|
||||
// Change the pulse mode at runtime
|
||||
public PulseRoutine.Modes PulseMode {
|
||||
get => pulseRoutine.Mode.Value;
|
||||
set => pulseRoutine.Mode.Value = value;
|
||||
}
|
||||
|
||||
// Change the pulse interval at runtime
|
||||
public float PulseInterval {
|
||||
get => pulseRoutine.Interval.Value;
|
||||
set => pulseRoutine.Interval.Value = value;
|
||||
}
|
||||
|
||||
// Change at runtime if the sensor will pulse in Update or FixedUpdate
|
||||
public PulseRoutine.UpdateFunctions PulseUpdateFunction {
|
||||
get => pulseRoutine.UpdateFunction;
|
||||
set => pulseRoutine.UpdateFunction = value;
|
||||
}
|
||||
|
||||
// The array size allocated for storing results from Physics.RaycastNonAlloc. Will automatically
|
||||
// be doubled in size when more space is needed.
|
||||
public int CurrentBufferSize => physics != null ? physics.Buffer.Length : 0;
|
||||
|
||||
public void SetSphereShape(float radius) {
|
||||
Shape = Shapes.Sphere;
|
||||
Sphere.Radius = radius;
|
||||
}
|
||||
public void SetBoxShape(Vector3 halfExtents) {
|
||||
Shape = Shapes.Box;
|
||||
Box.HalfExtents = halfExtents;
|
||||
}
|
||||
public void SetCapsuleShape(float radius, float height) {
|
||||
Shape = Shapes.Capsule;
|
||||
Capsule.Radius = radius;
|
||||
Capsule.Height = height;
|
||||
}
|
||||
|
||||
public override void PulseAll() => Pulse();
|
||||
#endregion
|
||||
|
||||
#region Internals
|
||||
static OverlapSphereTest SphereTestInstance = new OverlapSphereTest();
|
||||
static OverlapBoxTest BoxTestInstance = new OverlapBoxTest();
|
||||
static OverlapCapsuleTest CapsuleTestInstance = new OverlapCapsuleTest();
|
||||
|
||||
ITestNonAlloc<RangeSensor, Collider> physicsTest {
|
||||
get {
|
||||
switch (Shape) {
|
||||
case Shapes.Sphere:
|
||||
return SphereTestInstance;
|
||||
case Shapes.Box:
|
||||
return BoxTestInstance;
|
||||
case Shapes.Capsule:
|
||||
return CapsuleTestInstance;
|
||||
default:
|
||||
return SphereTestInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PhysicsNonAlloc<Collider> physics;
|
||||
|
||||
// Pulses the sensor to update its list of detected objects
|
||||
protected override PulseJob GetPulseJob() {
|
||||
|
||||
if (physics == null) {
|
||||
physics = new PhysicsNonAlloc<Collider>();
|
||||
}
|
||||
|
||||
int numberDetected = physics.PerformTest(this, physicsTest);
|
||||
|
||||
ClearColliders();
|
||||
|
||||
for (var i = 0; i < numberDetected; i++) {
|
||||
AddCollider(physics.Buffer[i], false);
|
||||
}
|
||||
|
||||
UpdateAllSignals();
|
||||
OnPulsed?.Invoke();
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
Coroutine StartEachFrame(IEnumerator routine) => StartCoroutine(PulseEachFrame(routine));
|
||||
IEnumerator PulseEachFrame(IEnumerator routine) {
|
||||
while (routine.MoveNext()) {
|
||||
yield return routine.Current;
|
||||
}
|
||||
}
|
||||
Coroutine StartFixedInterval(IEnumerator routine) => StartCoroutine(PulseFixedInterval(routine));
|
||||
IEnumerator PulseFixedInterval(IEnumerator routine) {
|
||||
while (routine.MoveNext()) {
|
||||
yield return routine.Current;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void Awake() {
|
||||
base.Awake();
|
||||
|
||||
if (pulseRoutine == null) {
|
||||
pulseRoutine = new PulseRoutine();
|
||||
}
|
||||
pulseRoutine.Awake(this, StartEachFrame, StartFixedInterval);
|
||||
}
|
||||
|
||||
protected void OnEnable() {
|
||||
pulseRoutine.OnEnable();
|
||||
}
|
||||
|
||||
protected override void OnDisable() {
|
||||
base.OnDisable();
|
||||
pulseRoutine.OnDisable();
|
||||
}
|
||||
|
||||
void OnValidate() {
|
||||
pulseRoutine?.OnValidate();
|
||||
}
|
||||
|
||||
protected override void OnDrawGizmosSelected() {
|
||||
base.OnDrawGizmosSelected();
|
||||
switch(Shape) {
|
||||
case Shapes.Sphere:
|
||||
DrawSphereGizmo();
|
||||
break;
|
||||
case Shapes.Box:
|
||||
DrawBoxGizmo();
|
||||
break;
|
||||
case Shapes.Capsule:
|
||||
DrawCapsuleGizmo();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
class OverlapSphereTest : ITestNonAlloc<RangeSensor, Collider> {
|
||||
public int Test(RangeSensor sensor, Collider[] results) {
|
||||
var queryTriggerInteraction = sensor.IgnoreTriggerColliders ? QueryTriggerInteraction.Ignore : QueryTriggerInteraction.Collide;
|
||||
return Physics.OverlapSphereNonAlloc(sensor.transform.position, sensor.Sphere.Radius, results, sensor.DetectsOnLayers, queryTriggerInteraction);
|
||||
}
|
||||
}
|
||||
void DrawSphereGizmo() {
|
||||
SensorGizmos.PushColor(STPrefs.RangeColour);
|
||||
SensorGizmos.SphereGizmo(transform.position, Sphere.Radius);
|
||||
SensorGizmos.PopColor();
|
||||
}
|
||||
|
||||
class OverlapBoxTest : ITestNonAlloc<RangeSensor, Collider> {
|
||||
public int Test(RangeSensor sensor, Collider[] results) {
|
||||
var queryTriggerInteraction = sensor.IgnoreTriggerColliders ? QueryTriggerInteraction.Ignore : QueryTriggerInteraction.Collide;
|
||||
return Physics.OverlapBoxNonAlloc(sensor.transform.position, sensor.Box.HalfExtents, results, sensor.transform.rotation, sensor.DetectsOnLayers, queryTriggerInteraction);
|
||||
}
|
||||
}
|
||||
void DrawBoxGizmo() {
|
||||
Gizmos.color = STPrefs.RangeColour;
|
||||
Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one);
|
||||
Gizmos.DrawWireCube(Vector3.zero, Box.HalfExtents * 2f);
|
||||
Gizmos.matrix = Matrix4x4.identity;
|
||||
}
|
||||
|
||||
class OverlapCapsuleTest : ITestNonAlloc<RangeSensor, Collider> {
|
||||
public int Test(RangeSensor sensor, Collider[] results) {
|
||||
var queryTriggerInteraction = sensor.IgnoreTriggerColliders ? QueryTriggerInteraction.Ignore : QueryTriggerInteraction.Collide;
|
||||
var pt1 = sensor.transform.position + sensor.transform.up * sensor.Capsule.Height / 2f;
|
||||
var pt2 = sensor.transform.position - sensor.transform.up * sensor.Capsule.Height / 2f;
|
||||
return Physics.OverlapCapsuleNonAlloc(pt1, pt2, sensor.Capsule.Radius, results, sensor.DetectsOnLayers, queryTriggerInteraction);
|
||||
}
|
||||
}
|
||||
void DrawCapsuleGizmo() {
|
||||
SensorGizmos.PushColor(STPrefs.RangeColour);
|
||||
SensorGizmos.PushMatrix(Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one));
|
||||
SensorGizmos.CapsuleGizmo(Vector3.zero, Capsule.Radius, Capsule.Height);
|
||||
SensorGizmos.PopMatrix();
|
||||
SensorGizmos.PopColor();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
11
Assets/SensorToolkit/Sensors/RangeSensor.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/RangeSensor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f00a8324cac4f1e84ad1644ec747516
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: e34ab08f67fe3b24595fae22e4de321b, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
235
Assets/SensorToolkit/Sensors/RangeSensor2D.cs
Normal file
235
Assets/SensorToolkit/Sensors/RangeSensor2D.cs
Normal file
@@ -0,0 +1,235 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
/*
|
||||
* The Range Sensor detects objects that are inside its detection volume. It uses the family of Overlap functions inside Physics or Physics2D.
|
||||
* A detected object will have one or more Collider that overlaps the detection volume.
|
||||
*/
|
||||
[AddComponentMenu("Sensors/2D Range Sensor")]
|
||||
[HelpURL("https://micosmo.com/sensortoolkit2/docs/manual/sensors/range")]
|
||||
public class RangeSensor2D : BaseAreaSensor, IPulseRoutine {
|
||||
|
||||
#region Configurations
|
||||
public enum Shapes { Circle, Box, Capsule }
|
||||
|
||||
// Determines which shape of Physics2D.Overlap[...] function to use
|
||||
public Shapes Shape;
|
||||
|
||||
// Configurations for Circle shape.
|
||||
public SphereShape Circle = new SphereShape(1f);
|
||||
// Configurations for Box shape.
|
||||
public Box2DShape Box = new Box2DShape(Vector2.one * .5f);
|
||||
// Configurations for Capsule shape.
|
||||
public CapsuleShape Capsule = new CapsuleShape(.5f, 1f);
|
||||
|
||||
[Tooltip("A layer mask specifying which physics layers objects will be detected on.")]
|
||||
public LayerMask DetectsOnLayers;
|
||||
|
||||
[Tooltip("The sensor will not detect trigger colliders when this is set to true.")]
|
||||
public bool IgnoreTriggerColliders;
|
||||
|
||||
[SerializeField]
|
||||
PulseRoutine pulseRoutine = new PulseRoutine();
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
public override event Action OnPulsed;
|
||||
#endregion
|
||||
|
||||
#region Public
|
||||
// Change the pulse mode at runtime
|
||||
public PulseRoutine.Modes PulseMode {
|
||||
get => pulseRoutine.Mode.Value;
|
||||
set => pulseRoutine.Mode.Value = value;
|
||||
}
|
||||
|
||||
// Change the pulse interval at runtime
|
||||
public float PulseInterval {
|
||||
get => pulseRoutine.Interval.Value;
|
||||
set => pulseRoutine.Interval.Value = value;
|
||||
}
|
||||
|
||||
// Change at runtime if the sensor will pulse in Update or FixedUpdate
|
||||
public PulseRoutine.UpdateFunctions PulseUpdateFunction {
|
||||
get => pulseRoutine.UpdateFunction;
|
||||
set => pulseRoutine.UpdateFunction = value;
|
||||
}
|
||||
|
||||
// The array size allocated for storing results from Physics.RaycastNonAlloc. Will automatically
|
||||
// be doubled in size when more space is needed.
|
||||
public int CurrentBufferSize => physics != null ? physics.Buffer.Length : 0;
|
||||
|
||||
public void SetCircleShape(float radius) {
|
||||
Shape = Shapes.Circle;
|
||||
Circle.Radius = radius;
|
||||
}
|
||||
public void SetBoxShape(Vector2 halfExtents) {
|
||||
Shape = Shapes.Box;
|
||||
Box.HalfExtents = halfExtents;
|
||||
}
|
||||
public void SetCapsuleShape(float radius, float height) {
|
||||
Shape = Shapes.Capsule;
|
||||
Capsule.Radius = radius;
|
||||
Capsule.Height = height;
|
||||
}
|
||||
|
||||
public override void PulseAll() => Pulse();
|
||||
#endregion
|
||||
|
||||
#region Internals
|
||||
static OverlapCircleTest SphereTestInstance = new OverlapCircleTest();
|
||||
static OverlapBoxTest BoxTestInstance = new OverlapBoxTest();
|
||||
static OverlapCapsuleTest CapsuleTestInstance = new OverlapCapsuleTest();
|
||||
|
||||
ITestNonAlloc<RangeSensor2D, Collider2D> physicsTest {
|
||||
get {
|
||||
switch (Shape) {
|
||||
case Shapes.Circle:
|
||||
return SphereTestInstance;
|
||||
case Shapes.Box:
|
||||
return BoxTestInstance;
|
||||
case Shapes.Capsule:
|
||||
return CapsuleTestInstance;
|
||||
default:
|
||||
return SphereTestInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PhysicsNonAlloc<Collider2D> physics;
|
||||
|
||||
// Pulses the sensor to update its list of detected objects
|
||||
protected override PulseJob GetPulseJob() {
|
||||
if (physics == null) {
|
||||
physics = new PhysicsNonAlloc<Collider2D>();
|
||||
}
|
||||
|
||||
var saveQHT = Physics2D.queriesHitTriggers;
|
||||
Physics2D.queriesHitTriggers = !IgnoreTriggerColliders;
|
||||
int numberDetected = physics.PerformTest(this, physicsTest);
|
||||
Physics2D.queriesHitTriggers = saveQHT;
|
||||
|
||||
ClearColliders();
|
||||
|
||||
for (var i = 0; i < numberDetected; i++) {
|
||||
AddCollider(physics.Buffer[i], false);
|
||||
}
|
||||
|
||||
UpdateAllSignals();
|
||||
OnPulsed?.Invoke();
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
Coroutine StartEachFrame(IEnumerator routine) => StartCoroutine(PulseEachFrame(routine));
|
||||
IEnumerator PulseEachFrame(IEnumerator routine) {
|
||||
while (routine.MoveNext()) {
|
||||
yield return routine.Current;
|
||||
}
|
||||
}
|
||||
Coroutine StartFixedInterval(IEnumerator routine) => StartCoroutine(PulseFixedInterval(routine));
|
||||
IEnumerator PulseFixedInterval(IEnumerator routine) {
|
||||
while (routine.MoveNext()) {
|
||||
yield return routine.Current;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void Awake() {
|
||||
base.Awake();
|
||||
|
||||
if (pulseRoutine == null) {
|
||||
pulseRoutine = new PulseRoutine();
|
||||
}
|
||||
pulseRoutine.Awake(this, StartEachFrame, StartFixedInterval);
|
||||
}
|
||||
|
||||
protected void OnEnable() {
|
||||
pulseRoutine.OnEnable();
|
||||
}
|
||||
|
||||
protected override void OnDisable() {
|
||||
base.OnDisable();
|
||||
pulseRoutine.OnDisable();
|
||||
}
|
||||
|
||||
void OnValidate() {
|
||||
pulseRoutine?.OnValidate();
|
||||
}
|
||||
|
||||
protected override void OnDrawGizmosSelected() {
|
||||
base.OnDrawGizmosSelected();
|
||||
switch (Shape) {
|
||||
case Shapes.Circle:
|
||||
DrawCircleGizmo();
|
||||
break;
|
||||
case Shapes.Box:
|
||||
DrawBoxGizmo();
|
||||
break;
|
||||
case Shapes.Capsule:
|
||||
DrawCapsuleGizmo();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
class OverlapCircleTest : ITestNonAlloc<RangeSensor2D, Collider2D> {
|
||||
public int Test(RangeSensor2D sensor, Collider2D[] results) {
|
||||
var filter = new ContactFilter2D {
|
||||
useLayerMask = true,
|
||||
layerMask = sensor.DetectsOnLayers,
|
||||
useTriggers = !sensor.IgnoreTriggerColliders
|
||||
};
|
||||
return Physics2D.OverlapCircle(sensor.transform.position, sensor.Circle.Radius, filter, results);
|
||||
}
|
||||
}
|
||||
void DrawCircleGizmo() {
|
||||
SensorGizmos.PushColor(STPrefs.RangeColour);
|
||||
SensorGizmos.CircleGizmo(transform.position, -Vector3.forward, Circle.Radius);
|
||||
SensorGizmos.PopColor();
|
||||
}
|
||||
|
||||
class OverlapBoxTest : ITestNonAlloc<RangeSensor2D, Collider2D> {
|
||||
public int Test(RangeSensor2D sensor, Collider2D[] results) {
|
||||
var filter = new ContactFilter2D {
|
||||
useLayerMask = true,
|
||||
layerMask = sensor.DetectsOnLayers,
|
||||
useTriggers = !sensor.IgnoreTriggerColliders
|
||||
};
|
||||
return Physics2D.OverlapBox(sensor.transform.position, 2 * sensor.Box.HalfExtents, sensor.transform.eulerAngles.z, filter, results);
|
||||
}
|
||||
}
|
||||
void DrawBoxGizmo() {
|
||||
SensorGizmos.PushColor(STPrefs.RangeColour);
|
||||
SensorGizmos.PushMatrix(Matrix4x4.TRS(transform.position, Quaternion.AngleAxis(transform.eulerAngles.z, Vector3.forward), Vector3.one));
|
||||
Gizmos.DrawWireCube(Vector3.zero, Box.HalfExtents * 2f);
|
||||
SensorGizmos.PopMatrix();
|
||||
SensorGizmos.PopColor();
|
||||
}
|
||||
|
||||
class OverlapCapsuleTest : ITestNonAlloc<RangeSensor2D, Collider2D> {
|
||||
public int Test(RangeSensor2D sensor, Collider2D[] results) {
|
||||
var pos = sensor.transform.position;
|
||||
var size = new Vector2(sensor.Capsule.Radius * 2, sensor.Capsule.Height);
|
||||
var dir = CapsuleDirection2D.Vertical;
|
||||
var angle = sensor.transform.eulerAngles.z;
|
||||
var filter = new ContactFilter2D {
|
||||
useLayerMask = true,
|
||||
layerMask = sensor.DetectsOnLayers,
|
||||
useTriggers = !sensor.IgnoreTriggerColliders
|
||||
};
|
||||
return Physics2D.OverlapCapsule(pos, size, dir, angle, filter, results);
|
||||
}
|
||||
}
|
||||
void DrawCapsuleGizmo() {
|
||||
SensorGizmos.PushColor(STPrefs.RangeColour);
|
||||
SensorGizmos.PushMatrix(Matrix4x4.TRS(transform.position, Quaternion.AngleAxis(transform.eulerAngles.z, Vector3.forward), Vector3.one));
|
||||
SensorGizmos.Capsule2DGizmo(Vector3.zero, Capsule.Radius, Capsule.Height);
|
||||
SensorGizmos.PopMatrix();
|
||||
SensorGizmos.PopColor();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
11
Assets/SensorToolkit/Sensors/RangeSensor2D.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/RangeSensor2D.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df2d86e514f647b4b1f579a4f0ab9022
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 3fabb3ed0748229409d0e989162dcb31, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
514
Assets/SensorToolkit/Sensors/RaySensor.cs
Normal file
514
Assets/SensorToolkit/Sensors/RaySensor.cs
Normal file
@@ -0,0 +1,514 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using UnityEngine.Events;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
/*
|
||||
* The Ray Sensor detects objects intersected by a ray. It uses the family of raycast functions inside Physics or Physics2D. The sensor detects
|
||||
* all objects along its path up until it hits an Obstructing Collider. It will not detect objects beyond the obstruction.
|
||||
*/
|
||||
[AddComponentMenu("Sensors/Ray Sensor")]
|
||||
[HelpURL("https://micosmo.com/sensortoolkit2/docs/manual/sensors/ray")]
|
||||
public class RaySensor : Sensor, IRayCastingSensor, IPulseRoutine {
|
||||
|
||||
#region Configurations
|
||||
|
||||
public enum CastShapeType { Ray, Sphere, Box, Capsule }
|
||||
|
||||
[Tooltip("The shape of the ray. Determines which Physics.Raycast function is called.")]
|
||||
public CastShapeType Shape;
|
||||
|
||||
// Configurations for the Sphere shape
|
||||
public SphereShape Sphere = new SphereShape(1f);
|
||||
// Configurations for the Box shape
|
||||
public BoxShape Box = new BoxShape(Vector3.one * .5f);
|
||||
// Configurations for the Capsule shape
|
||||
public CapsuleShape Capsule = new CapsuleShape(.5f, 1f);
|
||||
|
||||
[SerializeField]
|
||||
SignalFilter signalFilter = new SignalFilter();
|
||||
|
||||
[Tooltip("The detection range in world units.")]
|
||||
public float Length = 5f;
|
||||
|
||||
[Tooltip("The vector direction that the ray is cast in.")]
|
||||
public Vector3 Direction = Vector3.forward;
|
||||
|
||||
[Tooltip("Is the Direction parameter in world space or local space.")]
|
||||
public bool WorldSpace = false;
|
||||
|
||||
[Tooltip("A layer mask specifying which physics layers objects will be detected on.")]
|
||||
public LayerMask DetectsOnLayers;
|
||||
|
||||
[Tooltip("A layer mask specifying which physics layers objects will obstruct the ray on.")]
|
||||
public LayerMask ObstructedByLayers;
|
||||
|
||||
[Tooltip("In Collider mode the sensor detects GameObjects attached to colliders. In RigidBody mode it detects the Collider.AttachedRigidbody.")]
|
||||
public DetectionModes DetectionMode;
|
||||
|
||||
[Tooltip("Ignores all trigger colliders. Will not detect them or be obstructed by them.")]
|
||||
public bool IgnoreTriggerColliders;
|
||||
|
||||
[Range(0f, 90f)]
|
||||
[Tooltip("Calculated slope angle must be greater for the intersection to be a detection or an obstruction.")]
|
||||
public float MinimumSlopeAngle = 0f;
|
||||
|
||||
[Tooltip("Measure slope angle between this direction and the Normal of the RayCastHit. Interpreted in world-space.")]
|
||||
public Vector3 SlopeUpDirection = Vector3.up;
|
||||
|
||||
[SerializeField]
|
||||
PulseRoutine pulseRoutine = new PulseRoutine();
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
[SerializeField]
|
||||
ObstructionEvent onObstruction;
|
||||
public ObstructionEvent OnObstruction {
|
||||
get => onObstruction;
|
||||
set => onObstruction = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
ObstructionEvent onClear;
|
||||
public ObstructionEvent OnClear {
|
||||
get => onClear;
|
||||
set => onClear = value;
|
||||
}
|
||||
|
||||
public override event Action OnPulsed;
|
||||
#endregion
|
||||
|
||||
#region Public
|
||||
// Edit the IgnoreList at runtime. Anything in the list will not be detected
|
||||
public List<GameObject> IgnoreList => signalFilter.IgnoreList;
|
||||
|
||||
// Enable/Disable the tag filtering at runtime
|
||||
public bool EnableTagFilter {
|
||||
get => signalFilter.EnableTagFilter;
|
||||
set => signalFilter.EnableTagFilter = value;
|
||||
}
|
||||
|
||||
// Change the allowed tags at runtime
|
||||
public string[] AllowedTags {
|
||||
get => signalFilter.AllowedTags;
|
||||
set => signalFilter.AllowedTags = value;
|
||||
}
|
||||
|
||||
// Change the pulse mode at runtime
|
||||
public PulseRoutine.Modes PulseMode {
|
||||
get => pulseRoutine.Mode.Value;
|
||||
set => pulseRoutine.Mode.Value = value;
|
||||
}
|
||||
|
||||
// Change the pulse interval at runtime
|
||||
public float PulseInterval {
|
||||
get => pulseRoutine.Interval.Value;
|
||||
set => pulseRoutine.Interval.Value = value;
|
||||
}
|
||||
|
||||
// Change at runtime if the sensor will pulse in Update or FixedUpdate
|
||||
public PulseRoutine.UpdateFunctions PulseUpdateFunction {
|
||||
get => pulseRoutine.UpdateFunction;
|
||||
set => pulseRoutine.UpdateFunction = value;
|
||||
}
|
||||
|
||||
// The array size allocated for storing results from Physics.RaycastNonAlloc. Will automatically
|
||||
// be doubled in size when more space is needed.
|
||||
public int CurrentBufferSize => physics != null ? physics.Buffer.Length : 0;
|
||||
|
||||
// Returns true if the ray sensor is being obstructed and false otherwise
|
||||
public bool IsObstructed => GetObstructionRayHit().IsObstructing;
|
||||
|
||||
public void SetRayShape() {
|
||||
Shape = CastShapeType.Ray;
|
||||
}
|
||||
public void SetSphereShape(float radius) {
|
||||
Sphere.Radius = radius;
|
||||
Shape = CastShapeType.Sphere;
|
||||
}
|
||||
public void SetBoxShape(Vector3 halfExtents) {
|
||||
Box.HalfExtents = halfExtents;
|
||||
Shape = CastShapeType.Box;
|
||||
}
|
||||
public void SetCapsuleShape(float radius, float height) {
|
||||
Capsule.Radius = radius;
|
||||
Capsule.Height = height;
|
||||
Shape = CastShapeType.Capsule;
|
||||
}
|
||||
|
||||
public override void PulseAll() => Pulse();
|
||||
|
||||
public override void Clear() {
|
||||
base.Clear();
|
||||
clearDetectedObjects();
|
||||
SendObstructionEvents();
|
||||
}
|
||||
|
||||
// Returns the RayHit data associated with the detected GameObject
|
||||
public RayHit GetDetectionRayHit(GameObject detectedGameObject) {
|
||||
var result = RayHit.None;
|
||||
goWorkList.Clear();
|
||||
signalPipeline.GetInputObjects(detectedGameObject, goWorkList);
|
||||
foreach (var input in goWorkList) {
|
||||
RaycastHit hit;
|
||||
if (detectedObjectHits.TryGetValue(input, out hit)) {
|
||||
if (result.Equals(RayHit.None) || result.Distance > hit.distance) {
|
||||
result = new RayHit() {
|
||||
IsObstructing = false,
|
||||
Point = hit.point,
|
||||
Normal = hit.normal,
|
||||
Distance = hit.distance,
|
||||
DistanceFraction = hit.distance / Length,
|
||||
Collider = hit.collider
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns the RayHit data associated with the obstructing GameObject
|
||||
public RayHit GetObstructionRayHit() {
|
||||
if (!isObstructed || obstructionRayHit.collider == null) {
|
||||
return RayHit.None;
|
||||
}
|
||||
return new RayHit {
|
||||
IsObstructing = true,
|
||||
Point = obstructionRayHit.point,
|
||||
Normal = obstructionRayHit.normal,
|
||||
Distance = obstructionRayHit.distance,
|
||||
DistanceFraction = obstructionRayHit.distance / Length,
|
||||
Collider = obstructionRayHit.collider
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internals
|
||||
Vector3 direction => WorldSpace ? Direction.normalized : transform.rotation * Direction.normalized;
|
||||
|
||||
QueryTriggerInteraction queryTriggerInteraction => IgnoreTriggerColliders ? QueryTriggerInteraction.Ignore : QueryTriggerInteraction.Collide;
|
||||
|
||||
bool isObstructed = false;
|
||||
RaycastHit obstructionRayHit;
|
||||
Dictionary<GameObject, RaycastHit> detectedObjectHits = new Dictionary<GameObject, RaycastHit>();
|
||||
List<Signal> workList = new List<Signal>();
|
||||
List<GameObject> goWorkList = new List<GameObject>();
|
||||
|
||||
static SphereCastTest SphereCastTestInstance = new SphereCastTest();
|
||||
static BoxCastTest BoxCastTestInstance = new BoxCastTest();
|
||||
static CapsuleCastTest CapsuleCastTestInstance = new CapsuleCastTest();
|
||||
static RayCastTest RayCastTestInstance = new RayCastTest();
|
||||
|
||||
ITestNonAlloc<RaySensor, RaycastHit> physicsTest {
|
||||
get {
|
||||
if (Shape == CastShapeType.Sphere) {
|
||||
return SphereCastTestInstance;
|
||||
} else if (Shape == CastShapeType.Box) {
|
||||
return BoxCastTestInstance;
|
||||
} else if (Shape == CastShapeType.Capsule) {
|
||||
return CapsuleCastTestInstance;
|
||||
}
|
||||
return RayCastTestInstance;
|
||||
}
|
||||
}
|
||||
|
||||
PhysicsNonAlloc<RaycastHit> physics;
|
||||
|
||||
// Pulse the ray sensor
|
||||
protected override PulseJob GetPulseJob() {
|
||||
if (physics == null) {
|
||||
physics = new PhysicsNonAlloc<RaycastHit>();
|
||||
}
|
||||
|
||||
MapToRigidBody.Configure(DetectionMode, false);
|
||||
|
||||
TestRay();
|
||||
|
||||
UpdateAllSignals(workList);
|
||||
SendObstructionEvents();
|
||||
|
||||
OnPulsed?.Invoke();
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
protected override void InitialiseSignalProcessors() {
|
||||
base.InitialiseSignalProcessors();
|
||||
MapToSignalProxy.Configure(true);
|
||||
SignalFilter = signalFilter;
|
||||
}
|
||||
|
||||
protected override List<Collider> GetInputColliders(GameObject inputObject, List<Collider> storeIn) {
|
||||
RaycastHit hit;
|
||||
if (detectedObjectHits.TryGetValue(inputObject, out hit)) {
|
||||
storeIn.Add(hit.collider);
|
||||
}
|
||||
return storeIn;
|
||||
}
|
||||
|
||||
Coroutine StartEachFrame(IEnumerator routine) => StartCoroutine(PulseEachFrame(routine));
|
||||
IEnumerator PulseEachFrame(IEnumerator routine) {
|
||||
while (routine.MoveNext()) {
|
||||
yield return routine.Current;
|
||||
}
|
||||
}
|
||||
Coroutine StartFixedInterval(IEnumerator routine) => StartCoroutine(PulseFixedInterval(routine));
|
||||
IEnumerator PulseFixedInterval(IEnumerator routine) {
|
||||
while (routine.MoveNext()) {
|
||||
yield return routine.Current;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void Awake() {
|
||||
base.Awake();
|
||||
|
||||
if (onObstruction == null) {
|
||||
onObstruction = new ObstructionEvent();
|
||||
}
|
||||
|
||||
if (onClear == null) {
|
||||
onClear = new ObstructionEvent();
|
||||
}
|
||||
|
||||
if (pulseRoutine == null) {
|
||||
pulseRoutine = new PulseRoutine();
|
||||
}
|
||||
pulseRoutine.Awake(this, StartEachFrame, StartFixedInterval);
|
||||
}
|
||||
|
||||
void OnEnable() {
|
||||
clearDetectedObjects();
|
||||
pulseRoutine.OnEnable();
|
||||
}
|
||||
|
||||
protected override void OnDisable() {
|
||||
base.OnDisable();
|
||||
pulseRoutine.OnDisable();
|
||||
}
|
||||
|
||||
void OnValidate() {
|
||||
pulseRoutine?.OnValidate();
|
||||
}
|
||||
|
||||
bool isSingleResult() {
|
||||
var layerMaskIsSubsset = ((DetectsOnLayers | ObstructedByLayers) & (~ObstructedByLayers)) == 0;
|
||||
return layerMaskIsSubsset && MinimumSlopeAngle == 0f && signalFilter.IsNull();
|
||||
}
|
||||
|
||||
List<RaycastHit> hits = new List<RaycastHit>();
|
||||
void TestRay() {
|
||||
clearDetectedObjects();
|
||||
|
||||
var numberOfHits = physics.PerformTest(this, physicsTest);
|
||||
|
||||
hits.Clear();
|
||||
for (int i = 0; i < numberOfHits; i++) {
|
||||
var hit = physics.Buffer[i];
|
||||
if (hit.distance == 0) {
|
||||
continue;
|
||||
}
|
||||
hits.Add(physics.Buffer[i]);
|
||||
}
|
||||
|
||||
hits.Sort(RaycastHitComparison);
|
||||
|
||||
foreach (var hit in hits) {
|
||||
if (MinimumSlopeAngle > 0f) {
|
||||
var slope = Vector3.Angle(SlopeUpDirection, hit.normal);
|
||||
if (slope < MinimumSlopeAngle) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((1 << hit.collider.gameObject.layer & DetectsOnLayers) != 0) {
|
||||
if (signalFilter.TestCollider(hit.collider)) {
|
||||
addRayHit(hit);
|
||||
}
|
||||
}
|
||||
if ((1 << hit.collider.gameObject.layer & ObstructedByLayers) != 0) {
|
||||
if (signalFilter.TestCollider(hit.collider)) {
|
||||
obstructionRayHit = hit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SendObstructionEvents() {
|
||||
if (isObstructed && obstructionRayHit.collider == null) {
|
||||
isObstructed = false;
|
||||
OnClear.Invoke(this);
|
||||
} else if (!isObstructed && obstructionRayHit.collider != null) {
|
||||
isObstructed = true;
|
||||
OnObstruction.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
void addRayHit(RaycastHit hit) {
|
||||
var go = hit.collider.gameObject;
|
||||
if (!detectedObjectHits.ContainsKey(go)) {
|
||||
detectedObjectHits.Add(go, hit);
|
||||
workList.Add(new Signal() {
|
||||
Object = go,
|
||||
Strength = 1f,
|
||||
Bounds = new Bounds(hit.point, Vector3.zero)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void clearDetectedObjects() {
|
||||
obstructionRayHit = new RaycastHit();
|
||||
detectedObjectHits.Clear();
|
||||
workList.Clear();
|
||||
}
|
||||
|
||||
static Comparison<RaycastHit> RaycastHitComparison = new Comparison<RaycastHit>(CompareRaycastHits);
|
||||
|
||||
static int CompareRaycastHits(RaycastHit x, RaycastHit y) {
|
||||
if (x.distance < y.distance) {
|
||||
return -1;
|
||||
} else if (x.distance > y.distance) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected override void OnDrawGizmosSelected() {
|
||||
//base.OnDrawGizmosSelected();
|
||||
|
||||
switch (Shape) {
|
||||
case CastShapeType.Ray:
|
||||
DrawRayGizmo();
|
||||
break;
|
||||
case CastShapeType.Sphere:
|
||||
DrawSphereGizmo();
|
||||
break;
|
||||
case CastShapeType.Box:
|
||||
DrawBoxGizmo();
|
||||
break;
|
||||
case CastShapeType.Capsule:
|
||||
DrawCapsuleGizmo();
|
||||
break;
|
||||
}
|
||||
|
||||
if (ShowDetectionGizmos) {
|
||||
foreach (var detection in GetDetections()) {
|
||||
var hit = GetDetectionRayHit(detection);
|
||||
SensorGizmos.RaycastHitGizmo(hit.Point, hit.Normal, false);
|
||||
}
|
||||
if (IsObstructed) {
|
||||
SensorGizmos.RaycastHitGizmo(GetObstructionRayHit().Point, GetObstructionRayHit().Normal, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IsRunning() {
|
||||
return Application.isPlaying || ShowDetectionGizmos;
|
||||
}
|
||||
|
||||
class RayCastTest : ITestNonAlloc<RaySensor, RaycastHit> {
|
||||
public int Test(RaySensor sensor, RaycastHit[] results) {
|
||||
var queryTriggerInteraction = sensor.IgnoreTriggerColliders ? QueryTriggerInteraction.Ignore : QueryTriggerInteraction.Collide;
|
||||
Ray ray = new Ray(sensor.transform.position, sensor.direction);
|
||||
LayerMask combinedLayers = sensor.DetectsOnLayers | sensor.ObstructedByLayers;
|
||||
if (sensor.isSingleResult()) {
|
||||
if (Physics.Raycast(ray, out var hit, sensor.Length, combinedLayers, queryTriggerInteraction)) {
|
||||
results[0] = hit;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
return Physics.RaycastNonAlloc(ray, results, sensor.Length, combinedLayers, queryTriggerInteraction);
|
||||
}
|
||||
}
|
||||
}
|
||||
void DrawRayGizmo() {
|
||||
Vector3 endPosition;
|
||||
if (IsObstructed && IsRunning()) {
|
||||
Gizmos.color = STPrefs.CastingBlockedRayColour;
|
||||
endPosition = transform.position + direction * obstructionRayHit.distance;
|
||||
} else {
|
||||
Gizmos.color = STPrefs.CastingRayColour;
|
||||
endPosition = transform.position + direction * Length;
|
||||
}
|
||||
Gizmos.DrawLine(transform.position, endPosition);
|
||||
}
|
||||
|
||||
class SphereCastTest : ITestNonAlloc<RaySensor, RaycastHit> {
|
||||
public int Test(RaySensor sensor, RaycastHit[] results) {
|
||||
var queryTriggerInteraction = sensor.IgnoreTriggerColliders ? QueryTriggerInteraction.Ignore : QueryTriggerInteraction.Collide;
|
||||
Ray ray = new Ray(sensor.transform.position, sensor.direction);
|
||||
LayerMask combinedLayers = sensor.DetectsOnLayers | sensor.ObstructedByLayers;
|
||||
if (sensor.isSingleResult()) {
|
||||
if (Physics.SphereCast(ray, sensor.Sphere.Radius, out var hit, sensor.Length, combinedLayers, queryTriggerInteraction)) {
|
||||
results[0] = hit;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
return Physics.SphereCastNonAlloc(ray, sensor.Sphere.Radius, results, sensor.Length, combinedLayers, queryTriggerInteraction);
|
||||
}
|
||||
}
|
||||
}
|
||||
void DrawSphereGizmo() {
|
||||
var showObstruction = (isObstructed && IsRunning());
|
||||
var length = showObstruction ? obstructionRayHit.distance : Length;
|
||||
var direction = WorldSpace ? transform.InverseTransformDirection(Direction) : Direction;
|
||||
SensorGizmos.SpherecastGizmo(new Ray(transform.position, direction), length, transform.rotation, Sphere.Radius, showObstruction);
|
||||
}
|
||||
|
||||
class BoxCastTest : ITestNonAlloc<RaySensor, RaycastHit> {
|
||||
public int Test(RaySensor sensor, RaycastHit[] results) {
|
||||
var queryTriggerInteraction = sensor.IgnoreTriggerColliders ? QueryTriggerInteraction.Ignore : QueryTriggerInteraction.Collide;
|
||||
LayerMask combinedLayers = sensor.DetectsOnLayers | sensor.ObstructedByLayers;
|
||||
if (sensor.isSingleResult()) {
|
||||
if (Physics.BoxCast(sensor.transform.position, sensor.Box.HalfExtents, sensor.direction, out var hit, sensor.transform.rotation, sensor.Length, combinedLayers, queryTriggerInteraction)) {
|
||||
results[0] = hit;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
return Physics.BoxCastNonAlloc(sensor.transform.position, sensor.Box.HalfExtents, sensor.direction, results, sensor.transform.rotation, sensor.Length, combinedLayers, queryTriggerInteraction);
|
||||
}
|
||||
}
|
||||
}
|
||||
void DrawBoxGizmo() {
|
||||
var showObstruction = (isObstructed && IsRunning());
|
||||
var length = showObstruction ? obstructionRayHit.distance : Length;
|
||||
var direction = WorldSpace ? transform.InverseTransformDirection(Direction) : Direction;
|
||||
SensorGizmos.BoxcastGizmo(new Ray(transform.position, direction), length, transform.rotation, Box.HalfExtents, showObstruction);
|
||||
}
|
||||
|
||||
class CapsuleCastTest : ITestNonAlloc<RaySensor, RaycastHit> {
|
||||
public int Test(RaySensor sensor, RaycastHit[] results) {
|
||||
var queryTriggerInteraction = sensor.IgnoreTriggerColliders ? QueryTriggerInteraction.Ignore : QueryTriggerInteraction.Collide;
|
||||
LayerMask combinedLayers = sensor.DetectsOnLayers | sensor.ObstructedByLayers;
|
||||
|
||||
var center = sensor.transform.position;
|
||||
var up = sensor.transform.up;
|
||||
var pt1 = center + up * sensor.Capsule.Height / 2f;
|
||||
var pt2 = center - up * sensor.Capsule.Height / 2f;
|
||||
|
||||
if (sensor.isSingleResult()) {
|
||||
if (Physics.CapsuleCast(pt1, pt2, sensor.Capsule.Radius, sensor.direction, out var hit, sensor.Length, combinedLayers, queryTriggerInteraction)) {
|
||||
results[0] = hit;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
return Physics.CapsuleCastNonAlloc(pt1, pt2, sensor.Capsule.Radius, sensor.direction, results, sensor.Length, combinedLayers, queryTriggerInteraction);
|
||||
}
|
||||
}
|
||||
}
|
||||
void DrawCapsuleGizmo() {
|
||||
var showObstruction = (isObstructed && IsRunning());
|
||||
var length = showObstruction ? obstructionRayHit.distance : Length;
|
||||
var direction = WorldSpace ? transform.InverseTransformDirection(Direction) : Direction;
|
||||
SensorGizmos.CapsulecastGizmo(new Ray(transform.position, direction), length, transform.rotation, Capsule.Radius, Capsule.Height, showObstruction);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
11
Assets/SensorToolkit/Sensors/RaySensor.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/RaySensor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c69b166ee884be191d6d231d343d941
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 078a21a93c2cdb64bb3634cd8ec34db0, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
540
Assets/SensorToolkit/Sensors/RaySensor2D.cs
Normal file
540
Assets/SensorToolkit/Sensors/RaySensor2D.cs
Normal file
@@ -0,0 +1,540 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
|
||||
namespace Micosmo.SensorToolkit
|
||||
{
|
||||
/*
|
||||
* The Ray Sensor detects objects intersected by a ray. It uses the family of raycast functions inside Physics or Physics2D. The sensor detects
|
||||
* all objects along its path up until it hits an Obstructing Collider. It will not detect objects beyond the obstruction.
|
||||
*/
|
||||
[AddComponentMenu("Sensors/2D Ray Sensor")]
|
||||
[HelpURL("https://micosmo.com/sensortoolkit2/docs/manual/sensors/ray")]
|
||||
public class RaySensor2D : Sensor, IRayCastingSensor, IPulseRoutine {
|
||||
|
||||
#region Configurations
|
||||
|
||||
public enum CastShapeType { Ray, Circle, Box, Capsule }
|
||||
|
||||
[Tooltip("The shape of the ray. Determines which Physics.Raycast function is called.")]
|
||||
public CastShapeType Shape;
|
||||
|
||||
// Configurations for the Circle shape
|
||||
public SphereShape Circle = new SphereShape(1f);
|
||||
// Configurations for the Box shape
|
||||
public Box2DShape Box = new Box2DShape(Vector2.one * .5f);
|
||||
// Configurations for the Capsule shape
|
||||
public CapsuleShape Capsule = new CapsuleShape(.5f, 1f);
|
||||
|
||||
[SerializeField]
|
||||
SignalFilter signalFilter = new SignalFilter();
|
||||
|
||||
[Tooltip("The detection range in world units.")]
|
||||
public float Length = 5f;
|
||||
|
||||
[Tooltip("The vector direction that the ray is cast in.")]
|
||||
public Vector2 Direction = Vector2.up;
|
||||
|
||||
[Tooltip("Is the Direction parameter in world space or local space.")]
|
||||
public bool WorldSpace = false;
|
||||
|
||||
[Tooltip("A layer mask specifying which physics layers objects will be detected on.")]
|
||||
public LayerMask DetectsOnLayers;
|
||||
|
||||
[Tooltip("A layer mask specifying which physics layers objects will obstruct the ray on.")]
|
||||
public LayerMask ObstructedByLayers;
|
||||
|
||||
[Tooltip("In Collider mode the sensor detects GameObjects attached to colliders. In RigidBody mode it detects the RigidBody GameObject attached to colliders.")]
|
||||
public DetectionModes DetectionMode;
|
||||
|
||||
[Tooltip("Ignores all trigger colliders. Will not detect them or be obstructed by them.")]
|
||||
public bool IgnoreTriggerColliders;
|
||||
|
||||
[Range(0f, 90f)]
|
||||
[Tooltip("Calculated slope angle must be greater for the intersection to be a detection or an obstruction.")]
|
||||
public float MinimumSlopeAngle = 0f;
|
||||
|
||||
[Tooltip("Measure slope angle between this direction and the Normal of the RayCastHit. Interpreted in world-space.")]
|
||||
public Vector2 SlopeUpDirection = Vector3.up;
|
||||
|
||||
[SerializeField]
|
||||
PulseRoutine pulseRoutine = new PulseRoutine();
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
[SerializeField]
|
||||
ObstructionEvent onObstruction;
|
||||
public ObstructionEvent OnObstruction {
|
||||
get => onObstruction;
|
||||
set => onObstruction = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
ObstructionEvent onClear;
|
||||
public ObstructionEvent OnClear {
|
||||
get => onClear;
|
||||
set => onClear = value;
|
||||
}
|
||||
|
||||
public override event Action OnPulsed;
|
||||
#endregion
|
||||
|
||||
#region Public
|
||||
// Edit the IgnoreList at runtime. Anything in the list will not be detected
|
||||
public List<GameObject> IgnoreList => signalFilter.IgnoreList;
|
||||
|
||||
// Enable/Disable the tag filtering at runtime
|
||||
public bool EnableTagFilter {
|
||||
get => signalFilter.EnableTagFilter;
|
||||
set => signalFilter.EnableTagFilter = value;
|
||||
}
|
||||
|
||||
// Change the allowed tags at runtime
|
||||
public string[] AllowedTags {
|
||||
get => signalFilter.AllowedTags;
|
||||
set => signalFilter.AllowedTags = value;
|
||||
}
|
||||
|
||||
// Change the pulse mode at runtime
|
||||
public PulseRoutine.Modes PulseMode {
|
||||
get => pulseRoutine.Mode.Value;
|
||||
set => pulseRoutine.Mode.Value = value;
|
||||
}
|
||||
|
||||
// Change the pulse interval at runtime
|
||||
public float PulseInterval {
|
||||
get => pulseRoutine.Interval.Value;
|
||||
set => pulseRoutine.Interval.Value = value;
|
||||
}
|
||||
|
||||
// Change at runtime if the sensor will pulse in Update or FixedUpdate
|
||||
public PulseRoutine.UpdateFunctions PulseUpdateFunction {
|
||||
get => pulseRoutine.UpdateFunction;
|
||||
set => pulseRoutine.UpdateFunction = value;
|
||||
}
|
||||
|
||||
// The array size allocated for storing results from Physics.RaycastNonAlloc. Will automatically
|
||||
// be doubled in size when more space is needed.
|
||||
public int CurrentBufferSize => physics != null ? physics.Buffer.Length : 0;
|
||||
|
||||
// Returns true if the ray sensor is being obstructed and false otherwise
|
||||
public bool IsObstructed => GetObstructionRayHit().IsObstructing;
|
||||
|
||||
public void SetRayShape() {
|
||||
Shape = CastShapeType.Ray;
|
||||
}
|
||||
public void SetCircleShape(float radius) {
|
||||
Circle.Radius = radius;
|
||||
Shape = CastShapeType.Circle;
|
||||
}
|
||||
public void SetBoxShape(Vector2 halfExtents) {
|
||||
Box.HalfExtents = halfExtents;
|
||||
Shape = CastShapeType.Box;
|
||||
}
|
||||
public void SetCapsuleShape(float radius, float height) {
|
||||
Capsule.Radius = radius;
|
||||
Capsule.Height = height;
|
||||
Shape = CastShapeType.Capsule;
|
||||
}
|
||||
|
||||
public override void PulseAll() => Pulse();
|
||||
|
||||
public override void Clear() {
|
||||
base.Clear();
|
||||
clearDetectedObjects();
|
||||
SendObstructionEvents();
|
||||
}
|
||||
|
||||
// Returns the RayHit data associated with the detected GameObject
|
||||
public RayHit GetDetectionRayHit(GameObject detectedGameObject) {
|
||||
var result = RayHit.None;
|
||||
goWorkList.Clear();
|
||||
signalPipeline.GetInputObjects(detectedGameObject, goWorkList);
|
||||
foreach (var input in goWorkList) {
|
||||
RaycastHit2D hit;
|
||||
if (detectedObjectHits.TryGetValue(input, out hit)) {
|
||||
if (result.Equals(RayHit.None) || result.Distance > hit.distance) {
|
||||
result = new RayHit() {
|
||||
IsObstructing = false,
|
||||
Point = hit.point,
|
||||
Normal = hit.normal,
|
||||
Distance = hit.distance,
|
||||
DistanceFraction = hit.distance / Length,
|
||||
Collider2D = hit.collider
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns the RayHit data associated with the obstructing GameObject
|
||||
public RayHit GetObstructionRayHit() {
|
||||
if (!isObstructed || obstructionRayHit.collider == null) {
|
||||
return RayHit.None;
|
||||
}
|
||||
return new RayHit() {
|
||||
IsObstructing = true,
|
||||
Point = obstructionRayHit.point,
|
||||
Normal = obstructionRayHit.normal,
|
||||
Distance = obstructionRayHit.distance,
|
||||
DistanceFraction = obstructionRayHit.distance / Length,
|
||||
Collider2D = obstructionRayHit.collider
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internals
|
||||
Vector2 direction => WorldSpace
|
||||
? Direction.normalized :
|
||||
(Vector2)(Quaternion.AngleAxis(transform.rotation.eulerAngles.z, Vector3.forward) * Direction.normalized);
|
||||
|
||||
QueryTriggerInteraction queryTriggerInteraction { get { return IgnoreTriggerColliders ? QueryTriggerInteraction.Ignore : QueryTriggerInteraction.Collide; } }
|
||||
|
||||
bool isObstructed = false;
|
||||
RaycastHit2D obstructionRayHit;
|
||||
Dictionary<GameObject, RaycastHit2D> detectedObjectHits = new Dictionary<GameObject, RaycastHit2D>();
|
||||
List<Signal> workList = new List<Signal>();
|
||||
List<GameObject> goWorkList = new List<GameObject>();
|
||||
|
||||
static CircleCastTest SphereCastTestInstance = new CircleCastTest();
|
||||
static BoxCastTest BoxCastTestInstance = new BoxCastTest();
|
||||
static CapsuleCastTest CapsuleCastTestInstance = new CapsuleCastTest();
|
||||
static RayCastTest RayCastTestInstance = new RayCastTest();
|
||||
|
||||
ITestNonAlloc<RaySensor2D, RaycastHit2D> physicsTest {
|
||||
get {
|
||||
if (Shape == CastShapeType.Circle) {
|
||||
return SphereCastTestInstance;
|
||||
} else if (Shape == CastShapeType.Box) {
|
||||
return BoxCastTestInstance;
|
||||
} else if (Shape == CastShapeType.Capsule) {
|
||||
return CapsuleCastTestInstance;
|
||||
}
|
||||
return RayCastTestInstance;
|
||||
}
|
||||
}
|
||||
|
||||
PhysicsNonAlloc<RaycastHit2D> physics;
|
||||
|
||||
// Pulse the ray sensor
|
||||
protected override PulseJob GetPulseJob() {
|
||||
if (physics == null) {
|
||||
physics = new PhysicsNonAlloc<RaycastHit2D>();
|
||||
}
|
||||
|
||||
MapToRigidBody.Configure(DetectionMode, true);
|
||||
|
||||
TestRay();
|
||||
|
||||
UpdateAllSignals(workList);
|
||||
SendObstructionEvents();
|
||||
|
||||
OnPulsed?.Invoke();
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
protected override void InitialiseSignalProcessors() {
|
||||
base.InitialiseSignalProcessors();
|
||||
MapToSignalProxy.Configure(true);
|
||||
SignalFilter = signalFilter;
|
||||
}
|
||||
|
||||
protected override List<Collider2D> GetInputColliders(GameObject InputObject, List<Collider2D> storeIn) {
|
||||
RaycastHit2D hit;
|
||||
if (detectedObjectHits.TryGetValue(InputObject, out hit)) {
|
||||
storeIn.Add(hit.collider);
|
||||
}
|
||||
return storeIn;
|
||||
}
|
||||
|
||||
Coroutine StartEachFrame(IEnumerator routine) => StartCoroutine(PulseEachFrame(routine));
|
||||
IEnumerator PulseEachFrame(IEnumerator routine) {
|
||||
while (routine.MoveNext()) {
|
||||
yield return routine.Current;
|
||||
}
|
||||
}
|
||||
Coroutine StartFixedInterval(IEnumerator routine) => StartCoroutine(PulseFixedInterval(routine));
|
||||
IEnumerator PulseFixedInterval(IEnumerator routine) {
|
||||
while (routine.MoveNext()) {
|
||||
yield return routine.Current;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void Awake() {
|
||||
base.Awake();
|
||||
|
||||
if (onObstruction == null) {
|
||||
onObstruction = new ObstructionEvent();
|
||||
}
|
||||
|
||||
if (onClear == null) {
|
||||
onClear = new ObstructionEvent();
|
||||
}
|
||||
|
||||
if (pulseRoutine == null) {
|
||||
pulseRoutine = new PulseRoutine();
|
||||
}
|
||||
pulseRoutine.Awake(this, StartEachFrame, StartFixedInterval);
|
||||
}
|
||||
|
||||
void OnEnable() {
|
||||
clearDetectedObjects();
|
||||
pulseRoutine.OnEnable();
|
||||
}
|
||||
|
||||
protected override void OnDisable() {
|
||||
base.OnDisable();
|
||||
pulseRoutine.OnDisable();
|
||||
}
|
||||
|
||||
void OnValidate() {
|
||||
pulseRoutine?.OnValidate();
|
||||
}
|
||||
|
||||
bool isSingleResult() {
|
||||
var layerMaskIsSubsset = ((DetectsOnLayers | ObstructedByLayers) & (~ObstructedByLayers)) == 0;
|
||||
return layerMaskIsSubsset && MinimumSlopeAngle == 0f && signalFilter.IsNull();
|
||||
}
|
||||
|
||||
List<RaycastHit2D> hits = new List<RaycastHit2D>();
|
||||
void TestRay() {
|
||||
clearDetectedObjects();
|
||||
|
||||
var saveQHT = Physics2D.queriesHitTriggers;
|
||||
Physics2D.queriesHitTriggers = !IgnoreTriggerColliders; // Must keep this, Raycast with single result doesn't support ContactFilter
|
||||
var numberOfHits = physics.PerformTest(this, physicsTest);
|
||||
Physics2D.queriesHitTriggers = saveQHT;
|
||||
|
||||
hits.Clear();
|
||||
for (int i = 0; i < numberOfHits; i++) {
|
||||
hits.Add(physics.Buffer[i]);
|
||||
}
|
||||
|
||||
hits.Sort(RaycastHitComparison);
|
||||
|
||||
foreach (var hit in hits) {
|
||||
if (MinimumSlopeAngle > 0f) {
|
||||
var slope = Vector3.Angle(SlopeUpDirection, hit.normal);
|
||||
if (slope < MinimumSlopeAngle) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((1 << hit.collider.gameObject.layer & DetectsOnLayers) != 0) {
|
||||
if (signalFilter.TestCollider(hit.collider)) {
|
||||
addRayHit(hit);
|
||||
}
|
||||
}
|
||||
if ((1 << hit.collider.gameObject.layer & ObstructedByLayers) != 0) {
|
||||
if (signalFilter.TestCollider(hit.collider)) {
|
||||
obstructionRayHit = hit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SendObstructionEvents() {
|
||||
if (isObstructed && obstructionRayHit.collider == null) {
|
||||
isObstructed = false;
|
||||
OnClear.Invoke(this);
|
||||
} else if (!isObstructed && obstructionRayHit.collider != null) {
|
||||
isObstructed = true;
|
||||
OnObstruction.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
void addRayHit(RaycastHit2D hit) {
|
||||
var go = hit.collider.gameObject;
|
||||
if (!detectedObjectHits.ContainsKey(go)) {
|
||||
detectedObjectHits.Add(go, hit);
|
||||
workList.Add(new Signal() {
|
||||
Object = go,
|
||||
Strength = 1f,
|
||||
Bounds = new Bounds(hit.point, Vector3.zero)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void clearDetectedObjects() {
|
||||
obstructionRayHit = new RaycastHit2D();
|
||||
detectedObjectHits.Clear();
|
||||
workList.Clear();
|
||||
}
|
||||
|
||||
static Comparison<RaycastHit2D> RaycastHitComparison = new Comparison<RaycastHit2D>(CompareRaycastHits);
|
||||
|
||||
static int CompareRaycastHits(RaycastHit2D x, RaycastHit2D y) {
|
||||
if (x.distance < y.distance) {
|
||||
return -1;
|
||||
} else if (x.distance > y.distance) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected override void OnDrawGizmosSelected() {
|
||||
//base.OnDrawGizmosSelected();
|
||||
|
||||
switch (Shape) {
|
||||
case CastShapeType.Ray:
|
||||
DrawRayGizmo();
|
||||
break;
|
||||
case CastShapeType.Circle:
|
||||
DrawCircleGizmo();
|
||||
break;
|
||||
case CastShapeType.Box:
|
||||
DrawBoxGizmo();
|
||||
break;
|
||||
case CastShapeType.Capsule:
|
||||
DrawCapsuleGizmo();
|
||||
break;
|
||||
}
|
||||
|
||||
if (ShowDetectionGizmos) {
|
||||
var depth = Vector3.forward * transform.position.z;
|
||||
foreach (var detection in GetDetections()) {
|
||||
var hit = GetDetectionRayHit(detection);
|
||||
SensorGizmos.RaycastHitGizmo(hit.Point + depth, hit.Normal, false);
|
||||
}
|
||||
if (IsObstructed) {
|
||||
SensorGizmos.RaycastHitGizmo(GetObstructionRayHit().Point + depth, GetObstructionRayHit().Normal, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IsRunning() {
|
||||
return Application.isPlaying || ShowDetectionGizmos;
|
||||
}
|
||||
|
||||
class RayCastTest : ITestNonAlloc<RaySensor2D, RaycastHit2D> {
|
||||
public int Test(RaySensor2D sensor, RaycastHit2D[] results) {
|
||||
Ray ray = new Ray(sensor.transform.position, sensor.direction);
|
||||
LayerMask combinedLayers = sensor.DetectsOnLayers | sensor.ObstructedByLayers;
|
||||
if (sensor.isSingleResult()) {
|
||||
var hit = Physics2D.Raycast(ray.origin, ray.direction, sensor.Length, combinedLayers);
|
||||
if (hit.collider != null) {
|
||||
results[0] = hit;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
var filter = new ContactFilter2D {
|
||||
useLayerMask = true,
|
||||
layerMask = combinedLayers,
|
||||
useTriggers = !sensor.IgnoreTriggerColliders
|
||||
};
|
||||
return Physics2D.Raycast(ray.origin, ray.direction, filter, results, sensor.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
void DrawRayGizmo() {
|
||||
Vector3 endPosition;
|
||||
if (IsObstructed && IsRunning()) {
|
||||
SensorGizmos.PushColor(STPrefs.CastingBlockedRayColour);
|
||||
endPosition = transform.position + (Vector3)direction * obstructionRayHit.distance;
|
||||
} else {
|
||||
SensorGizmos.PushColor(STPrefs.CastingRayColour);
|
||||
endPosition = transform.position + (Vector3)direction * Length;
|
||||
}
|
||||
Gizmos.DrawLine(transform.position, endPosition);
|
||||
SensorGizmos.PopColor();
|
||||
}
|
||||
|
||||
class CircleCastTest : ITestNonAlloc<RaySensor2D, RaycastHit2D> {
|
||||
public int Test(RaySensor2D sensor, RaycastHit2D[] results) {
|
||||
Ray ray = new Ray(sensor.transform.position, sensor.direction);
|
||||
LayerMask combinedLayers = sensor.DetectsOnLayers | sensor.ObstructedByLayers;
|
||||
if (sensor.isSingleResult()) {
|
||||
var hit = Physics2D.CircleCast(ray.origin, sensor.Circle.Radius, ray.direction, sensor.Length, combinedLayers);
|
||||
if (hit.collider != null) {
|
||||
results[0] = hit;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
var filter = new ContactFilter2D {
|
||||
useLayerMask = true,
|
||||
layerMask = combinedLayers,
|
||||
useTriggers = !sensor.IgnoreTriggerColliders
|
||||
};
|
||||
return Physics2D.CircleCast(ray.origin, sensor.Circle.Radius, ray.direction, filter, results, sensor.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
void DrawCircleGizmo() {
|
||||
var showObstruction = (isObstructed && IsRunning());
|
||||
var length = showObstruction ? obstructionRayHit.distance : Length;
|
||||
var euler = Quaternion.Euler(0, 0, transform.rotation.eulerAngles.z);
|
||||
var direction = WorldSpace ? Quaternion.Inverse(euler) * Direction : (Vector3)Direction;
|
||||
SensorGizmos.CirclecastGizmo(new Ray(transform.position, direction), length, euler, Circle.Radius, showObstruction);
|
||||
}
|
||||
|
||||
class BoxCastTest : ITestNonAlloc<RaySensor2D, RaycastHit2D> {
|
||||
public int Test(RaySensor2D sensor, RaycastHit2D[] results) {
|
||||
LayerMask combinedLayers = sensor.DetectsOnLayers | sensor.ObstructedByLayers;
|
||||
if (sensor.isSingleResult()) {
|
||||
var hit = Physics2D.BoxCast(sensor.transform.position, 2f*sensor.Box.HalfExtents, sensor.transform.eulerAngles.z, sensor.direction, sensor.Length, combinedLayers);
|
||||
if (hit.collider != null) {
|
||||
results[0] = hit;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
var filter = new ContactFilter2D {
|
||||
useLayerMask = true,
|
||||
layerMask = combinedLayers,
|
||||
useTriggers = !sensor.IgnoreTriggerColliders
|
||||
};
|
||||
return Physics2D.BoxCast(sensor.transform.position, 2f*sensor.Box.HalfExtents, sensor.transform.eulerAngles.z, sensor.direction, filter, results, sensor.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
void DrawBoxGizmo() {
|
||||
var showObstruction = (isObstructed && IsRunning());
|
||||
var length = showObstruction ? obstructionRayHit.distance : Length;
|
||||
var euler = Quaternion.Euler(0, 0, transform.rotation.eulerAngles.z);
|
||||
var direction = WorldSpace ? Quaternion.Inverse(euler) * Direction : (Vector3)Direction;
|
||||
SensorGizmos.BoxcastGizmo(new Ray(transform.position, direction), length, euler, Box.HalfExtents, showObstruction);
|
||||
}
|
||||
|
||||
class CapsuleCastTest : ITestNonAlloc<RaySensor2D, RaycastHit2D> {
|
||||
public int Test(RaySensor2D sensor, RaycastHit2D[] results) {
|
||||
LayerMask combinedLayers = sensor.DetectsOnLayers | sensor.ObstructedByLayers;
|
||||
|
||||
var pos = sensor.transform.position;
|
||||
var size = new Vector2(sensor.Capsule.Radius*2f, sensor.Capsule.Height);
|
||||
var dir = CapsuleDirection2D.Vertical;
|
||||
var angle = sensor.transform.eulerAngles.z;
|
||||
|
||||
if (sensor.isSingleResult()) {
|
||||
var hit = Physics2D.CapsuleCast(pos, size, dir, angle, sensor.direction, sensor.Length, combinedLayers);
|
||||
if (hit.collider != null) {
|
||||
results[0] = hit;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
var filter = new ContactFilter2D {
|
||||
useLayerMask = true,
|
||||
layerMask = combinedLayers,
|
||||
useTriggers = !sensor.IgnoreTriggerColliders
|
||||
};
|
||||
return Physics2D.CapsuleCast(pos, size, dir, angle, sensor.direction, filter, results, sensor.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
void DrawCapsuleGizmo() {
|
||||
var showObstruction = (isObstructed && IsRunning());
|
||||
var length = showObstruction ? obstructionRayHit.distance : Length;
|
||||
var euler = Quaternion.Euler(0, 0, transform.rotation.eulerAngles.z);
|
||||
var direction = WorldSpace ? Quaternion.Inverse(euler) * Direction : (Vector3)Direction;
|
||||
SensorGizmos.Capsule2DcastGizmo(new Ray(transform.position, direction), length, euler, Capsule.Radius, Capsule.Height, showObstruction);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
11
Assets/SensorToolkit/Sensors/RaySensor2D.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/RaySensor2D.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67743d858b7a45d39461726716d6af2e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 261e5377a5e6dcc42bd55a1bf682ccd6, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/SensorToolkit/Sensors/SignalProcessors.meta
Normal file
8
Assets/SensorToolkit/Sensors/SignalProcessors.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 56c8b0695c571794ab9c79d10ce8f47d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
|
||||
[AddComponentMenu("Sensors/Processors/Ignore List")]
|
||||
public class IgnoreListProcessor : SignalProcessor {
|
||||
|
||||
[SerializeField] List<GameObject> ignoreList = new List<GameObject>();
|
||||
public List<GameObject> IgnoreList => ignoreList;
|
||||
|
||||
public override bool Process(ref Signal signal, Sensor sensor) {
|
||||
return !ignoreList.Contains(signal.Object);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 37ce1b5f0579bf448bcb504900228f65
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
|
||||
[AddComponentMenu("Sensors/Processors/Ignore")]
|
||||
public class IgnoreProcessor : SignalProcessor {
|
||||
|
||||
public GameObject ToIgnore;
|
||||
|
||||
public override bool Process(ref Signal signal, Sensor sensor) {
|
||||
return !ReferenceEquals(signal.Object, ToIgnore);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3d7a92bf9e85ed4fbe660f3c9daca92
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
|
||||
[AddComponentMenu("Sensors/Processors/Map Strength By Distance")]
|
||||
public class MapStrengthByDistanceProcessor : SignalProcessor {
|
||||
|
||||
public enum OperationType { Multiply, Set };
|
||||
|
||||
public OperationType Operation = OperationType.Multiply;
|
||||
public RadialInterpolation RadialRange = new RadialInterpolation(0f, 20f);
|
||||
[Range(0,1)] public float MinimumStrength = 0f;
|
||||
|
||||
public override bool Process(ref Signal signal, Sensor sensor) {
|
||||
var distance = Mathf.Sqrt(signal.Bounds.SqrDistance(sensor.transform.position));
|
||||
var t = RadialRange.Calculate(distance);
|
||||
if (Operation == OperationType.Multiply) {
|
||||
signal.Strength *= t;
|
||||
} else {
|
||||
signal.Strength = t;
|
||||
}
|
||||
return signal.Strength > MinimumStrength;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3158a8dc55efa854d98b1e9d38a0e0fd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
|
||||
[AddComponentMenu("Sensors/Processors/Map Strength By Tag")]
|
||||
public class MapStrengthByTagProcessor : SignalProcessor {
|
||||
|
||||
[System.Serializable]
|
||||
public struct Mapping {
|
||||
[TagSelector]
|
||||
public string Tag;
|
||||
public float Value;
|
||||
}
|
||||
|
||||
public enum OperationType { Multiply, Set };
|
||||
|
||||
public OperationType Operation = OperationType.Multiply;
|
||||
public List<Mapping> Tags => tags;
|
||||
[SerializeField] List<Mapping> tags = new List<Mapping>();
|
||||
public float DefaultValue = 1f;
|
||||
[Range(0, 1)] public float MinimumStrength = 0f;
|
||||
|
||||
public override bool Process(ref Signal signal, Sensor sensor) {
|
||||
var value = DefaultValue;
|
||||
foreach (var map in Tags) {
|
||||
if (signal.Object.CompareTag(map.Tag)) {
|
||||
value = map.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Operation == OperationType.Multiply) {
|
||||
signal.Strength *= value;
|
||||
} else {
|
||||
signal.Strength = value;
|
||||
}
|
||||
return signal.Strength > MinimumStrength;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4a0ca8f23d46b8548b8df396968ea181
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
|
||||
[AddComponentMenu("Sensors/Processors/Processor List")]
|
||||
public class ProcessorListProcessor : SignalProcessor {
|
||||
|
||||
[SerializeField] List<SignalProcessor> processors = new List<SignalProcessor>();
|
||||
public List<SignalProcessor> Processors => processors;
|
||||
|
||||
public override bool Process(ref Signal signal, Sensor sensor) {
|
||||
foreach (var processor in Processors) {
|
||||
if (!processor.Process(ref signal, sensor)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ccfa9428303fce04794fa65fef6edee1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
36
Assets/SensorToolkit/Sensors/SignalProxy.cs
Normal file
36
Assets/SensorToolkit/Sensors/SignalProxy.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
|
||||
/*
|
||||
* If a GameObject is detected and it has this component then the detected object will become
|
||||
* the 'ProxyTarget' this component points to.
|
||||
* This could be useful for example if you have characters with Rigidbodies/Colliders on
|
||||
* each of its limbs. You could put this component on each limb and point it to the root
|
||||
* GameObject of the character. Then if a sensor detects one or more of the limbs, it will
|
||||
* show the root GameObject in the list of detections. Otherwise the limbs would each be
|
||||
* detected separately.
|
||||
*/
|
||||
[AddComponentMenu("Sensors/Signal Proxy")]
|
||||
[HelpURL("https://micosmo.com/sensortoolkit2/docs/manual/proxy")]
|
||||
public class SignalProxy : MonoBehaviour {
|
||||
|
||||
public GameObject ProxyTarget;
|
||||
|
||||
public static GameObject GetProxyTarget(GameObject from) {
|
||||
GameObject target = from;
|
||||
SignalProxy proxy;
|
||||
while (target.TryGetComponent(out proxy)) {
|
||||
if (proxy.ProxyTarget == null || proxy.ProxyTarget == target) {
|
||||
break;
|
||||
}
|
||||
target = proxy.ProxyTarget;
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
11
Assets/SensorToolkit/Sensors/SignalProxy.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/SignalProxy.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67eea049b12f26745897ae87177de0a6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bbd5a0635de315248965a75ca3dfbbef, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
349
Assets/SensorToolkit/Sensors/SteeringSensor.cs
Normal file
349
Assets/SensorToolkit/Sensors/SteeringSensor.cs
Normal file
@@ -0,0 +1,349 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Jobs;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
|
||||
/**
|
||||
* The Steering Sensor is a unique sensor for implementing steering behaviour. It's an implementation of
|
||||
* Context Based Steering as described here. The sensor can operate in a spherical mode suitable for flying
|
||||
* agents, or a planar mode for ground-based agents.
|
||||
*/
|
||||
[AddComponentMenu("Sensors/Steering Sensor")]
|
||||
[HelpURL("https://micosmo.com/sensortoolkit2/docs/manual/sensors/steering")]
|
||||
public class SteeringSensor : BasePulsableSensor, IPulseRoutine, ISteeringSensor {
|
||||
|
||||
#region Configurations
|
||||
[SerializeField]
|
||||
[Tooltip("Steering Vectors are 3D when this is true and they are planar when this is false.")]
|
||||
ObservableBool isSpherical = new ObservableBool();
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The up-drection of the sensor when using circular grids.")]
|
||||
ObservableVector3 upDirection = new ObservableVector3() { Value = Vector3.up };
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Determines the number of discrete buckets that directions around the sensor are boken up into.")]
|
||||
ObservableInt resolution = new ObservableInt() { Value = 3 };
|
||||
|
||||
[SerializeField] SteerSeek seek = new SteerSeek();
|
||||
|
||||
[SerializeField] SteerInterest interest = new SteerInterest();
|
||||
|
||||
[SerializeField] SteerDanger danger = new SteerDanger();
|
||||
|
||||
[SerializeField, FormerlySerializedAs("velocityObstacles")] SteerVO velocity = new SteerVO();
|
||||
|
||||
[SerializeField] SteerDecision decision = new SteerDecision();
|
||||
|
||||
[SerializeField]
|
||||
PulseRoutine pulseRoutine = new PulseRoutine();
|
||||
|
||||
[Tooltip("Enables the built-in locomotion if this is any value other then None.")]
|
||||
public LocomotionMode LocomotionMode;
|
||||
|
||||
[Tooltip("The RigidBody to control with built-in locomotion.")]
|
||||
public Rigidbody RigidBody;
|
||||
|
||||
[Tooltip("The CharacterController to control with built-in locomotion.")]
|
||||
public CharacterController CharacterController;
|
||||
|
||||
// Configurations struct for the built-in locomotion behaviours.
|
||||
[SerializeField, FormerlySerializedAs("Locomotion")] LocomotionSystem locomotion;
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
public override event System.Action OnPulsed;
|
||||
#endregion
|
||||
|
||||
#region Public
|
||||
// Change IsSpherical value at runtime
|
||||
public bool IsSpherical {
|
||||
get => isSpherical.Value;
|
||||
set => isSpherical.Value = value;
|
||||
}
|
||||
|
||||
public Vector3 UpDirection {
|
||||
get => upDirection.Value;
|
||||
set => upDirection.Value = value;
|
||||
}
|
||||
|
||||
// Change Resolution at runtime
|
||||
public int Resolution {
|
||||
get => Mathf.Abs(resolution.Value);
|
||||
set => resolution.Value = value;
|
||||
}
|
||||
|
||||
// Change the pulse mode at runtime
|
||||
public PulseRoutine.Modes PulseMode {
|
||||
get => pulseRoutine.Mode.Value;
|
||||
set => pulseRoutine.Mode.Value = value;
|
||||
}
|
||||
|
||||
// Change the pulse interval at runtime
|
||||
public float PulseInterval {
|
||||
get => pulseRoutine.Interval.Value;
|
||||
set => pulseRoutine.Interval.Value = value;
|
||||
}
|
||||
|
||||
// Change at runtime if the sensor will pulse in Update or FixedUpdate
|
||||
public PulseRoutine.UpdateFunctions PulseUpdateFunction {
|
||||
get => pulseRoutine.UpdateFunction;
|
||||
set => pulseRoutine.UpdateFunction = value;
|
||||
}
|
||||
|
||||
public SteerSeek Seek => seek;
|
||||
|
||||
public SteerInterest Interest => interest;
|
||||
|
||||
public SteerDanger Danger => danger;
|
||||
|
||||
public SteerVO Velocity => velocity;
|
||||
|
||||
public SteerDecision Decision => decision;
|
||||
|
||||
public LocomotionSystem Locomotion => locomotion;
|
||||
|
||||
// Is true when we are within the desired range from the target seek position.
|
||||
public bool IsDestinationReached => seek.GetIsDestinationReached(this);
|
||||
|
||||
// Is true when we have not yet reached the destination.
|
||||
public bool IsSeeking => !IsDestinationReached;
|
||||
|
||||
public void SeekTo(Transform destination, float distanceOffset = 0f) {
|
||||
Seek.SeekMode = SeekMode.Position;
|
||||
Seek.SeekPosition = new SeekPosition(destination, false, distanceOffset);
|
||||
}
|
||||
public void SeekTo(Vector3 destination, float distanceOffset = 0f) {
|
||||
Seek.SeekMode = SeekMode.Position;
|
||||
Seek.SeekPosition = new SeekPosition(destination, false, distanceOffset);
|
||||
}
|
||||
public void ArriveTo(Transform destination, float distanceOffset = 0f) {
|
||||
Seek.SeekMode = SeekMode.Position;
|
||||
Seek.SeekPosition = new SeekPosition(destination, true, distanceOffset);
|
||||
}
|
||||
public void ArriveTo(Vector3 destination, float distanceOffset = 0f) {
|
||||
Seek.SeekMode = SeekMode.Position;
|
||||
Seek.SeekPosition = new SeekPosition(destination, true, distanceOffset);
|
||||
}
|
||||
public void SeekDirection(Vector3 direction) {
|
||||
Seek.SeekMode = SeekMode.Direction;
|
||||
Seek.SeekDirection = direction;
|
||||
}
|
||||
public void Wander() {
|
||||
Seek.SeekMode = SeekMode.Wander;
|
||||
}
|
||||
public void Stop() {
|
||||
Seek.SeekMode = SeekMode.Stop;
|
||||
}
|
||||
|
||||
public Vector3 GetSteeringVector() => seek.GetSteeringVector(this);
|
||||
|
||||
public float GetSpeedCandidate(Vector3 direction) => velocity.GetSpeedCandidate(direction);
|
||||
|
||||
public override void PulseAll() {
|
||||
interest.PulseSensors();
|
||||
danger.PulseSensors();
|
||||
velocity.PulseSensors();
|
||||
Pulse();
|
||||
}
|
||||
|
||||
public override void Clear() {
|
||||
ClearPendingPulse();
|
||||
interest.Clear();
|
||||
danger.Clear();
|
||||
velocity.Clear();
|
||||
Decision.Clear();
|
||||
if (!Application.isPlaying) {
|
||||
DisposeGrids();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internals
|
||||
bool isControlling => LocomotionMode != LocomotionMode.None;
|
||||
|
||||
ObservableEffect gridConfigEffect;
|
||||
ObservableEffect upDirectionEffect;
|
||||
|
||||
PulseJob pulseJob;
|
||||
|
||||
void CreatePulseJob() {
|
||||
pulseJob = new PulseJob(new PulseJob.Step[] {
|
||||
(isRun) => {
|
||||
var interestJob = interest.ScheduleJob(this);
|
||||
var dangerJob = danger.ScheduleJob(this);
|
||||
var velocityJob = velocity.ScheduleJob(this);
|
||||
return decision.ScheduleJob(this, interestJob, dangerJob, velocityJob);
|
||||
},
|
||||
(isRun) => {
|
||||
interest.ManagedFinish();
|
||||
danger.ManagedFinish();
|
||||
velocity.ManagedFinish();
|
||||
decision.ManagedFinish();
|
||||
|
||||
OnPulsed?.Invoke();
|
||||
|
||||
return default;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override PulseJob GetPulseJob() {
|
||||
if (!Application.isPlaying) {
|
||||
RecreateGrids();
|
||||
}
|
||||
if (!pulseJob.IsCreated) {
|
||||
CreatePulseJob();
|
||||
}
|
||||
return pulseJob;
|
||||
}
|
||||
|
||||
Coroutine StartEachFrame(IEnumerator routine) => StartCoroutine(PulseEachFrame(routine));
|
||||
IEnumerator PulseEachFrame(IEnumerator routine) {
|
||||
while (routine.MoveNext()) {
|
||||
yield return routine.Current;
|
||||
}
|
||||
}
|
||||
Coroutine StartFixedInterval(IEnumerator routine) => StartCoroutine(PulseFixedInterval(routine));
|
||||
IEnumerator PulseFixedInterval(IEnumerator routine) {
|
||||
while (routine.MoveNext()) {
|
||||
yield return routine.Current;
|
||||
}
|
||||
}
|
||||
|
||||
void Awake() {
|
||||
if (isSpherical == null) {
|
||||
isSpherical = new ObservableBool();
|
||||
}
|
||||
if (resolution == null) {
|
||||
resolution = new ObservableInt() { Value = 3 };
|
||||
}
|
||||
gridConfigEffect = ObservableEffect.Create(RecreateGrids, new Observable[] { isSpherical, resolution }, false);
|
||||
upDirectionEffect = ObservableEffect.Create(UpdateUpDirection, new Observable[] { upDirection }, false);
|
||||
|
||||
if (pulseRoutine == null) {
|
||||
pulseRoutine = new PulseRoutine();
|
||||
}
|
||||
pulseRoutine.Awake(this, StartEachFrame, StartFixedInterval);
|
||||
}
|
||||
|
||||
void OnEnable() {
|
||||
RecreateGrids();
|
||||
pulseRoutine.OnEnable();
|
||||
}
|
||||
|
||||
protected override void OnDisable() {
|
||||
base.OnDisable();
|
||||
pulseRoutine.OnDisable();
|
||||
DisposeGrids();
|
||||
}
|
||||
|
||||
void OnDestroy() {
|
||||
gridConfigEffect?.Dispose();
|
||||
upDirectionEffect?.Dispose();
|
||||
}
|
||||
|
||||
void OnValidate() {
|
||||
isSpherical?.OnValidate();
|
||||
upDirection?.OnValidate();
|
||||
resolution?.OnValidate();
|
||||
pulseRoutine?.OnValidate();
|
||||
}
|
||||
|
||||
void Update() {
|
||||
decision.Interpolate(Time.deltaTime);
|
||||
|
||||
if (LocomotionMode == LocomotionMode.UnityCharacterController) {
|
||||
locomotion.CharacterSeek(CharacterController, GetSteeringVector(), IsSpherical ? Vector3.up : GetSafeUpDirection());
|
||||
}
|
||||
}
|
||||
|
||||
void FixedUpdate() {
|
||||
if (LocomotionMode == LocomotionMode.RigidBodyFlying) {
|
||||
locomotion.FlyableSeek(RigidBody, GetSteeringVector());
|
||||
} else if (LocomotionMode == LocomotionMode.RigidBodyCharacter) {
|
||||
locomotion.CharacterSeek(RigidBody, GetSteeringVector(), IsSpherical ? Vector3.up : GetSafeUpDirection());
|
||||
}
|
||||
}
|
||||
|
||||
void DisposeGrids() {
|
||||
ClearPendingPulse();
|
||||
velocity.Dispose();
|
||||
interest.Dispose();
|
||||
danger.Dispose();
|
||||
velocity.Dispose();
|
||||
decision.Dispose();
|
||||
}
|
||||
|
||||
void RecreateGrids() {
|
||||
ClearPendingPulse();
|
||||
var up = GetSafeUpDirection();
|
||||
interest.RecreateGrids(Resolution, IsSpherical, up);
|
||||
danger.RecreateGrids(Resolution, IsSpherical, up);
|
||||
velocity.RecreateGrids(Resolution, IsSpherical, up);
|
||||
decision.RecreateGrids(Resolution, IsSpherical, up);
|
||||
}
|
||||
|
||||
void UpdateUpDirection() {
|
||||
var up = GetSafeUpDirection();
|
||||
interest.UpdateUpDirection(up);
|
||||
danger.UpdateUpDirection(up);
|
||||
velocity.UpdateUpDirection(up);
|
||||
decision.UpdateUpDirection(up);
|
||||
}
|
||||
|
||||
Vector3 GetSafeUpDirection() {
|
||||
if (UpDirection == Vector3.zero) {
|
||||
return Vector3.up;
|
||||
}
|
||||
return UpDirection.normalized;
|
||||
}
|
||||
|
||||
public static bool ShowInterestGizmos = false;
|
||||
public static bool ShowDangerGizmos = false;
|
||||
public static bool ShowVelocityGizmos = false;
|
||||
public static bool ShowDecisionGizmos = false;
|
||||
|
||||
const float rayScaleMult = 0.05f;
|
||||
const float minRayScale = 1f;
|
||||
const float rayWidth = 3.5f;
|
||||
const float rayGap = 1.1f;
|
||||
void OnDrawGizmosSelected() {
|
||||
if (!ShowDetectionGizmos) {
|
||||
return;
|
||||
}
|
||||
|
||||
var camera = Camera.current;
|
||||
var distance = Vector3.Distance(camera.transform.position, transform.position);
|
||||
var rayScale = Mathf.Max(distance * rayScaleMult, minRayScale);
|
||||
|
||||
int nShown = 1;
|
||||
|
||||
if (ShowInterestGizmos) {
|
||||
interest.DrawGizmos(this, nShown * rayScale * rayGap, rayScale, rayWidth);
|
||||
nShown++;
|
||||
}
|
||||
if (ShowDangerGizmos) {
|
||||
danger.DrawGizmos(this, nShown * rayScale * rayGap, rayScale, rayWidth);
|
||||
nShown++;
|
||||
}
|
||||
if (ShowVelocityGizmos) {
|
||||
velocity.DrawGizmos(this, nShown * rayScale * rayGap, rayScale, rayWidth);
|
||||
nShown++;
|
||||
}
|
||||
if (ShowDecisionGizmos) {
|
||||
decision.DrawGizmos(this, nShown * rayScale * rayGap, rayScale, rayWidth);
|
||||
nShown++;
|
||||
}
|
||||
|
||||
SensorGizmos.PushColor(STPrefs.SteeringVectorColour);
|
||||
SensorGizmos.ThickLineNoZTest(transform.position, transform.position + GetSteeringVector(), rayWidth);
|
||||
SensorGizmos.PopColor();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
11
Assets/SensorToolkit/Sensors/SteeringSensor.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/SteeringSensor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d0f1022f3325494ca6299643c717054a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 25950f11d12fb4c4b803f9a3f2c17497, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
297
Assets/SensorToolkit/Sensors/SteeringSensor2D.cs
Normal file
297
Assets/SensorToolkit/Sensors/SteeringSensor2D.cs
Normal file
@@ -0,0 +1,297 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Jobs;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
|
||||
/**
|
||||
* The Steering Sensor is a unique sensor for implementing steering behaviour. It's an implementation of
|
||||
* Context Based Steering as described here. The sensor can operate in a spherical mode suitable for flying
|
||||
* agents, or a planar mode for ground-based agents.
|
||||
*/
|
||||
[AddComponentMenu("Sensors/2D Steering Sensor")]
|
||||
[HelpURL("https://micosmo.com/sensortoolkit2/docs/manual/sensors/steering")]
|
||||
public class SteeringSensor2D : BasePulsableSensor, IPulseRoutine, ISteeringSensor {
|
||||
|
||||
#region Configurations
|
||||
[SerializeField]
|
||||
[Tooltip("Determines the number of discrete buckets that directions around the sensor are boken up into.")]
|
||||
ObservableInt resolution = new ObservableInt() { Value = 3 };
|
||||
|
||||
[SerializeField] SteerSeek seek = new SteerSeek();
|
||||
|
||||
[SerializeField] SteerInterest interest = new SteerInterest() { LocalForwardDirection = Vector3.up };
|
||||
|
||||
[SerializeField] SteerDanger danger = new SteerDanger();
|
||||
|
||||
[SerializeField, FormerlySerializedAs("velocityObstacles")] SteerVO velocity = new SteerVO();
|
||||
|
||||
[SerializeField] SteerDecision decision = new SteerDecision();
|
||||
|
||||
[SerializeField]
|
||||
PulseRoutine pulseRoutine = new PulseRoutine();
|
||||
|
||||
[Tooltip("Enables the built-in locomotion if this is any value other then None.")]
|
||||
public LocomotionMode2D LocomotionMode;
|
||||
|
||||
[Tooltip("The RigidBody to control with built-in locomotion.")]
|
||||
public Rigidbody2D RigidBody;
|
||||
|
||||
// Configurations struct for the built-in locomotion behaviours.
|
||||
[SerializeField, FormerlySerializedAs("Locomotion")] LocomotionSystem locomotion;
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
public override event System.Action OnPulsed;
|
||||
#endregion
|
||||
|
||||
#region Public
|
||||
// Change Resolution at runtime
|
||||
public int Resolution {
|
||||
get => Mathf.Abs(resolution.Value);
|
||||
set => resolution.Value = value;
|
||||
}
|
||||
|
||||
// Change the pulse mode at runtime
|
||||
public PulseRoutine.Modes PulseMode {
|
||||
get => pulseRoutine.Mode.Value;
|
||||
set => pulseRoutine.Mode.Value = value;
|
||||
}
|
||||
|
||||
// Change the pulse interval at runtime
|
||||
public float PulseInterval {
|
||||
get => pulseRoutine.Interval.Value;
|
||||
set => pulseRoutine.Interval.Value = value;
|
||||
}
|
||||
|
||||
// Change at runtime if the sensor will pulse in Update or FixedUpdate
|
||||
public PulseRoutine.UpdateFunctions PulseUpdateFunction {
|
||||
get => pulseRoutine.UpdateFunction;
|
||||
set => pulseRoutine.UpdateFunction = value;
|
||||
}
|
||||
|
||||
public SteerSeek Seek => seek;
|
||||
|
||||
public SteerInterest Interest => interest;
|
||||
|
||||
public SteerDanger Danger => danger;
|
||||
|
||||
public SteerVO Velocity => velocity;
|
||||
|
||||
public SteerDecision Decision => decision;
|
||||
|
||||
public LocomotionSystem Locomotion => locomotion;
|
||||
|
||||
// Is true when we are within the desired range from the target seek position.
|
||||
public bool IsDestinationReached => seek.GetIsDestinationReached(this);
|
||||
|
||||
// Is true when we have not yet reached the destination.
|
||||
public bool IsSeeking => !IsDestinationReached;
|
||||
|
||||
public void SeekTo(Transform destination, float distanceOffset = 0f) {
|
||||
Seek.SeekMode = SeekMode.Position;
|
||||
Seek.SeekPosition = new SeekPosition(destination, false, distanceOffset);
|
||||
}
|
||||
public void SeekTo(Vector3 destination, float distanceOffset = 0f) {
|
||||
Seek.SeekMode = SeekMode.Position;
|
||||
Seek.SeekPosition = new SeekPosition(destination, false, distanceOffset);
|
||||
}
|
||||
public void ArriveTo(Transform destination, float distanceOffset = 0f) {
|
||||
Seek.SeekMode = SeekMode.Position;
|
||||
Seek.SeekPosition = new SeekPosition(destination, true, distanceOffset);
|
||||
}
|
||||
public void ArriveTo(Vector3 destination, float distanceOffset = 0f) {
|
||||
Seek.SeekMode = SeekMode.Position;
|
||||
Seek.SeekPosition = new SeekPosition(destination, true, distanceOffset);
|
||||
}
|
||||
public void SeekDirection(Vector3 direction) {
|
||||
Seek.SeekMode = SeekMode.Direction;
|
||||
Seek.SeekDirection = direction;
|
||||
}
|
||||
public void Wander() {
|
||||
Seek.SeekMode = SeekMode.Wander;
|
||||
}
|
||||
public void Stop() {
|
||||
Seek.SeekMode = SeekMode.Stop;
|
||||
}
|
||||
|
||||
public Vector3 GetSteeringVector() => seek.GetSteeringVector(this);
|
||||
|
||||
public float GetSpeedCandidate(Vector3 direction) => velocity.GetSpeedCandidate(direction);
|
||||
|
||||
public override void PulseAll() {
|
||||
interest.PulseSensors();
|
||||
danger.PulseSensors();
|
||||
velocity.PulseSensors();
|
||||
Pulse();
|
||||
}
|
||||
|
||||
public override void Clear() {
|
||||
ClearPendingPulse();
|
||||
interest.Clear();
|
||||
danger.Clear();
|
||||
velocity.Clear();
|
||||
Decision.Clear();
|
||||
if (!Application.isPlaying) {
|
||||
DisposeGrids();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internals
|
||||
bool isControlling => LocomotionMode != LocomotionMode2D.None;
|
||||
|
||||
ObservableEffect gridConfigEffect;
|
||||
|
||||
PulseJob pulseJob;
|
||||
|
||||
void CreatePulseJob() {
|
||||
pulseJob = new PulseJob(new PulseJob.Step[] {
|
||||
(isRun) => {
|
||||
var interestJob = interest.ScheduleJob(this);
|
||||
var dangerJob = danger.ScheduleJob(this);
|
||||
var velocityJob = velocity.ScheduleJob(this);
|
||||
return decision.ScheduleJob(this, interestJob, dangerJob, velocityJob);
|
||||
},
|
||||
(isRun) => {
|
||||
interest.ManagedFinish();
|
||||
danger.ManagedFinish();
|
||||
velocity.ManagedFinish();
|
||||
decision.ManagedFinish();
|
||||
|
||||
OnPulsed?.Invoke();
|
||||
|
||||
return default;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override PulseJob GetPulseJob() {
|
||||
if (!Application.isPlaying) {
|
||||
RecreateGrids();
|
||||
}
|
||||
if (!pulseJob.IsCreated) {
|
||||
CreatePulseJob();
|
||||
}
|
||||
return pulseJob;
|
||||
}
|
||||
|
||||
Coroutine StartEachFrame(IEnumerator routine) => StartCoroutine(PulseEachFrame(routine));
|
||||
IEnumerator PulseEachFrame(IEnumerator routine) {
|
||||
while (routine.MoveNext()) {
|
||||
yield return routine.Current;
|
||||
}
|
||||
}
|
||||
Coroutine StartFixedInterval(IEnumerator routine) => StartCoroutine(PulseFixedInterval(routine));
|
||||
IEnumerator PulseFixedInterval(IEnumerator routine) {
|
||||
while (routine.MoveNext()) {
|
||||
yield return routine.Current;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Awake() {
|
||||
if (resolution == null) {
|
||||
resolution = new ObservableInt() { Value = 3 };
|
||||
}
|
||||
gridConfigEffect = ObservableEffect.Create(RecreateGrids, new Observable[] { resolution });
|
||||
|
||||
if (pulseRoutine == null) {
|
||||
pulseRoutine = new PulseRoutine();
|
||||
}
|
||||
pulseRoutine.Awake(this, StartEachFrame, StartFixedInterval);
|
||||
}
|
||||
|
||||
void OnEnable() {
|
||||
RecreateGrids();
|
||||
pulseRoutine.OnEnable();
|
||||
}
|
||||
|
||||
protected override void OnDisable() {
|
||||
base.OnDisable();
|
||||
pulseRoutine.OnDisable();
|
||||
DisposeGrids();
|
||||
}
|
||||
|
||||
void OnDestroy() {
|
||||
gridConfigEffect?.Dispose();
|
||||
}
|
||||
|
||||
void OnValidate() {
|
||||
resolution?.OnValidate();
|
||||
pulseRoutine?.OnValidate();
|
||||
}
|
||||
|
||||
void Update() {
|
||||
decision.Interpolate(Time.deltaTime);
|
||||
}
|
||||
|
||||
void FixedUpdate() {
|
||||
if (LocomotionMode == LocomotionMode2D.RigidBody2D) {
|
||||
locomotion.RigidBody2DSeek(RigidBody, GetSteeringVector());
|
||||
}
|
||||
}
|
||||
|
||||
void DisposeGrids() {
|
||||
ClearPendingPulse();
|
||||
velocity.Dispose();
|
||||
interest.Dispose();
|
||||
danger.Dispose();
|
||||
velocity.Dispose();
|
||||
decision.Dispose();
|
||||
}
|
||||
|
||||
void RecreateGrids() {
|
||||
DisposeGrids();
|
||||
interest.RecreateGrids(Resolution, false, Vector3.back);
|
||||
danger.RecreateGrids(Resolution, false, Vector3.back);
|
||||
velocity.RecreateGrids(Resolution, false, Vector3.back);
|
||||
decision.RecreateGrids(Resolution, false, Vector3.back);
|
||||
}
|
||||
|
||||
public static bool ShowInterestGizmos = false;
|
||||
public static bool ShowDangerGizmos = false;
|
||||
public static bool ShowVelocityGizmos = false;
|
||||
public static bool ShowDecisionGizmos = false;
|
||||
|
||||
const float rayScaleMult = 0.05f;
|
||||
const float minRayScale = 1f;
|
||||
const float rayWidth = 3.5f;
|
||||
const float rayGap = 1.1f;
|
||||
void OnDrawGizmosSelected() {
|
||||
if (!ShowDetectionGizmos) {
|
||||
return;
|
||||
}
|
||||
|
||||
var camera = Camera.current;
|
||||
var distance = Vector3.Distance(camera.transform.position, transform.position);
|
||||
var rayScale = Mathf.Max(distance * rayScaleMult, minRayScale);
|
||||
|
||||
int nShown = 1;
|
||||
|
||||
if (ShowInterestGizmos) {
|
||||
interest.DrawGizmos(this, nShown * rayScale * rayGap, rayScale, rayWidth);
|
||||
nShown++;
|
||||
}
|
||||
if (ShowDangerGizmos) {
|
||||
danger.DrawGizmos(this, nShown * rayScale * rayGap, rayScale, rayWidth);
|
||||
nShown++;
|
||||
}
|
||||
if (ShowVelocityGizmos) {
|
||||
velocity.DrawGizmos(this, nShown * rayScale * rayGap, rayScale, rayWidth);
|
||||
nShown++;
|
||||
}
|
||||
if (ShowDecisionGizmos) {
|
||||
decision.DrawGizmos(this, nShown * rayScale * rayGap, rayScale, rayWidth);
|
||||
nShown++;
|
||||
}
|
||||
|
||||
SensorGizmos.PushColor(Color.cyan);
|
||||
SensorGizmos.ThickLineNoZTest(transform.position, transform.position + GetSteeringVector(), rayWidth);
|
||||
SensorGizmos.PopColor();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
11
Assets/SensorToolkit/Sensors/SteeringSensor2D.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/SteeringSensor2D.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f677a5c7e13449eaa95a95bb394eb757
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 9c2db91041ab9aa4c9633738955d6818, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
192
Assets/SensorToolkit/Sensors/TriggerSensor.cs
Normal file
192
Assets/SensorToolkit/Sensors/TriggerSensor.cs
Normal file
@@ -0,0 +1,192 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
|
||||
namespace Micosmo.SensorToolkit
|
||||
{
|
||||
/*
|
||||
* The Trigger Sensor detects objects that intersect a Trigger Collider. It works by listening
|
||||
* for the events OnTriggerEnter and OnTriggerExit. The sensor has a similar role as the
|
||||
* Range Sensor, with some unique advantages. The downside is that its more difficult to configure.
|
||||
* There are some subtle complexities to Trigger Colliders in Unity that must be considered when
|
||||
* using this sensor.
|
||||
*/
|
||||
[AddComponentMenu("Sensors/Trigger Sensor")]
|
||||
[HelpURL("https://micosmo.com/sensortoolkit2/docs/manual/sensors/trigger")]
|
||||
public class TriggerSensor : BaseVolumeSensor {
|
||||
|
||||
#region Configurations
|
||||
[SerializeField]
|
||||
ObservableBool runInSafeMode = new ObservableBool();
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
#pragma warning disable
|
||||
public override event Action OnPulsed;
|
||||
#endregion
|
||||
|
||||
#region Public
|
||||
// Change RunInSafeMode at runtime
|
||||
public bool RunInSafeMode {
|
||||
get => runInSafeMode.Value;
|
||||
set => runInSafeMode.Value = value;
|
||||
}
|
||||
|
||||
public override void PulseAll() => Pulse();
|
||||
|
||||
public override void Clear() {
|
||||
base.Clear();
|
||||
colliderCount.Clear();
|
||||
OnCleared?.Invoke();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internals
|
||||
event Action OnCleared;
|
||||
|
||||
Dictionary<Collider, int> colliderCount = new Dictionary<Collider, int>();
|
||||
Safety safety;
|
||||
|
||||
// Not necessary to call Pulse on the TriggerSensor.
|
||||
protected override PulseJob GetPulseJob() {
|
||||
UpdateAllSignals();
|
||||
return default;
|
||||
}
|
||||
|
||||
protected override void Awake() {
|
||||
base.Awake();
|
||||
|
||||
if (runInSafeMode == null) {
|
||||
runInSafeMode = new ObservableBool();
|
||||
}
|
||||
|
||||
runInSafeMode.OnChanged += RunInSafeModeChangedHandler;
|
||||
RunInSafeModeChangedHandler();
|
||||
}
|
||||
|
||||
void OnDestroy() {
|
||||
runInSafeMode.OnChanged -= RunInSafeModeChangedHandler;
|
||||
if (safety != null) {
|
||||
Destroy(safety);
|
||||
}
|
||||
}
|
||||
|
||||
void OnValidate() {
|
||||
if (runInSafeMode != null) {
|
||||
runInSafeMode.OnValidate();
|
||||
}
|
||||
}
|
||||
|
||||
void RunInSafeModeChangedHandler() {
|
||||
if (RunInSafeMode && safety == null) {
|
||||
safety = gameObject.AddComponent<Safety>();
|
||||
safety.TriggerSensor = this;
|
||||
} else if (!RunInSafeMode && safety != null) {
|
||||
Destroy(safety);
|
||||
safety = null;
|
||||
}
|
||||
}
|
||||
|
||||
void OnTriggerEnter(Collider other) {
|
||||
if (!colliderCount.TryGetValue(other, out var currCount)) {
|
||||
colliderCount[other] = 1;
|
||||
AddCollider(other, true);
|
||||
} else {
|
||||
colliderCount[other] = currCount + 1;
|
||||
}
|
||||
}
|
||||
|
||||
void OnTriggerExit(Collider other) {
|
||||
if (colliderCount.TryGetValue(other, out var currCount)) {
|
||||
if (currCount == 1) {
|
||||
colliderCount.Remove(other);
|
||||
RemoveCollider(other, true);
|
||||
} else {
|
||||
colliderCount[other] = currCount - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Safety Implementation
|
||||
public class Safety : MonoBehaviour {
|
||||
TriggerSensor triggerSensor;
|
||||
public TriggerSensor TriggerSensor {
|
||||
set {
|
||||
if (!ReferenceEquals(triggerSensor, value)) {
|
||||
if (triggerSensor != null) {
|
||||
triggerSensor.OnCleared -= ClearedHandler;
|
||||
}
|
||||
triggerSensor = value;
|
||||
triggerStayTests.Clear();
|
||||
if (triggerSensor != null) {
|
||||
foreach (var colliderCount in triggerSensor.colliderCount) {
|
||||
triggerStayTests[colliderCount.Key] = colliderCount.Value;
|
||||
}
|
||||
triggerSensor.OnCleared += ClearedHandler;
|
||||
}
|
||||
}
|
||||
} get {
|
||||
return triggerSensor;
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<Collider, int> triggerStayTests = new Dictionary<Collider, int>();
|
||||
bool didPhysicsRun = false;
|
||||
|
||||
void FixedUpdate() {
|
||||
triggerStayTests.Clear();
|
||||
didPhysicsRun = true;
|
||||
}
|
||||
|
||||
void OnTriggerStay(Collider other) {
|
||||
int currCount;
|
||||
if (!triggerStayTests.TryGetValue(other, out currCount)) {
|
||||
currCount = 0;
|
||||
}
|
||||
triggerStayTests[other] = currCount + 1;
|
||||
}
|
||||
|
||||
List<Collider> removeList = new List<Collider>();
|
||||
void Update() {
|
||||
if (!didPhysicsRun) {
|
||||
return;
|
||||
}
|
||||
didPhysicsRun = false;
|
||||
|
||||
removeList.Clear();
|
||||
foreach (var test in triggerStayTests) {
|
||||
var collider = test.Key;
|
||||
var count = test.Value;
|
||||
int sensorCount;
|
||||
if (!triggerSensor.colliderCount.TryGetValue(collider, out sensorCount)) {
|
||||
sensorCount = 0;
|
||||
}
|
||||
for (int i = count; i > sensorCount; i--) {
|
||||
triggerSensor.OnTriggerEnter(collider);
|
||||
}
|
||||
}
|
||||
foreach (var colliderCount in triggerSensor.colliderCount) {
|
||||
var collider = colliderCount.Key;
|
||||
var sensorCount = colliderCount.Value;
|
||||
int count;
|
||||
if (!triggerStayTests.TryGetValue(collider, out count)) {
|
||||
count = 0;
|
||||
}
|
||||
for (int i = count; i < sensorCount; i++) {
|
||||
removeList.Add(collider);
|
||||
}
|
||||
}
|
||||
foreach (var collider in removeList) {
|
||||
triggerSensor.OnTriggerExit(collider);
|
||||
}
|
||||
}
|
||||
|
||||
void ClearedHandler() {
|
||||
triggerStayTests.Clear();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
11
Assets/SensorToolkit/Sensors/TriggerSensor.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/TriggerSensor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 41f6fec8fb4941a8b51158e83854d6d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: f3e8e6c46929b3a45b363fca98f51737, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
193
Assets/SensorToolkit/Sensors/TriggerSensor2D.cs
Normal file
193
Assets/SensorToolkit/Sensors/TriggerSensor2D.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
|
||||
namespace Micosmo.SensorToolkit
|
||||
{
|
||||
/*
|
||||
* The Trigger Sensor detects objects that intersect a Trigger Collider. It works by listening
|
||||
* for the events OnTriggerEnter and OnTriggerExit. The sensor has a similar role as the
|
||||
* Range Sensor, with some unique advantages. The downside is that its more difficult to configure.
|
||||
* There are some subtle complexities to Trigger Colliders in Unity that must be considered when
|
||||
* using this sensor.
|
||||
*/
|
||||
[AddComponentMenu("Sensors/2D Trigger Sensor")]
|
||||
[HelpURL("https://micosmo.com/sensortoolkit2/docs/manual/sensors/trigger")]
|
||||
public class TriggerSensor2D : BaseAreaSensor {
|
||||
|
||||
#region Configurations
|
||||
[SerializeField]
|
||||
ObservableBool runInSafeMode = new ObservableBool();
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
#pragma warning disable
|
||||
public override event Action OnPulsed;
|
||||
#endregion
|
||||
|
||||
#region Public
|
||||
// Change RunInSafeMode at runtime
|
||||
public bool RunInSafeMode {
|
||||
get => runInSafeMode.Value;
|
||||
set => runInSafeMode.Value = value;
|
||||
}
|
||||
|
||||
public override void PulseAll() => Pulse();
|
||||
|
||||
public override void Clear() {
|
||||
base.Clear();
|
||||
colliderCount.Clear();
|
||||
OnCleared?.Invoke();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internals
|
||||
event Action OnCleared;
|
||||
|
||||
Dictionary<Collider2D, int> colliderCount = new Dictionary<Collider2D, int>();
|
||||
Safety safety;
|
||||
|
||||
// Not necessary to call Pulse on the TriggerSensor.
|
||||
protected override PulseJob GetPulseJob() {
|
||||
UpdateAllSignals();
|
||||
return default;
|
||||
}
|
||||
|
||||
protected override void Awake() {
|
||||
base.Awake();
|
||||
|
||||
if (runInSafeMode == null) {
|
||||
runInSafeMode = new ObservableBool();
|
||||
}
|
||||
|
||||
runInSafeMode.OnChanged += RunInSafeModeChangedHandler;
|
||||
RunInSafeModeChangedHandler();
|
||||
}
|
||||
|
||||
void OnDestroy() {
|
||||
runInSafeMode.OnChanged -= RunInSafeModeChangedHandler;
|
||||
if (safety != null) {
|
||||
Destroy(safety);
|
||||
}
|
||||
}
|
||||
|
||||
void OnValidate() {
|
||||
if (runInSafeMode != null) {
|
||||
runInSafeMode.OnValidate();
|
||||
}
|
||||
}
|
||||
|
||||
void RunInSafeModeChangedHandler() {
|
||||
if (RunInSafeMode && safety == null) {
|
||||
safety = gameObject.AddComponent<Safety>();
|
||||
safety.TriggerSensor = this;
|
||||
} else if (!RunInSafeMode && safety != null) {
|
||||
Destroy(safety);
|
||||
safety = null;
|
||||
}
|
||||
}
|
||||
|
||||
void OnTriggerEnter2D(Collider2D other) {
|
||||
if (!colliderCount.TryGetValue(other, out var currCount)) {
|
||||
colliderCount[other] = 1;
|
||||
AddCollider(other, true);
|
||||
} else {
|
||||
colliderCount[other] = currCount + 1;
|
||||
}
|
||||
}
|
||||
|
||||
void OnTriggerExit2D(Collider2D other) {
|
||||
if (colliderCount.TryGetValue(other, out var currCount)) {
|
||||
if (currCount == 1) {
|
||||
colliderCount.Remove(other);
|
||||
RemoveCollider(other, true);
|
||||
} else {
|
||||
colliderCount[other] = currCount - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Safety Implementation
|
||||
public class Safety : MonoBehaviour {
|
||||
TriggerSensor2D triggerSensor;
|
||||
public TriggerSensor2D TriggerSensor {
|
||||
set {
|
||||
if (!ReferenceEquals(triggerSensor, value)) {
|
||||
if (triggerSensor != null) {
|
||||
triggerSensor.OnCleared -= ClearedHandler;
|
||||
}
|
||||
triggerSensor = value;
|
||||
triggerStayTests.Clear();
|
||||
if (triggerSensor != null) {
|
||||
foreach (var colliderCount in triggerSensor.colliderCount) {
|
||||
triggerStayTests[colliderCount.Key] = colliderCount.Value;
|
||||
}
|
||||
triggerSensor.OnCleared += ClearedHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
get {
|
||||
return triggerSensor;
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<Collider2D, int> triggerStayTests = new Dictionary<Collider2D, int>();
|
||||
bool didPhysicsRun = false;
|
||||
|
||||
void FixedUpdate() {
|
||||
triggerStayTests.Clear();
|
||||
didPhysicsRun = true;
|
||||
}
|
||||
|
||||
void OnTriggerStay2D(Collider2D other) {
|
||||
int currCount;
|
||||
if (!triggerStayTests.TryGetValue(other, out currCount)) {
|
||||
currCount = 0;
|
||||
}
|
||||
triggerStayTests[other] = currCount + 1;
|
||||
}
|
||||
|
||||
List<Collider2D> removeList = new List<Collider2D>();
|
||||
void Update() {
|
||||
if (!didPhysicsRun) {
|
||||
return;
|
||||
}
|
||||
didPhysicsRun = false;
|
||||
|
||||
removeList.Clear();
|
||||
foreach (var test in triggerStayTests) {
|
||||
var collider = test.Key;
|
||||
var count = test.Value;
|
||||
int sensorCount;
|
||||
if (!triggerSensor.colliderCount.TryGetValue(collider, out sensorCount)) {
|
||||
sensorCount = 0;
|
||||
}
|
||||
for (int i = count; i > sensorCount; i--) {
|
||||
triggerSensor.OnTriggerEnter2D(collider);
|
||||
}
|
||||
}
|
||||
foreach (var colliderCount in triggerSensor.colliderCount) {
|
||||
var collider = colliderCount.Key;
|
||||
var sensorCount = colliderCount.Value;
|
||||
int count;
|
||||
if (!triggerStayTests.TryGetValue(collider, out count)) {
|
||||
count = 0;
|
||||
}
|
||||
for (int i = count; i < sensorCount; i++) {
|
||||
removeList.Add(collider);
|
||||
}
|
||||
}
|
||||
foreach (var collider in removeList) {
|
||||
triggerSensor.OnTriggerExit2D(collider);
|
||||
}
|
||||
}
|
||||
|
||||
void ClearedHandler() {
|
||||
triggerStayTests.Clear();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
11
Assets/SensorToolkit/Sensors/TriggerSensor2D.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/TriggerSensor2D.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 49ef58afebdb4d60b88c5b9a80b60694
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 19c16e2836734574096b32c4e2a807cd, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
55
Assets/SensorToolkit/Sensors/UserSignals.cs
Normal file
55
Assets/SensorToolkit/Sensors/UserSignals.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
[AddComponentMenu("Sensors/User Signals")]
|
||||
[HelpURL("https://micosmo.com/sensortoolkit2/docs/manual/sensors/user-signals")]
|
||||
public class UserSignals : Sensor {
|
||||
|
||||
[Serializable]
|
||||
public class ObservableSignalList : ObservableList<Signal> { }
|
||||
|
||||
[SerializeField]
|
||||
ObservableSignalList inputSignals = new ObservableSignalList();
|
||||
public ObservableSignalList InputSignals => inputSignals;
|
||||
|
||||
#pragma warning disable
|
||||
public override event Action OnPulsed;
|
||||
|
||||
public override void PulseAll() => Pulse();
|
||||
|
||||
protected override PulseJob GetPulseJob() {
|
||||
InputSignalsChangeHandler();
|
||||
OnPulsed?.Invoke();
|
||||
return default;
|
||||
}
|
||||
|
||||
protected override void Awake() {
|
||||
base.Awake();
|
||||
inputSignals.OnChanged += InputSignalsChangeHandler;
|
||||
InputSignalsChangeHandler();
|
||||
}
|
||||
|
||||
void OnDestroy() {
|
||||
inputSignals.OnChanged -= InputSignalsChangeHandler;
|
||||
}
|
||||
|
||||
void OnValidate() {
|
||||
if (inputSignals == null) {
|
||||
inputSignals = new ObservableSignalList();
|
||||
}
|
||||
inputSignals.OnValidate();
|
||||
}
|
||||
|
||||
List<Signal> workList = new List<Signal>();
|
||||
void InputSignalsChangeHandler() {
|
||||
workList.Clear();
|
||||
foreach (var signal in inputSignals) {
|
||||
workList.Add(signal);
|
||||
}
|
||||
UpdateAllSignals(workList);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/SensorToolkit/Sensors/UserSignals.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/UserSignals.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c4c1d5d87910469a9a2713f1ff1cf39a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 47fe4d794c97807468bc95d8cc623f61, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
9
Assets/SensorToolkit/Sensors/src.meta
Normal file
9
Assets/SensorToolkit/Sensors/src.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e92d95716e44c70addc4445c6d5658f
|
||||
folderAsset: yes
|
||||
timeCreated: 1491027974
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
126
Assets/SensorToolkit/Sensors/src/Accumulator.cs
Normal file
126
Assets/SensorToolkit/Sensors/src/Accumulator.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
|
||||
[Serializable]
|
||||
public struct Accumulator<REF, T> : IEquatable<Accumulator<REF, T>>
|
||||
where REF : UnityEngine.Object
|
||||
where T : IAccumulated<REF, T> {
|
||||
[SerializeField] REF outputTarget;
|
||||
public REF OutputTarget => outputTarget;
|
||||
[SerializeField] List<REF> inputTargets;
|
||||
public List<REF> InputTargets => inputTargets;
|
||||
[SerializeField] List<T> inputs;
|
||||
public List<T> Inputs => inputs;
|
||||
[SerializeField] T output;
|
||||
public T RawOutput => output;
|
||||
[SerializeField] bool isDirty;
|
||||
public T PreviousOutput { get; private set; }
|
||||
|
||||
int Timestamp;
|
||||
|
||||
public void Initialize() {
|
||||
Timestamp = -1;
|
||||
inputTargets = new List<REF>();
|
||||
inputs = new List<T>();
|
||||
}
|
||||
|
||||
public T Output {
|
||||
get {
|
||||
if (isDirty) {
|
||||
Combine();
|
||||
isDirty = false;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
public void Spawn(REF target, int timestamp) {
|
||||
Timestamp = timestamp;
|
||||
outputTarget = target;
|
||||
isDirty = true;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
inputs.Clear();
|
||||
outputTarget = null;
|
||||
PreviousOutput = default;
|
||||
output = default(T);
|
||||
isDirty = false;
|
||||
}
|
||||
|
||||
public bool UpdateInput(REF target, T input, int timestamp) {
|
||||
if (TryGetInput(target, out var found, out var index)) {
|
||||
if (found.Equals(input)) {
|
||||
return false;
|
||||
}
|
||||
SetTimestamp(timestamp);
|
||||
inputs[index] = input;
|
||||
isDirty = true;
|
||||
return true;
|
||||
} else {
|
||||
SetTimestamp(timestamp);
|
||||
inputs.Add(input);
|
||||
inputTargets.Add(target);
|
||||
isDirty = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool RemoveInput(REF target, int timestamp) {
|
||||
if (TryGetInput(target, out var found, out var index)) {
|
||||
SetTimestamp(timestamp);
|
||||
inputs.RemoveAt(index);
|
||||
inputTargets.RemoveAt(index);
|
||||
isDirty = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Equals(Accumulator<REF, T> other) {
|
||||
return outputTarget == other.outputTarget;
|
||||
}
|
||||
|
||||
public override int GetHashCode() {
|
||||
return outputTarget.GetHashCode();
|
||||
}
|
||||
|
||||
void SetTimestamp(int timestamp) {
|
||||
if (Timestamp != timestamp) {
|
||||
Timestamp = timestamp;
|
||||
PreviousOutput = Output;
|
||||
}
|
||||
}
|
||||
|
||||
bool TryGetInput(REF target, out T input, out int index) {
|
||||
for (int i = 0; i < inputTargets.Count; i++) {
|
||||
var t = inputTargets[i];
|
||||
if (ReferenceEquals(t, target)) {
|
||||
input = inputs[i];
|
||||
index = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
input = default(T);
|
||||
index = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
void Combine() {
|
||||
bool isFirst = true;
|
||||
foreach (var input in inputs) {
|
||||
if (isFirst) {
|
||||
output = input;
|
||||
isFirst = false;
|
||||
} else {
|
||||
output = output.Combine(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
11
Assets/SensorToolkit/Sensors/src/Accumulator.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/src/Accumulator.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe0121fbe93c42545969b8c96860bb57
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
415
Assets/SensorToolkit/Sensors/src/AccumulatorPipeline.cs
Normal file
415
Assets/SensorToolkit/Sensors/src/AccumulatorPipeline.cs
Normal file
@@ -0,0 +1,415 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
|
||||
[Serializable]
|
||||
public abstract class AccumulatorPipeline<REF, T> : ISerializationCallbackReceiver
|
||||
where REF : UnityEngine.Object
|
||||
where T : IAccumulated<REF, T> {
|
||||
|
||||
TargetsEnumerable targetsEnumerable;
|
||||
public TargetsEnumerable OutputTargets {
|
||||
get {
|
||||
if (targetsEnumerable == null) {
|
||||
targetsEnumerable = new TargetsEnumerable(this);
|
||||
}
|
||||
return targetsEnumerable;
|
||||
}
|
||||
}
|
||||
|
||||
OutputsEnumerable outputsEnumerable;
|
||||
public OutputsEnumerable Outputs {
|
||||
get {
|
||||
if (outputsEnumerable == null) {
|
||||
outputsEnumerable = new OutputsEnumerable(this);
|
||||
}
|
||||
return outputsEnumerable;
|
||||
}
|
||||
}
|
||||
|
||||
public delegate bool SignalProcessor(in T input, out T processed);
|
||||
|
||||
public SignalProcessor SignalProcessorCallback;
|
||||
|
||||
public event Action<T> OnAdd;
|
||||
public event Action<T, T> OnChange;
|
||||
public event Action<T> OnRemove;
|
||||
public event Action OnSome;
|
||||
public event Action OnNone;
|
||||
|
||||
Dictionary<REF, int> inputToMap = new Dictionary<REF, int>();
|
||||
Dictionary<REF, int> outputToMap = new Dictionary<REF, int>();
|
||||
[NonSerialized] Accumulator<REF, T>[] accumulators = new Accumulator<REF, T>[32];
|
||||
[NonSerialized] int accumulatorCount = 0;
|
||||
|
||||
[NonSerialized] HashSet<REF> toRemove = new HashSet<REF>();
|
||||
[NonSerialized] HashSet<Accumulator<REF, T>> changed = new HashSet<Accumulator<REF, T>>();
|
||||
[NonSerialized] List<T> removed = new List<T>();
|
||||
|
||||
struct Event {
|
||||
public enum Type { Add, Change, Remove }
|
||||
public Type type;
|
||||
public T target;
|
||||
}
|
||||
[NonSerialized] List<Event> eventQueue = new List<Event>();
|
||||
|
||||
int prevSignalCount = -1;
|
||||
int timestamp = 0;
|
||||
bool isPlayingEvents = false;
|
||||
|
||||
public T GetOutput(REF go) {
|
||||
return accumulators[outputToMap[go]].Output;
|
||||
}
|
||||
|
||||
public bool TryGetOutput(REF go, out T output) {
|
||||
if (outputToMap.TryGetValue(go, out var i)) {
|
||||
output = accumulators[i].Output;
|
||||
return true;
|
||||
}
|
||||
output = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<REF> GetInputObjects(REF go, List<REF> storeIn) {
|
||||
if (outputToMap.TryGetValue(go, out var i)) {
|
||||
foreach (var input in accumulators[i].InputTargets) {
|
||||
storeIn.Add(input);
|
||||
}
|
||||
}
|
||||
return storeIn;
|
||||
}
|
||||
|
||||
public bool ContainsOutput(REF go) {
|
||||
return outputToMap.ContainsKey(go);
|
||||
}
|
||||
|
||||
public void UpdateAllInputs(List<T> nextInputs) {
|
||||
if (!AssertRecursionLimitNotReached()) {
|
||||
return;
|
||||
}
|
||||
|
||||
timestamp += 1;
|
||||
|
||||
toRemove.Clear();
|
||||
foreach (var input in inputToMap) {
|
||||
toRemove.Add(input.Key);
|
||||
}
|
||||
|
||||
foreach (var signal in nextInputs) {
|
||||
toRemove.Remove(signal.Object);
|
||||
UpdateInputInternal(signal);
|
||||
}
|
||||
|
||||
foreach (var remaining in toRemove) {
|
||||
RemoveInputInternal(remaining);
|
||||
}
|
||||
|
||||
SerializeEvents();
|
||||
if (!isPlayingEvents) {
|
||||
PlayEvents();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateInput(T signal) {
|
||||
if (!AssertRecursionLimitNotReached()) {
|
||||
return;
|
||||
}
|
||||
|
||||
timestamp += 1;
|
||||
|
||||
UpdateInputInternal(signal);
|
||||
SerializeEvents();
|
||||
if (!isPlayingEvents) {
|
||||
PlayEvents();
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveInput(REF forObject) {
|
||||
if (!AssertRecursionLimitNotReached()) {
|
||||
return;
|
||||
}
|
||||
|
||||
timestamp += 1;
|
||||
|
||||
RemoveInputInternal(forObject);
|
||||
SerializeEvents();
|
||||
if (!isPlayingEvents) {
|
||||
PlayEvents();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnBeforeSerialize() { }
|
||||
|
||||
public void OnAfterDeserialize() {
|
||||
/*inputToMap.Clear();
|
||||
outputToMap.Clear();
|
||||
for (int i = 0; i < accumulatorCount; i++) {
|
||||
var acc = accumulators[i];
|
||||
outputToMap.Add(acc.OutputTarget, i);
|
||||
foreach (var input in acc.InputTargets) {
|
||||
inputToMap.Add(input, i);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
int recursionCount = 0;
|
||||
bool AssertRecursionLimitNotReached() {
|
||||
if (!isPlayingEvents) {
|
||||
recursionCount = 0;
|
||||
return true;
|
||||
}
|
||||
recursionCount += 1;
|
||||
if (recursionCount > 10) {
|
||||
Debug.LogError("It appears that a sensor is being pulsed recursively from a detection event handler. This will be ignored to avoid a stack overflow. To avoid this error make sure that any detection event handlers are not causing a sensor to change its list of detections, and therefore firing a new sequence of events.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void UpdateInputInternal(T signal) {
|
||||
if (ReferenceEquals(signal.Object, null)) {
|
||||
return;
|
||||
}
|
||||
var processed = signal;
|
||||
if (SignalProcessorCallback?.Invoke(in signal, out processed) ?? true) {
|
||||
UpdateProcessedInput(signal.Object, processed);
|
||||
} else {
|
||||
RemoveInputInternal(signal.Object);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveInputInternal(REF forObject) {
|
||||
if (inputToMap.TryGetValue(forObject, out var accIndex)) {
|
||||
RemoveInputFromMap(forObject, accIndex);
|
||||
inputToMap.Remove(forObject);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateProcessedInput(REF inputTarget, T processed) {
|
||||
if (inputToMap.TryGetValue(inputTarget, out var accIndex)) {
|
||||
if (ReferenceEquals(accumulators[accIndex].OutputTarget, processed.Object)) {
|
||||
if (accumulators[accIndex].UpdateInput(inputTarget, processed, timestamp)) {
|
||||
OnChangedEvent(accumulators[accIndex]);
|
||||
}
|
||||
} else {
|
||||
RemoveInputFromMap(inputTarget, accIndex);
|
||||
NewProcessedInput(inputTarget, processed);
|
||||
}
|
||||
} else {
|
||||
NewProcessedInput(inputTarget, processed);
|
||||
}
|
||||
}
|
||||
|
||||
void NewProcessedInput(REF inputTarget, T processed) {
|
||||
if (!outputToMap.TryGetValue(processed.Object, out var accIndex)) {
|
||||
var acc = accumulatorCache.Get();
|
||||
acc.Spawn(processed.Object, timestamp);
|
||||
|
||||
// Add the accumulator to the array, resize if needed
|
||||
accIndex = accumulatorCount;
|
||||
if (accIndex == accumulators.Length) {
|
||||
Array.Resize(ref accumulators, accumulators.Length * 2);
|
||||
}
|
||||
accumulators[accIndex] = acc;
|
||||
accumulatorCount += 1;
|
||||
|
||||
outputToMap[acc.OutputTarget] = accIndex;
|
||||
}
|
||||
inputToMap[inputTarget] = accIndex;
|
||||
if (accumulators[accIndex].UpdateInput(inputTarget, processed, timestamp)) {
|
||||
OnChangedEvent(accumulators[accIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveInputFromMap(REF inputObject, int accIndex) {
|
||||
if (accumulators[accIndex].RemoveInput(inputObject, timestamp)) {
|
||||
if (accumulators[accIndex].Inputs.Count > 0) {
|
||||
OnChangedEvent(accumulators[accIndex]);
|
||||
} else {
|
||||
OnRemovedEvent(accumulators[accIndex]);
|
||||
outputToMap.Remove(accumulators[accIndex].OutputTarget);
|
||||
accumulatorCache.Dispose(accumulators[accIndex]);
|
||||
|
||||
// Swap with last accumulator and remove
|
||||
var lastIndex = accumulatorCount - 1;
|
||||
if (accIndex != lastIndex) {
|
||||
var lastAcc = accumulators[lastIndex];
|
||||
accumulators[accIndex] = lastAcc;
|
||||
outputToMap[lastAcc.OutputTarget] = accIndex;
|
||||
foreach (var input in lastAcc.InputTargets) {
|
||||
inputToMap[input] = accIndex;
|
||||
}
|
||||
}
|
||||
accumulatorCount -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnChangedEvent(Accumulator<REF, T> signal) {
|
||||
changed.Add(signal);
|
||||
}
|
||||
|
||||
void OnRemovedEvent(Accumulator<REF, T> signal) {
|
||||
changed.Remove(signal);
|
||||
removed.Add(signal.PreviousOutput);
|
||||
}
|
||||
|
||||
void SerializeEvents() {
|
||||
foreach (var change in changed) {
|
||||
var previousOutput = change.PreviousOutput;
|
||||
if (ReferenceEquals(previousOutput.Object, null)) {
|
||||
eventQueue.Add(new Event { type = Event.Type.Add, target = change.Output });
|
||||
} else {
|
||||
eventQueue.Add(new Event { type = Event.Type.Change, target = previousOutput });
|
||||
eventQueue.Add(new Event { type = Event.Type.Change, target = change.Output });
|
||||
}
|
||||
}
|
||||
foreach (var remove in removed) {
|
||||
eventQueue.Add(new Event { type = Event.Type.Remove, target = remove });
|
||||
}
|
||||
changed.Clear();
|
||||
removed.Clear();
|
||||
}
|
||||
|
||||
void PlayEvents() {
|
||||
isPlayingEvents = true;
|
||||
try {
|
||||
var eventIndex = 0;
|
||||
while (eventIndex < eventQueue.Count) {
|
||||
var evt = eventQueue[eventIndex];
|
||||
switch (evt.type) {
|
||||
case Event.Type.Add:
|
||||
OnAdd?.Invoke(evt.target);
|
||||
break;
|
||||
case Event.Type.Change:
|
||||
// We store the prevValue as current index. The next value in the next index.
|
||||
eventIndex++;
|
||||
var changeTo = eventQueue[eventIndex];
|
||||
OnChange?.Invoke(evt.target, changeTo.target);
|
||||
break;
|
||||
case Event.Type.Remove:
|
||||
OnRemove?.Invoke(evt.target);
|
||||
break;
|
||||
}
|
||||
eventIndex += 1;
|
||||
}
|
||||
eventQueue.Clear();
|
||||
|
||||
var signalCount = Outputs.Count;
|
||||
// prevSignalCount initialized to -1, so event always triggers on first update
|
||||
if (prevSignalCount <= 0 && signalCount > 0) {
|
||||
OnSome?.Invoke();
|
||||
} else if (prevSignalCount != 0 && signalCount == 0) {
|
||||
OnNone?.Invoke();
|
||||
}
|
||||
prevSignalCount = signalCount;
|
||||
} finally {
|
||||
isPlayingEvents = false;
|
||||
}
|
||||
}
|
||||
|
||||
AccumulatorCache accumulatorCache = new AccumulatorCache();
|
||||
|
||||
class AccumulatorCache : ObjectCache<Accumulator<REF, T>> {
|
||||
public override void Dispose(Accumulator<REF, T> obj) {
|
||||
obj.Dispose();
|
||||
base.Dispose(obj);
|
||||
}
|
||||
protected override Accumulator<REF, T> create() {
|
||||
var inst = base.create();
|
||||
inst.Initialize();
|
||||
return inst;
|
||||
}
|
||||
}
|
||||
|
||||
public class OutputsEnumerable : IEnumerable<T>, IEnumerable {
|
||||
AccumulatorPipeline<REF, T> source;
|
||||
public OutputsEnumerable(AccumulatorPipeline<REF, T> source) { this.source = source; }
|
||||
public Enumerator GetEnumerator() { return new Enumerator(source); }
|
||||
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() { return GetEnumerator(); }
|
||||
|
||||
public int Count {
|
||||
get {
|
||||
int n = 0;
|
||||
for (int i = 0; i < source.accumulatorCount; i++) {
|
||||
if (source.accumulators[i].OutputTarget != null) {
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<T>, IEnumerator {
|
||||
AccumulatorPipeline<REF, T> source;
|
||||
int index;
|
||||
public Enumerator(AccumulatorPipeline<REF, T> source) {
|
||||
this.source = source;
|
||||
index = -1;
|
||||
}
|
||||
public T Current { get { return source.accumulators[index].Output; } }
|
||||
object IEnumerator.Current => throw new NotImplementedException();
|
||||
public void Dispose() { }
|
||||
public bool MoveNext() {
|
||||
index += 1;
|
||||
if (index >= source.accumulatorCount) {
|
||||
return false;
|
||||
}
|
||||
if (Current.Object == null) {
|
||||
return MoveNext();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public void Reset() { index = -1; }
|
||||
}
|
||||
}
|
||||
|
||||
public class TargetsEnumerable : IEnumerable<REF>, IEnumerable {
|
||||
AccumulatorPipeline<REF, T> source;
|
||||
public TargetsEnumerable(AccumulatorPipeline<REF, T> source) { this.source = source; }
|
||||
public Enumerator GetEnumerator() { return new Enumerator(source); }
|
||||
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
|
||||
IEnumerator<REF> IEnumerable<REF>.GetEnumerator() { return GetEnumerator(); }
|
||||
|
||||
public int Count {
|
||||
get {
|
||||
int n = 0;
|
||||
for (int i = 0; i < source.accumulatorCount; i++) {
|
||||
if (source.accumulators[i].OutputTarget != null) {
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<REF>, IEnumerator {
|
||||
AccumulatorPipeline<REF, T> source;
|
||||
int index;
|
||||
public Enumerator(AccumulatorPipeline<REF, T> source) {
|
||||
this.source = source;
|
||||
index = -1;
|
||||
}
|
||||
public REF Current { get { return source.accumulators[index].OutputTarget; } }
|
||||
object IEnumerator.Current => throw new NotImplementedException();
|
||||
public void Dispose() { }
|
||||
public bool MoveNext() {
|
||||
index += 1;
|
||||
if (index >= source.accumulatorCount) {
|
||||
return false;
|
||||
}
|
||||
if (Current == null) {
|
||||
return MoveNext();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public void Reset() { index = -1; }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
11
Assets/SensorToolkit/Sensors/src/AccumulatorPipeline.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/src/AccumulatorPipeline.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 372a7af7ced857b46af861b11ad5b2a2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
97
Assets/SensorToolkit/Sensors/src/AngleEnumerator.cs
Normal file
97
Assets/SensorToolkit/Sensors/src/AngleEnumerator.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
|
||||
public struct AngleEnumerator {
|
||||
public enum AngleMethodType { Center, Origin, BoundingBox }
|
||||
public enum SortByType { HorizontalAngle, CentralAngle }
|
||||
|
||||
public AngleMethodType AngleMethod;
|
||||
public SortByType SortBy;
|
||||
|
||||
public List<AngleResult> results { get; private set; }
|
||||
|
||||
public static AngleEnumerator Create() => new AngleEnumerator {
|
||||
results = new List<AngleResult>()
|
||||
};
|
||||
|
||||
public void Clear() {
|
||||
results.Clear();
|
||||
}
|
||||
|
||||
public void Calculate(ReferenceFrame frame, FOVRange fov, List<Signal> signals) {
|
||||
Clear();
|
||||
fov.Distance *= fov.Distance;
|
||||
foreach (var signal in signals) {
|
||||
var angles =
|
||||
AngleMethod == AngleMethodType.Origin ? frame.AngleTo(signal.Object.transform.position) :
|
||||
AngleMethod == AngleMethodType.Center ? frame.AngleTo(signal.Bounds.center) :
|
||||
AngleMethod == AngleMethodType.BoundingBox ? frame.AngleTo(signal.Bounds) :
|
||||
default;
|
||||
var quadrance = signal.Bounds.SqrDistance(frame.Position);
|
||||
if (!fov.Contains(angles, quadrance)) {
|
||||
continue;
|
||||
}
|
||||
var result = new AngleResult {
|
||||
Object = signal.Object,
|
||||
Angles = angles,
|
||||
CentralAngle = angles.GetCentralAngle(),
|
||||
Distance = quadrance
|
||||
};
|
||||
results.Add(result);
|
||||
}
|
||||
if (SortBy == SortByType.HorizontalAngle) {
|
||||
results.Sort(AngleResult.CompareHorizAngle);
|
||||
} else if (SortBy == SortByType.CentralAngle) {
|
||||
results.Sort(AngleResult.CompareCentralAngle);
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawGizmos() {
|
||||
SensorGizmos.PushColor(Color.black);
|
||||
int i = 0;
|
||||
foreach (var result in results) {
|
||||
SensorGizmos.Label(result.Object.transform.position, $"Index: {i}\n({result.Angles.HorizAngle.ToString("N1")},{result.Angles.VertAngle.ToString("N1")})");
|
||||
i++;
|
||||
}
|
||||
SensorGizmos.PopColor();
|
||||
}
|
||||
|
||||
public struct AngleResult {
|
||||
public GameObject Object;
|
||||
public ViewAngles Angles;
|
||||
public float CentralAngle;
|
||||
public float Distance;
|
||||
public static int CompareCentralAngle(AngleResult r1, AngleResult r2) {
|
||||
var angleDiff = r1.CentralAngle - r2.CentralAngle;
|
||||
if (angleDiff != 0f) {
|
||||
return angleDiff > 0f ? 1 : -1;
|
||||
}
|
||||
var distanceDiff = r1.Distance - r2.Distance;
|
||||
if (distanceDiff != 0f) {
|
||||
return distanceDiff > 0f ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
public static int CompareHorizAngle(AngleResult r1, AngleResult r2) {
|
||||
//var a1 = r1.Coords.HorizAngle >= 0 ? r1.Coords.HorizAngle : 360f + r1.Coords.HorizAngle;
|
||||
//var a2 = r2.Coords.HorizAngle >= 0 ? r2.Coords.HorizAngle : 360f + r2.Coords.HorizAngle;
|
||||
var a1 = r1.Angles.HorizAngle;
|
||||
var a2 = r2.Angles.HorizAngle;
|
||||
var angleDiff = a1 - a2;
|
||||
if (angleDiff != 0f) {
|
||||
return angleDiff > 0f ? 1 : -1;
|
||||
}
|
||||
var distanceDiff = r1.Distance - r2.Distance;
|
||||
if (distanceDiff != 0f) {
|
||||
return distanceDiff > 0f ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
11
Assets/SensorToolkit/Sensors/src/AngleEnumerator.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/src/AngleEnumerator.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e77fc9afd958c154c9e4cfe6dc9067ed
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
202
Assets/SensorToolkit/Sensors/src/BaseAreaSensor.cs
Normal file
202
Assets/SensorToolkit/Sensors/src/BaseAreaSensor.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Micosmo.SensorToolkit
|
||||
{
|
||||
/*
|
||||
* Common functionality for 2D sensors that detect colliders within an area such as the Range2D and Trigger2D Sensors.
|
||||
*/
|
||||
public abstract class BaseAreaSensor : Sensor {
|
||||
|
||||
#region Configurations
|
||||
[SerializeField]
|
||||
SignalFilter signalFilter = new SignalFilter();
|
||||
|
||||
[Tooltip("In Collider mode the sensor detects GameObjects attached to colliders. In RigidBody mode it detects the RigidBody GameObject attached to colliders.")]
|
||||
public DetectionModes DetectionMode;
|
||||
#endregion
|
||||
|
||||
#region Public
|
||||
// Edit the IgnoreList at runtime. Anything in the list will not be detected
|
||||
public List<GameObject> IgnoreList => signalFilter.IgnoreList;
|
||||
|
||||
// Enable/Disable the tag filtering at runtime
|
||||
public bool EnableTagFilter {
|
||||
get => signalFilter.EnableTagFilter;
|
||||
set => signalFilter.EnableTagFilter = value;
|
||||
}
|
||||
|
||||
// Change the allowed tags at runtime
|
||||
public string[] AllowedTags {
|
||||
get => signalFilter.AllowedTags;
|
||||
set => signalFilter.AllowedTags = value;
|
||||
}
|
||||
|
||||
public override void Clear() {
|
||||
base.Clear();
|
||||
ClearColliders();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Protected
|
||||
protected override List<Collider2D> GetInputColliders(GameObject inputObject, List<Collider2D> storeIn) {
|
||||
List<Collider2D> clist;
|
||||
if (gameObjectColliders.TryGetValue(inputObject, out clist)) {
|
||||
foreach (var c in clist) {
|
||||
storeIn.Add(c);
|
||||
}
|
||||
}
|
||||
return storeIn;
|
||||
}
|
||||
|
||||
protected override void InitialiseSignalProcessors() {
|
||||
base.InitialiseSignalProcessors();
|
||||
MapToSignalProxy.Configure(true);
|
||||
SignalFilter = signalFilter;
|
||||
}
|
||||
|
||||
protected void UpdateAllSignals() {
|
||||
workList.Clear();
|
||||
|
||||
foreach (var cols in gameObjectColliders) {
|
||||
Signal signal;
|
||||
if (CalculateSignal(cols.Value, out signal)) {
|
||||
workList.Add(signal);
|
||||
}
|
||||
}
|
||||
MapToRigidBody.Configure(DetectionMode, true);
|
||||
UpdateAllSignals(workList);
|
||||
}
|
||||
|
||||
protected void AddCollider(Collider2D c, bool updateSignal) {
|
||||
var cols = AddColliderToMap(c, c.gameObject, gameObjectColliders);
|
||||
|
||||
if (!updateSignal) {
|
||||
return;
|
||||
}
|
||||
MapToRigidBody.Configure(DetectionMode, true);
|
||||
Signal signal;
|
||||
if (CalculateSignal(cols, out signal)) {
|
||||
UpdateSignalImmediate(signal);
|
||||
}
|
||||
}
|
||||
|
||||
protected void RemoveCollider(Collider2D c, bool updateSignal) {
|
||||
if (c == null) {
|
||||
ClearDestroyedGameObjects();
|
||||
return;
|
||||
}
|
||||
|
||||
var cols = RemoveColliderFromMap(c, c.gameObject, gameObjectColliders);
|
||||
|
||||
if (!updateSignal) {
|
||||
return;
|
||||
}
|
||||
MapToRigidBody.Configure(DetectionMode, true);
|
||||
if (cols == null) {
|
||||
LostSignalImmediate(c.gameObject);
|
||||
} else {
|
||||
Signal signal;
|
||||
if (CalculateSignal(cols, out signal)) {
|
||||
UpdateSignalImmediate(signal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void ClearColliders() {
|
||||
foreach (var set in gameObjectColliders) {
|
||||
colliderListCache.Dispose(set.Value);
|
||||
}
|
||||
gameObjectColliders.Clear();
|
||||
}
|
||||
|
||||
List<Collider2D> colliderList = new List<Collider2D>();
|
||||
protected List<Collider2D> GetColliders() {
|
||||
colliderList.Clear();
|
||||
foreach (var set in gameObjectColliders) {
|
||||
foreach (var collider in set.Value) {
|
||||
colliderList.Add(collider);
|
||||
}
|
||||
}
|
||||
return colliderList;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internals
|
||||
// Maps a GameObject to a list of it's colliders that have been detected.
|
||||
Dictionary<GameObject, List<Collider2D>> gameObjectColliders = new Dictionary<GameObject, List<Collider2D>>();
|
||||
|
||||
// List of temporary values for modifying collections
|
||||
List<GameObject> gameObjectList = new List<GameObject>();
|
||||
List<Signal> workList = new List<Signal>();
|
||||
|
||||
static ListCache<Collider2D> colliderListCache = new ListCache<Collider2D>();
|
||||
|
||||
void ClearDestroyedGameObjects() {
|
||||
gameObjectList.Clear();
|
||||
foreach (var set in gameObjectColliders) {
|
||||
if (set.Key == null) {
|
||||
gameObjectList.Add(set.Key);
|
||||
}
|
||||
}
|
||||
foreach (var go in gameObjectList) {
|
||||
colliderListCache.Dispose(gameObjectColliders[go]);
|
||||
gameObjectColliders.Remove(go);
|
||||
}
|
||||
}
|
||||
|
||||
List<Collider2D> AddColliderToMap(Collider2D c, GameObject go, Dictionary<GameObject, List<Collider2D>> dict) {
|
||||
List<Collider2D> colliderList;
|
||||
if (!dict.TryGetValue(go, out colliderList)) {
|
||||
colliderList = colliderListCache.Get();
|
||||
dict[go] = colliderList;
|
||||
}
|
||||
if (!colliderList.Contains(c)) {
|
||||
colliderList.Add(c);
|
||||
}
|
||||
return colliderList;
|
||||
}
|
||||
|
||||
List<Collider2D> RemoveColliderFromMap(Collider2D c, GameObject go, Dictionary<GameObject, List<Collider2D>> dict) {
|
||||
List<Collider2D> colliderList = null;
|
||||
if (dict.TryGetValue(go, out colliderList)) {
|
||||
colliderList.Remove(c);
|
||||
if (colliderList.Count == 0) {
|
||||
dict.Remove(go);
|
||||
colliderListCache.Dispose(colliderList);
|
||||
colliderList = null;
|
||||
}
|
||||
}
|
||||
return colliderList;
|
||||
}
|
||||
|
||||
bool CalculateSignal(List<Collider2D> colliders, out Signal signal) {
|
||||
signal = default;
|
||||
|
||||
Bounds bounds = new Bounds();
|
||||
bool anyFound = false;
|
||||
foreach (var c in colliders) {
|
||||
if (!signalFilter.TestCollider(c)) {
|
||||
continue;
|
||||
}
|
||||
if (!anyFound) {
|
||||
bounds = c.bounds;
|
||||
anyFound = true;
|
||||
} else {
|
||||
bounds.Encapsulate(c.bounds);
|
||||
}
|
||||
}
|
||||
|
||||
var obj = colliders[0].gameObject;
|
||||
signal = new Signal {
|
||||
Object = obj,
|
||||
Shape = new Bounds(bounds.center - obj.transform.position, bounds.size),
|
||||
Strength = 1f
|
||||
};
|
||||
|
||||
return anyFound;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
12
Assets/SensorToolkit/Sensors/src/BaseAreaSensor.cs.meta
Normal file
12
Assets/SensorToolkit/Sensors/src/BaseAreaSensor.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d2980a7f0c34d2199b27bb0ec29c6ff
|
||||
timeCreated: 1491308888
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
82
Assets/SensorToolkit/Sensors/src/BasePulsableSensor.cs
Normal file
82
Assets/SensorToolkit/Sensors/src/BasePulsableSensor.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
|
||||
public abstract class BasePulsableSensor : MonoBehaviour {
|
||||
|
||||
// User-defined description for this sensor.
|
||||
// Enable in SensorToolkit/Settings to view in inspectors
|
||||
[SerializeField] string comment;
|
||||
|
||||
PulseHandle runningPulse;
|
||||
PulseHandle pendingPulse;
|
||||
|
||||
// Should cause the sensor to perform it's 'sensing' routine, so that its list of detected objects
|
||||
// is up to date at the time of calling. Each sensor can be configured to pulse automatically at
|
||||
// fixed intervals or each timestep, however, if you need more control over when this occurs then
|
||||
// you can call this method manually.
|
||||
public void Pulse() {
|
||||
if (!isActiveAndEnabled) {
|
||||
return;
|
||||
}
|
||||
if (!runningPulse.TryCancel()) {
|
||||
runningPulse.Complete();
|
||||
}
|
||||
if (!pendingPulse.TryCancel()) {
|
||||
pendingPulse.Complete();
|
||||
}
|
||||
GetPulseJob().Run();
|
||||
}
|
||||
|
||||
// If this sensor has input sensors, then the inputs are pulsed first and then this one is pulsed.
|
||||
public abstract void PulseAll();
|
||||
|
||||
public PulseHandle SchedulePulse() {
|
||||
if (!isActiveAndEnabled) {
|
||||
return default;
|
||||
}
|
||||
if (runningPulse.IsCompleted && !pendingPulse.IsCompleted) {
|
||||
runningPulse = pendingPulse;
|
||||
pendingPulse = default;
|
||||
}
|
||||
|
||||
if (!runningPulse.IsCompleted) {
|
||||
if (!pendingPulse.IsCompleted) {
|
||||
return pendingPulse;
|
||||
}
|
||||
pendingPulse = GetPulseJob().Schedule(runningPulse);
|
||||
pendingPulse.Tick();
|
||||
return pendingPulse;
|
||||
}
|
||||
|
||||
runningPulse = GetPulseJob().Schedule();
|
||||
runningPulse.Tick();
|
||||
return runningPulse;
|
||||
}
|
||||
|
||||
public abstract event System.Action OnPulsed;
|
||||
|
||||
public abstract void Clear();
|
||||
|
||||
public bool ShowDetectionGizmos { get; set; }
|
||||
|
||||
protected abstract PulseJob GetPulseJob();
|
||||
|
||||
protected void ClearPendingPulse() {
|
||||
if (!runningPulse.TryCancel()) {
|
||||
runningPulse.Complete();
|
||||
}
|
||||
if (!pendingPulse.TryCancel()) {
|
||||
pendingPulse.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnDisable() {
|
||||
ClearPendingPulse();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
11
Assets/SensorToolkit/Sensors/src/BasePulsableSensor.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/src/BasePulsableSensor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7129a068598c426bb92759e418b30fdb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
202
Assets/SensorToolkit/Sensors/src/BaseVolumeSensor.cs
Normal file
202
Assets/SensorToolkit/Sensors/src/BaseVolumeSensor.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
/*
|
||||
* Common functionality for sensors that detect colliders within a volume such as the Range and Trigger Sensors.
|
||||
*/
|
||||
public abstract class BaseVolumeSensor : Sensor {
|
||||
|
||||
#region Configurations
|
||||
[SerializeField]
|
||||
SignalFilter signalFilter = new SignalFilter();
|
||||
|
||||
[Tooltip("In Collider mode the sensor detects GameObjects attached to colliders. In RigidBody mode it detects the RigidBody GameObject attached to colliders.")]
|
||||
public DetectionModes DetectionMode;
|
||||
#endregion
|
||||
|
||||
#region Public
|
||||
// Edit the IgnoreList at runtime. Anything in the list will not be detected
|
||||
public List<GameObject> IgnoreList => signalFilter.IgnoreList;
|
||||
|
||||
// Enable/Disable the tag filtering at runtime
|
||||
public bool EnableTagFilter {
|
||||
get => signalFilter.EnableTagFilter;
|
||||
set => signalFilter.EnableTagFilter = value;
|
||||
}
|
||||
|
||||
// Change the allowed tags at runtime
|
||||
public string[] AllowedTags {
|
||||
get => signalFilter.AllowedTags;
|
||||
set => signalFilter.AllowedTags = value;
|
||||
}
|
||||
|
||||
public override void Clear() {
|
||||
base.Clear();
|
||||
ClearColliders();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Protected
|
||||
protected override List<Collider> GetInputColliders(GameObject inputObject, List<Collider> storeIn) {
|
||||
List<Collider> clist;
|
||||
if (gameObjectColliders.TryGetValue(inputObject, out clist)) {
|
||||
foreach (var c in clist) {
|
||||
storeIn.Add(c);
|
||||
}
|
||||
}
|
||||
return storeIn;
|
||||
}
|
||||
|
||||
protected override void InitialiseSignalProcessors() {
|
||||
base.InitialiseSignalProcessors();
|
||||
MapToSignalProxy.Configure(true);
|
||||
SignalFilter = signalFilter;
|
||||
}
|
||||
|
||||
protected void UpdateAllSignals() {
|
||||
workList.Clear();
|
||||
foreach (var cols in gameObjectColliders) {
|
||||
Signal signal;
|
||||
if (CalculateSignal(cols.Value, out signal)) {
|
||||
workList.Add(signal);
|
||||
}
|
||||
}
|
||||
MapToRigidBody.Configure(DetectionMode, false);
|
||||
UpdateAllSignals(workList);
|
||||
}
|
||||
|
||||
protected void AddCollider(Collider c, bool updateSignal) {
|
||||
var cols = AddColliderToMap(c, c.gameObject, gameObjectColliders);
|
||||
|
||||
if (!updateSignal) {
|
||||
return;
|
||||
}
|
||||
MapToRigidBody.Configure(DetectionMode, false);
|
||||
Signal signal;
|
||||
if (CalculateSignal(cols, out signal)) {
|
||||
UpdateSignalImmediate(signal);
|
||||
}
|
||||
}
|
||||
|
||||
protected void RemoveCollider(Collider c, bool updateSignal) {
|
||||
if (c == null) {
|
||||
ClearDestroyedGameObjects();
|
||||
return;
|
||||
}
|
||||
|
||||
var cols = RemoveColliderFromMap(c, c.gameObject, gameObjectColliders);
|
||||
|
||||
if (!updateSignal) {
|
||||
return;
|
||||
}
|
||||
MapToRigidBody.Configure(DetectionMode, false);
|
||||
if (cols == null) {
|
||||
LostSignalImmediate(c.gameObject);
|
||||
} else {
|
||||
Signal signal;
|
||||
if (CalculateSignal(cols, out signal)) {
|
||||
UpdateSignalImmediate(signal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void ClearColliders() {
|
||||
foreach (var set in gameObjectColliders) {
|
||||
colliderListCache.Dispose(set.Value);
|
||||
}
|
||||
gameObjectColliders.Clear();
|
||||
}
|
||||
|
||||
List<Collider> colliderList = new List<Collider>();
|
||||
protected List<Collider> GetColliders() {
|
||||
colliderList.Clear();
|
||||
foreach (var set in gameObjectColliders) {
|
||||
foreach (var collider in set.Value) {
|
||||
colliderList.Add(collider);
|
||||
}
|
||||
}
|
||||
return colliderList;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internals
|
||||
// Maps a GameObject to a list of it's colliders that have been detected.
|
||||
List<Collider> colliders = new List<Collider>();
|
||||
Dictionary<GameObject, List<Collider>> gameObjectColliders = new Dictionary<GameObject, List<Collider>>();
|
||||
|
||||
// List of temporary values for modifying collections
|
||||
List<GameObject> gameObjectList = new List<GameObject>();
|
||||
List<Signal> workList = new List<Signal>();
|
||||
|
||||
static ListCache<Collider> colliderListCache = new ListCache<Collider>();
|
||||
|
||||
void ClearDestroyedGameObjects() {
|
||||
gameObjectList.Clear();
|
||||
foreach (var set in gameObjectColliders) {
|
||||
if (set.Key == null) {
|
||||
gameObjectList.Add(set.Key);
|
||||
}
|
||||
}
|
||||
foreach (var go in gameObjectList) {
|
||||
colliderListCache.Dispose(gameObjectColliders[go]);
|
||||
gameObjectColliders.Remove(go);
|
||||
}
|
||||
}
|
||||
|
||||
List<Collider> AddColliderToMap(Collider c, GameObject go, Dictionary<GameObject, List<Collider>> dict) {
|
||||
List<Collider> colliderList;
|
||||
if (!dict.TryGetValue(go, out colliderList)) {
|
||||
colliderList = colliderListCache.Get();
|
||||
dict[go] = colliderList;
|
||||
}
|
||||
if (!colliderList.Contains(c)) {
|
||||
colliderList.Add(c);
|
||||
}
|
||||
return colliderList;
|
||||
}
|
||||
|
||||
List<Collider> RemoveColliderFromMap(Collider c, GameObject go, Dictionary<GameObject, List<Collider>> dict) {
|
||||
List<Collider> colliderList = null;
|
||||
if (dict.TryGetValue(go, out colliderList)) {
|
||||
colliderList.Remove(c);
|
||||
if (colliderList.Count == 0) {
|
||||
dict.Remove(go);
|
||||
colliderListCache.Dispose(colliderList);
|
||||
colliderList = null;
|
||||
}
|
||||
}
|
||||
return colliderList;
|
||||
}
|
||||
|
||||
bool CalculateSignal(List<Collider> colliders, out Signal signal) {
|
||||
signal = default;
|
||||
|
||||
Bounds bounds = new Bounds();
|
||||
bool anyFound = false;
|
||||
foreach (var c in colliders) {
|
||||
if (!signalFilter.TestCollider(c)) {
|
||||
continue;
|
||||
}
|
||||
if (!anyFound) {
|
||||
bounds = c.bounds;
|
||||
anyFound = true;
|
||||
} else {
|
||||
bounds.Encapsulate(c.bounds);
|
||||
}
|
||||
}
|
||||
|
||||
var obj = colliders[0].gameObject;
|
||||
var position = obj.transform.position;
|
||||
signal = new Signal {
|
||||
Object = obj,
|
||||
Shape = new Bounds(bounds.center - position, bounds.size),
|
||||
Strength = 1f
|
||||
};
|
||||
|
||||
return anyFound;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
12
Assets/SensorToolkit/Sensors/src/BaseVolumeSensor.cs.meta
Normal file
12
Assets/SensorToolkit/Sensors/src/BaseVolumeSensor.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d62bea7337d540409a9678c0a6e8f081
|
||||
timeCreated: 1489882031
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
40
Assets/SensorToolkit/Sensors/src/BufferedPhysics.cs
Normal file
40
Assets/SensorToolkit/Sensors/src/BufferedPhysics.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
|
||||
public interface ITestNonAlloc<S, T> where S : Sensor {
|
||||
int Test(S sensor, T[] results);
|
||||
}
|
||||
|
||||
public class PhysicsNonAlloc<T> {
|
||||
public static int InitialSize = 20;
|
||||
public static bool DynamicallyIncreaseBufferSize = true;
|
||||
|
||||
public T[] Buffer { get; private set; }
|
||||
public int Count { get; private set; }
|
||||
|
||||
public bool IsAtCapacity { get { return Count == Buffer.Length; } }
|
||||
|
||||
public PhysicsNonAlloc() {
|
||||
Expand(Mathf.Max(InitialSize, 1));
|
||||
}
|
||||
|
||||
public int PerformTest<S>(S sensor, ITestNonAlloc<S, T> tester) where S : Sensor {
|
||||
Count = tester.Test(sensor, Buffer);
|
||||
|
||||
if (Count == Buffer.Length && DynamicallyIncreaseBufferSize) {
|
||||
Expand(Count * 2);
|
||||
return PerformTest(sensor, tester);
|
||||
}
|
||||
|
||||
return Count;
|
||||
}
|
||||
|
||||
void Expand(int toSize) {
|
||||
Buffer = new T[toSize];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
12
Assets/SensorToolkit/Sensors/src/BufferedPhysics.cs.meta
Normal file
12
Assets/SensorToolkit/Sensors/src/BufferedPhysics.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1514ea5919444691b9b9cde7cb0cf94a
|
||||
timeCreated: 1601161652
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
156
Assets/SensorToolkit/Sensors/src/DrawIfAttribute.cs
Normal file
156
Assets/SensorToolkit/Sensors/src/DrawIfAttribute.cs
Normal file
@@ -0,0 +1,156 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace Micosmo.SensorToolkit {
|
||||
|
||||
/// <summary>
|
||||
/// Draws the field/property ONLY if the compared property compared by the comparison type with the value of comparedValue returns true.
|
||||
/// Based on: https://forum.unity.com/threads/draw-a-field-only-if-a-condition-is-met.448855/
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
|
||||
internal class DrawIfAttribute : PropertyAttribute {
|
||||
#region Fields
|
||||
|
||||
public string comparedPropertyName { get; private set; }
|
||||
public object comparedValue { get; private set; }
|
||||
public DisablingType disablingType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Types of comperisons.
|
||||
/// </summary>
|
||||
public enum DisablingType {
|
||||
ReadOnly = 2,
|
||||
DontDraw = 3
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Only draws the field only if a condition is met. Supports enum and bools.
|
||||
/// </summary>
|
||||
/// <param name="comparedPropertyName">The name of the property that is being compared (case sensitive).</param>
|
||||
/// <param name="comparedValue">The value the property is being compared to.</param>
|
||||
/// <param name="disablingType">The type of disabling that should happen if the condition is NOT met. Defaulted to DisablingType.DontDraw.</param>
|
||||
public DrawIfAttribute(string comparedPropertyName, object comparedValue, DisablingType disablingType = DisablingType.DontDraw) {
|
||||
this.comparedPropertyName = comparedPropertyName;
|
||||
this.comparedValue = comparedValue;
|
||||
this.disablingType = disablingType;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
namespace Micosmo.SensorToolkit.Editors {
|
||||
|
||||
/// <summary>
|
||||
/// Based on: https://forum.unity.com/threads/draw-a-field-only-if-a-condition-is-met.448855/
|
||||
/// </summary>
|
||||
[CustomPropertyDrawer(typeof(DrawIfAttribute))]
|
||||
public class DrawIfPropertyDrawer : PropertyDrawer {
|
||||
#region Fields
|
||||
|
||||
// Reference to the attribute on the property.
|
||||
DrawIfAttribute drawIf;
|
||||
|
||||
// Field that is being compared.
|
||||
SerializedProperty comparedField;
|
||||
|
||||
#endregion
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
|
||||
if (!ShowMe(property) && drawIf.disablingType == DrawIfAttribute.DisablingType.DontDraw) {
|
||||
return -EditorGUIUtility.standardVerticalSpacing;
|
||||
} else {
|
||||
if (property.propertyType == SerializedPropertyType.Generic) {
|
||||
int numChildren = 0;
|
||||
float totalHeight = 0.0f;
|
||||
|
||||
var children = property.GetEnumerator();
|
||||
|
||||
while (children.MoveNext()) {
|
||||
SerializedProperty child = children.Current as SerializedProperty;
|
||||
|
||||
GUIContent childLabel = new GUIContent(child.displayName);
|
||||
|
||||
totalHeight += EditorGUI.GetPropertyHeight(child, childLabel) + EditorGUIUtility.standardVerticalSpacing;
|
||||
numChildren++;
|
||||
}
|
||||
|
||||
// Remove extra space at end, (we only want spaces between items)
|
||||
totalHeight -= EditorGUIUtility.standardVerticalSpacing;
|
||||
|
||||
return totalHeight;
|
||||
}
|
||||
|
||||
return EditorGUI.GetPropertyHeight(property, label);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Errors default to showing the property.
|
||||
/// </summary>
|
||||
private bool ShowMe(SerializedProperty property) {
|
||||
drawIf = attribute as DrawIfAttribute;
|
||||
// Replace propertyname to the value from the parameter
|
||||
string path = property.propertyPath.Contains(".") ? System.IO.Path.ChangeExtension(property.propertyPath, drawIf.comparedPropertyName) : drawIf.comparedPropertyName;
|
||||
|
||||
comparedField = property.serializedObject.FindProperty(path);
|
||||
|
||||
if (comparedField == null) {
|
||||
Debug.LogError("Cannot find property with name: " + path);
|
||||
return true;
|
||||
}
|
||||
|
||||
// get the value & compare based on types
|
||||
switch (comparedField.type) { // Possible extend cases to support your own type
|
||||
case "bool":
|
||||
return comparedField.boolValue.Equals(drawIf.comparedValue);
|
||||
case "Enum":
|
||||
return comparedField.enumValueIndex.Equals((int)drawIf.comparedValue);
|
||||
default:
|
||||
Debug.LogError("Error: " + comparedField.type + " is not supported of " + path);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
|
||||
// If the condition is met, simply draw the field.
|
||||
if (ShowMe(property)) {
|
||||
// A Generic type means a custom class...
|
||||
if (property.propertyType == SerializedPropertyType.Generic) {
|
||||
var children = property.GetEnumerator();
|
||||
|
||||
Rect offsetPosition = position;
|
||||
|
||||
while (children.MoveNext()) {
|
||||
SerializedProperty child = children.Current as SerializedProperty;
|
||||
|
||||
GUIContent childLabel = new GUIContent(child.displayName);
|
||||
|
||||
float childHeight = EditorGUI.GetPropertyHeight(child, childLabel);
|
||||
offsetPosition.height = childHeight;
|
||||
|
||||
EditorGUI.PropertyField(offsetPosition, child, childLabel);
|
||||
|
||||
offsetPosition.y += childHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
}
|
||||
} else {
|
||||
EditorGUI.PropertyField(position, property, label);
|
||||
}
|
||||
|
||||
} //...check if the disabling type is read only. If it is, draw it disabled
|
||||
else if (drawIf.disablingType == DrawIfAttribute.DisablingType.ReadOnly) {
|
||||
GUI.enabled = false;
|
||||
EditorGUI.PropertyField(position, property, label);
|
||||
GUI.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
11
Assets/SensorToolkit/Sensors/src/DrawIfAttribute.cs.meta
Normal file
11
Assets/SensorToolkit/Sensors/src/DrawIfAttribute.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 258e14d7ad06f12429ff0ebf28166fcd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
9
Assets/SensorToolkit/Sensors/src/Editor.meta
Normal file
9
Assets/SensorToolkit/Sensors/src/Editor.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 259cbc14df0e41919b6fa5f3c0020f64
|
||||
folderAsset: yes
|
||||
timeCreated: 1488607388
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
127
Assets/SensorToolkit/Sensors/src/Editor/ArcSensor2DEditor.cs
Normal file
127
Assets/SensorToolkit/Sensors/src/Editor/ArcSensor2DEditor.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Micosmo.SensorToolkit.Editors {
|
||||
|
||||
[CustomEditor(typeof(ArcSensor2D))]
|
||||
[CanEditMultipleObjects]
|
||||
public class ArcSensor2DEditor : BaseSensorEditor<ArcSensor2D> {
|
||||
SerializedProperty parameterisation;
|
||||
SerializedProperty bezier;
|
||||
SerializedProperty ballistic;
|
||||
SerializedProperty ignoreList;
|
||||
SerializedProperty tagFilterEnabled;
|
||||
SerializedProperty tagFilter;
|
||||
SerializedProperty detectsOnLayers;
|
||||
SerializedProperty detectionMode;
|
||||
SerializedProperty ignoreTriggerColliders;
|
||||
SerializedProperty signalProcessors;
|
||||
SerializedProperty obstructedByLayers;
|
||||
SerializedProperty worldSpace;
|
||||
SerializedProperty pulseMode;
|
||||
SerializedProperty pulseUpdateFunction;
|
||||
SerializedProperty pulseInterval;
|
||||
SerializedProperty onDetected;
|
||||
SerializedProperty onLostDetection;
|
||||
SerializedProperty onSomeDetection;
|
||||
SerializedProperty onNoDetection;
|
||||
SerializedProperty onObstructed;
|
||||
SerializedProperty onClear;
|
||||
|
||||
bool showEvents = false;
|
||||
|
||||
protected override bool canTest => true;
|
||||
|
||||
protected override void OnEnable() {
|
||||
base.OnEnable();
|
||||
|
||||
if (serializedObject == null) return;
|
||||
|
||||
parameterisation = serializedObject.FindProperty("Parameterisation");
|
||||
bezier = serializedObject.FindProperty("Bezier");
|
||||
ballistic = serializedObject.FindProperty("Ballistic");
|
||||
ignoreList = serializedObject.FindProperty("signalFilter.IgnoreList");
|
||||
tagFilterEnabled = serializedObject.FindProperty("signalFilter.EnableTagFilter");
|
||||
tagFilter = serializedObject.FindProperty("signalFilter.AllowedTags");
|
||||
detectsOnLayers = serializedObject.FindProperty("DetectsOnLayers");
|
||||
detectionMode = serializedObject.FindProperty("DetectionMode");
|
||||
ignoreTriggerColliders = serializedObject.FindProperty("IgnoreTriggerColliders");
|
||||
signalProcessors = serializedObject.FindProperty("signalProcessors");
|
||||
obstructedByLayers = serializedObject.FindProperty("ObstructedByLayers");
|
||||
worldSpace = serializedObject.FindProperty("WorldSpace");
|
||||
pulseMode = serializedObject.FindProperty("pulseRoutine.Mode");
|
||||
pulseUpdateFunction = serializedObject.FindProperty("pulseRoutine.UpdateFunction");
|
||||
pulseInterval = serializedObject.FindProperty("pulseRoutine.Interval");
|
||||
onDetected = serializedObject.FindProperty("OnDetected");
|
||||
onLostDetection = serializedObject.FindProperty("OnLostDetection");
|
||||
onSomeDetection = serializedObject.FindProperty("OnSomeDetection");
|
||||
onNoDetection = serializedObject.FindProperty("OnNoDetection");
|
||||
onObstructed = serializedObject.FindProperty("onObstruction");
|
||||
onClear = serializedObject.FindProperty("onClear");
|
||||
}
|
||||
|
||||
protected override void InspectorParameters() {
|
||||
EditorGUILayout.PropertyField(parameterisation);
|
||||
if (sensor.Parameterisation == ArcSensor2D.ParameterisationType.Bezier) {
|
||||
EditorUtils.InlinePropertyField(bezier);
|
||||
} else if (sensor.Parameterisation == ArcSensor2D.ParameterisationType.Ballistic) {
|
||||
EditorUtils.InlinePropertyField(ballistic);
|
||||
}
|
||||
EditorGUILayout.PropertyField(worldSpace);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(ignoreList, true);
|
||||
EditorGUILayout.PropertyField(tagFilterEnabled);
|
||||
if (tagFilterEnabled.boolValue) {
|
||||
EditorGUILayout.PropertyField(tagFilter, true);
|
||||
}
|
||||
EditorGUILayout.PropertyField(detectsOnLayers);
|
||||
EditorGUILayout.PropertyField(detectionMode);
|
||||
EditorGUILayout.PropertyField(ignoreTriggerColliders);
|
||||
EditorGUILayout.PropertyField(signalProcessors, new GUIContent("Signal Processors"));
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(obstructedByLayers);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(pulseMode, new GUIContent("Pulse Mode"));
|
||||
if (sensor.PulseMode != PulseRoutine.Modes.Manual) {
|
||||
EditorGUILayout.PropertyField(pulseUpdateFunction);
|
||||
}
|
||||
if (sensor.PulseMode == PulseRoutine.Modes.FixedInterval) {
|
||||
EditorGUILayout.PropertyField(pulseInterval, new GUIContent("Pulse Interval"));
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (showEvents = EditorGUILayout.Foldout(showEvents, "Events")) {
|
||||
EditorGUILayout.PropertyField(onDetected);
|
||||
EditorGUILayout.PropertyField(onLostDetection);
|
||||
EditorGUILayout.PropertyField(onSomeDetection);
|
||||
EditorGUILayout.PropertyField(onNoDetection);
|
||||
EditorGUILayout.PropertyField(onObstructed);
|
||||
EditorGUILayout.PropertyField(onClear);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
BufferSizeInfo(sensor.CurrentBufferSize);
|
||||
}
|
||||
|
||||
protected override void InspectorDetectedObjects() {
|
||||
base.InspectorDetectedObjects();
|
||||
|
||||
if (!sensor.IsObstructed) return;
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Arc is Obstructed", new GUIStyle() { fontStyle = FontStyle.Bold, normal = new GUIStyleState() { textColor = STPrefs.RedEditorTextColour } });
|
||||
DetectedObjectFieldLayout(sensor.GetObstructionRayHit().GameObject);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3836000b75c34e6a94ea1d518fbef050
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
128
Assets/SensorToolkit/Sensors/src/Editor/ArcSensorEditor.cs
Normal file
128
Assets/SensorToolkit/Sensors/src/Editor/ArcSensorEditor.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Micosmo.SensorToolkit.Editors {
|
||||
|
||||
[CustomEditor(typeof(ArcSensor))]
|
||||
[CanEditMultipleObjects]
|
||||
public class ArcSensorEditor : BaseSensorEditor<ArcSensor> {
|
||||
SerializedProperty parameterisation;
|
||||
SerializedProperty bezier;
|
||||
SerializedProperty ballistic;
|
||||
SerializedProperty ignoreList;
|
||||
SerializedProperty tagFilterEnabled;
|
||||
SerializedProperty tagFilter;
|
||||
SerializedProperty detectsOnLayers;
|
||||
SerializedProperty detectionMode;
|
||||
SerializedProperty ignoreTriggerColliders;
|
||||
SerializedProperty signalProcessors;
|
||||
SerializedProperty obstructedByLayers;
|
||||
SerializedProperty worldSpace;
|
||||
SerializedProperty pulseMode;
|
||||
SerializedProperty pulseUpdateFunction;
|
||||
SerializedProperty pulseInterval;
|
||||
SerializedProperty onDetected;
|
||||
SerializedProperty onLostDetection;
|
||||
SerializedProperty onSomeDetection;
|
||||
SerializedProperty onNoDetection;
|
||||
SerializedProperty onObstructed;
|
||||
SerializedProperty onClear;
|
||||
|
||||
bool showEvents = false;
|
||||
|
||||
protected override bool canTest => true;
|
||||
|
||||
protected override void OnEnable() {
|
||||
base.OnEnable();
|
||||
|
||||
if (serializedObject == null) return;
|
||||
|
||||
parameterisation = serializedObject.FindProperty("Parameterisation");
|
||||
bezier = serializedObject.FindProperty("Bezier");
|
||||
ballistic = serializedObject.FindProperty("Ballistic");
|
||||
ignoreList = serializedObject.FindProperty("signalFilter.IgnoreList");
|
||||
tagFilterEnabled = serializedObject.FindProperty("signalFilter.EnableTagFilter");
|
||||
tagFilter = serializedObject.FindProperty("signalFilter.AllowedTags");
|
||||
detectsOnLayers = serializedObject.FindProperty("DetectsOnLayers");
|
||||
detectionMode = serializedObject.FindProperty("DetectionMode");
|
||||
ignoreTriggerColliders = serializedObject.FindProperty("IgnoreTriggerColliders");
|
||||
signalProcessors = serializedObject.FindProperty("signalProcessors");
|
||||
obstructedByLayers = serializedObject.FindProperty("ObstructedByLayers");
|
||||
worldSpace = serializedObject.FindProperty("WorldSpace");
|
||||
pulseMode = serializedObject.FindProperty("pulseRoutine.Mode");
|
||||
pulseUpdateFunction = serializedObject.FindProperty("pulseRoutine.UpdateFunction");
|
||||
pulseInterval = serializedObject.FindProperty("pulseRoutine.Interval");
|
||||
onDetected = serializedObject.FindProperty("OnDetected");
|
||||
onLostDetection = serializedObject.FindProperty("OnLostDetection");
|
||||
onSomeDetection = serializedObject.FindProperty("OnSomeDetection");
|
||||
onNoDetection = serializedObject.FindProperty("OnNoDetection");
|
||||
onObstructed = serializedObject.FindProperty("onObstruction");
|
||||
onClear = serializedObject.FindProperty("onClear");
|
||||
}
|
||||
|
||||
protected override void InspectorParameters() {
|
||||
EditorGUILayout.PropertyField(parameterisation);
|
||||
if (sensor.Parameterisation == ArcSensor.ParameterisationType.Bezier) {
|
||||
EditorUtils.InlinePropertyField(bezier);
|
||||
} else if (sensor.Parameterisation == ArcSensor.ParameterisationType.Ballistic) {
|
||||
EditorUtils.InlinePropertyField(ballistic);
|
||||
}
|
||||
EditorGUILayout.PropertyField(worldSpace);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(ignoreList, true);
|
||||
EditorGUILayout.PropertyField(tagFilterEnabled);
|
||||
if (tagFilterEnabled.boolValue) {
|
||||
EditorGUILayout.PropertyField(tagFilter, true);
|
||||
}
|
||||
EditorGUILayout.PropertyField(detectsOnLayers);
|
||||
EditorGUILayout.PropertyField(detectionMode);
|
||||
EditorGUILayout.PropertyField(ignoreTriggerColliders);
|
||||
EditorGUILayout.PropertyField(signalProcessors, new GUIContent("Signal Processors"));
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(obstructedByLayers);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(pulseMode, new GUIContent("Pulse Mode"));
|
||||
if (sensor.PulseMode != PulseRoutine.Modes.Manual) {
|
||||
EditorGUILayout.PropertyField(pulseUpdateFunction);
|
||||
}
|
||||
if (sensor.PulseMode == PulseRoutine.Modes.FixedInterval) {
|
||||
EditorGUILayout.PropertyField(pulseInterval, new GUIContent("Pulse Interval"));
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (showEvents = EditorGUILayout.Foldout(showEvents, "Events")) {
|
||||
EditorGUILayout.PropertyField(onDetected);
|
||||
EditorGUILayout.PropertyField(onLostDetection);
|
||||
EditorGUILayout.PropertyField(onSomeDetection);
|
||||
EditorGUILayout.PropertyField(onNoDetection);
|
||||
EditorGUILayout.PropertyField(onObstructed);
|
||||
EditorGUILayout.PropertyField(onClear);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
BufferSizeInfo(sensor.CurrentBufferSize);
|
||||
}
|
||||
|
||||
protected override void InspectorDetectedObjects() {
|
||||
base.InspectorDetectedObjects();
|
||||
|
||||
if (!sensor.IsObstructed) return;
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Arc is Obstructed", new GUIStyle() { fontStyle = FontStyle.Bold, normal = new GUIStyleState() { textColor = STPrefs.RedEditorTextColour } });
|
||||
DetectedObjectFieldLayout(sensor.GetObstructionRayHit().GameObject);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b59d8dc7c6f475dbafc42d1973b84aa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
142
Assets/SensorToolkit/Sensors/src/Editor/BasePulsableEditor.cs
Normal file
142
Assets/SensorToolkit/Sensors/src/Editor/BasePulsableEditor.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Micosmo.SensorToolkit.Editors {
|
||||
|
||||
public static class EditorState {
|
||||
public static event System.Action OnStopTesting;
|
||||
public static Observable<BasePulsableSensor> ActivePulsable = new Observable<BasePulsableSensor>();
|
||||
public static void StopAllTesting() {
|
||||
if (EditorApplication.isPlaying || EditorApplication.isPaused) {
|
||||
return;
|
||||
}
|
||||
OnStopTesting?.Invoke();
|
||||
ActivePulsable.Value = null;
|
||||
}
|
||||
[InitializeOnEnterPlayMode]
|
||||
static void OnEnterPlayMode(EnterPlayModeOptions options) {
|
||||
if (options.HasFlag(EnterPlayModeOptions.DisableDomainReload)) {
|
||||
StopAllTesting();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class BasePulsableEditor<T> : Editor where T : BasePulsableSensor {
|
||||
|
||||
T pulsable;
|
||||
protected bool IsTesting = false;
|
||||
protected bool IsActivePulsable => EditorState.ActivePulsable.Value == pulsable;
|
||||
protected abstract bool canTest { get; }
|
||||
|
||||
bool isInGame => EditorApplication.isPlaying || EditorApplication.isPaused;
|
||||
protected bool showDetections => isInGame || IsTesting;
|
||||
|
||||
SerializedProperty comment;
|
||||
|
||||
protected virtual void OnEnable() {
|
||||
if (serializedObject == null) {
|
||||
return;
|
||||
}
|
||||
pulsable = serializedObject.targetObject as T;
|
||||
pulsable.OnPulsed += OnPulsedHandler;
|
||||
EditorState.OnStopTesting += OnStopTestingHandler;
|
||||
EditorState.ActivePulsable.OnChanged += ActivePulsableChangedHandler;
|
||||
|
||||
if ((EditorApplication.isPlaying || EditorApplication.isPaused) && EditorState.ActivePulsable.Value == null) {
|
||||
EditorState.ActivePulsable.Value = pulsable;
|
||||
}
|
||||
|
||||
comment = serializedObject.FindProperty("comment");
|
||||
}
|
||||
|
||||
protected virtual void OnDisable() {
|
||||
EditorState.StopAllTesting();
|
||||
if (IsActivePulsable) {
|
||||
EditorState.ActivePulsable.Value = null;
|
||||
}
|
||||
pulsable.OnPulsed -= OnPulsedHandler;
|
||||
EditorState.OnStopTesting -= OnStopTestingHandler;
|
||||
EditorState.ActivePulsable.OnChanged -= ActivePulsableChangedHandler;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI() {
|
||||
var mb = pulsable as MonoBehaviour;
|
||||
if (mb != null && mb.transform.hasChanged) {
|
||||
EditorState.StopAllTesting();
|
||||
mb.transform.hasChanged = false;
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
var rect = EditorGUILayout.BeginVertical();
|
||||
rect.xMin -= 12; rect.xMax += 2;
|
||||
if (IsActivePulsable) {
|
||||
DrawActive(rect);
|
||||
}
|
||||
if (STPrefs.ShowUserComments) {
|
||||
EditorGUILayout.PropertyField(comment);
|
||||
}
|
||||
OnPulsableGUI();
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (showDetections && !IsActivePulsable) {
|
||||
if (GUILayout.Button("Show Gizmos", GUILayout.Width(100))) {
|
||||
EditorState.ActivePulsable.Value = pulsable;
|
||||
}
|
||||
}
|
||||
if (canTest && !isInGame) {
|
||||
if (GUILayout.Button("Test", GUILayout.Width(100))) {
|
||||
StartTesting();
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
void DrawActive(Rect rect) {
|
||||
var colour = STPrefs.ActiveSensorEditorColour;
|
||||
EditorGUI.DrawRect(rect, colour);
|
||||
}
|
||||
|
||||
protected abstract void OnPulsableGUI();
|
||||
|
||||
void OnPulsedHandler() {
|
||||
Repaint();
|
||||
if (Application.isPlaying || pulsable == null) {
|
||||
return;
|
||||
}
|
||||
IsTesting = true;
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
void StartTesting() {
|
||||
if (Application.isPlaying || pulsable == null) {
|
||||
return;
|
||||
}
|
||||
if (IsTesting) {
|
||||
EditorState.StopAllTesting();
|
||||
}
|
||||
pulsable.PulseAll();
|
||||
EditorState.ActivePulsable.Value = pulsable;
|
||||
}
|
||||
|
||||
void OnStopTestingHandler() {
|
||||
if (!IsTesting || Application.isPlaying || pulsable == null) {
|
||||
return;
|
||||
}
|
||||
IsTesting = false;
|
||||
pulsable.Clear();
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
void ActivePulsableChangedHandler() {
|
||||
pulsable.ShowDetectionGizmos = IsActivePulsable;
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f7a263227ad44aba56cc981feac916b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
106
Assets/SensorToolkit/Sensors/src/Editor/BaseSensorEditor.cs
Normal file
106
Assets/SensorToolkit/Sensors/src/Editor/BaseSensorEditor.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Micosmo.SensorToolkit.Editors {
|
||||
|
||||
public class SensorEditorActions {
|
||||
static double prevPingTime;
|
||||
static GameObject prevPingObject;
|
||||
public static void PingOrSelectObject(GameObject o) {
|
||||
var time = EditorApplication.timeSinceStartup;
|
||||
var delta = time - prevPingTime;
|
||||
if (o != null && ReferenceEquals(o, prevPingObject) && delta <= .3f) {
|
||||
Selection.activeGameObject = o;
|
||||
} else if (o != null) {
|
||||
EditorGUIUtility.PingObject(o);
|
||||
}
|
||||
prevPingTime = time;
|
||||
prevPingObject = o;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class BaseSensorEditor<T> : BasePulsableEditor<T> where T : Sensor {
|
||||
|
||||
protected T sensor;
|
||||
|
||||
protected override void OnEnable() {
|
||||
base.OnEnable();
|
||||
sensor = serializedObject.targetObject as T;
|
||||
}
|
||||
|
||||
protected override void OnPulsableGUI() {
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
InspectorParameters();
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
EditorState.StopAllTesting();
|
||||
}
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
if (showDetections) {
|
||||
EditorUtils.HorizontalLine(new Color(0.5f, 0.5f, 0.5f, 1));
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
InspectorDetectedObjects();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void InspectorParameters();
|
||||
|
||||
protected virtual void InspectorDetectedObjects() {
|
||||
if (sensor.Signals.Count == 0) {
|
||||
EditorGUILayout.LabelField("Nothing Detected...", new GUIStyle() { fontStyle = FontStyle.Bold });
|
||||
return;
|
||||
}
|
||||
EditorGUILayout.LabelField("Detected Signals", new GUIStyle() { fontStyle = FontStyle.Bold });
|
||||
foreach (var signal in sensor.Signals) {
|
||||
SignalFieldLayout(signal);
|
||||
}
|
||||
}
|
||||
|
||||
protected void SignalFieldLayout(Signal signal) {
|
||||
var rect = EditorGUILayout.GetControlRect();
|
||||
SignalField(rect, signal);
|
||||
}
|
||||
|
||||
protected void SignalField(Rect rect, Signal signal) {
|
||||
var width = rect.width;
|
||||
var objRect = new Rect(rect.x, rect.y, width / 2, rect.height);
|
||||
var strengthRect = new Rect(objRect.max.x, rect.y, width / 4, rect.height);
|
||||
var shapeRect = new Rect(strengthRect.max.x, rect.y, width / 4, rect.height);
|
||||
DetectedObjectField(objRect, signal.Object);
|
||||
EditorGUI.LabelField(strengthRect, $"Str: {signal.Strength.ToString("N1")}", new GUIStyle() { alignment = TextAnchor.MiddleCenter });
|
||||
EditorGUI.LabelField(shapeRect, $"Size: {signal.Shape.size.magnitude.ToString("N1")}");
|
||||
}
|
||||
|
||||
protected void DetectedObjectFieldLayout(GameObject go) {
|
||||
var rect = EditorGUILayout.GetControlRect();
|
||||
DetectedObjectField(rect, go);
|
||||
}
|
||||
|
||||
protected void DetectedObjectField(Rect rect, GameObject go) {
|
||||
var guiContent = EditorGUIUtility.ObjectContent(go, typeof(GameObject));
|
||||
|
||||
var style = new GUIStyle("TextField");
|
||||
style.fixedHeight = 16;
|
||||
style.imagePosition = go ? ImagePosition.ImageLeft : ImagePosition.TextOnly;
|
||||
|
||||
if (GUI.Button(rect, guiContent, style) && go) {
|
||||
SensorEditorActions.PingOrSelectObject(go);
|
||||
}
|
||||
}
|
||||
|
||||
protected void BufferSizeInfo(int bufferSize) {
|
||||
if (bufferSize > PhysicsNonAlloc<object>.InitialSize && Application.isPlaying) {
|
||||
EditorGUILayout.HelpBox("Buffer size expanded to: " + bufferSize, MessageType.Info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70eca8a011db432fb1fdebee224b01d3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,56 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Micosmo.SensorToolkit.Editors {
|
||||
|
||||
[CustomEditor(typeof(BooleanSensor))]
|
||||
[CanEditMultipleObjects]
|
||||
public class BooleanSensorEditor : BaseSensorEditor<BooleanSensor> {
|
||||
SerializedProperty inputSensors;
|
||||
SerializedProperty operation;
|
||||
SerializedProperty signalProcessors;
|
||||
SerializedProperty onDetected;
|
||||
SerializedProperty onLostDetection;
|
||||
SerializedProperty onSomeDetection;
|
||||
SerializedProperty onNoDetection;
|
||||
|
||||
bool showEvents = false;
|
||||
|
||||
protected override bool canTest { get { return true; } }
|
||||
|
||||
protected override void OnEnable() {
|
||||
base.OnEnable();
|
||||
|
||||
if (serializedObject == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
inputSensors = serializedObject.FindProperty("InputSensors");
|
||||
operation = serializedObject.FindProperty("operation");
|
||||
signalProcessors = serializedObject.FindProperty("signalProcessors");
|
||||
onDetected = serializedObject.FindProperty("OnDetected");
|
||||
onLostDetection = serializedObject.FindProperty("OnLostDetection");
|
||||
onSomeDetection = serializedObject.FindProperty("OnSomeDetection");
|
||||
onNoDetection = serializedObject.FindProperty("OnNoDetection");
|
||||
}
|
||||
|
||||
protected override void InspectorParameters() {
|
||||
EditorGUILayout.PropertyField(inputSensors, true);
|
||||
|
||||
EditorGUILayout.PropertyField(operation);
|
||||
EditorGUILayout.PropertyField(signalProcessors, new GUIContent("Signal Processors"));
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (showEvents = EditorGUILayout.Foldout(showEvents, "Events")) {
|
||||
EditorGUILayout.PropertyField(onDetected);
|
||||
EditorGUILayout.PropertyField(onLostDetection);
|
||||
EditorGUILayout.PropertyField(onSomeDetection);
|
||||
EditorGUILayout.PropertyField(onNoDetection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2019cf5f0c448dba819243098ec8e49
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
40
Assets/SensorToolkit/Sensors/src/Editor/EditorUtils.cs
Normal file
40
Assets/SensorToolkit/Sensors/src/Editor/EditorUtils.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections;
|
||||
|
||||
namespace Micosmo.SensorToolkit.Editors {
|
||||
|
||||
public class EditorUtils {
|
||||
|
||||
public static void InlinePropertyField(SerializedProperty root) {
|
||||
if (!root.hasChildren) {
|
||||
return;
|
||||
}
|
||||
|
||||
SerializedProperty child = root.Copy();
|
||||
SerializedProperty nextSiblingProperty = root.Copy();
|
||||
nextSiblingProperty.NextVisible(false);
|
||||
|
||||
child.NextVisible(true);
|
||||
|
||||
while(!SerializedProperty.EqualContents(child, nextSiblingProperty)) {
|
||||
EditorGUILayout.PropertyField(child, true);
|
||||
child.NextVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void HorizontalLine(Color color) {
|
||||
GUIStyle horizontalLine;
|
||||
horizontalLine = new GUIStyle();
|
||||
horizontalLine.normal.background = EditorGUIUtility.whiteTexture;
|
||||
horizontalLine.margin = new RectOffset(0, 0, 4, 4);
|
||||
horizontalLine.fixedHeight = 1;
|
||||
var c = GUI.color;
|
||||
GUI.color = color;
|
||||
GUILayout.Box(GUIContent.none, horizontalLine);
|
||||
GUI.color = c;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
12
Assets/SensorToolkit/Sensors/src/Editor/EditorUtils.cs.meta
Normal file
12
Assets/SensorToolkit/Sensors/src/Editor/EditorUtils.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f1d93a18b8749548f323705b3edaafb
|
||||
timeCreated: 1601171253
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections;
|
||||
|
||||
namespace Micosmo.SensorToolkit
|
||||
{
|
||||
[CustomEditor(typeof(FOVCollider2D))]
|
||||
[CanEditMultipleObjects]
|
||||
public class FOVCollider2DEditor : Editor
|
||||
{
|
||||
SerializedProperty length;
|
||||
SerializedProperty nearDistance;
|
||||
SerializedProperty fovAngle;
|
||||
SerializedProperty resolution;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (serializedObject == null) return;
|
||||
|
||||
length = serializedObject.FindProperty("Length");
|
||||
nearDistance = serializedObject.FindProperty("NearDistance");
|
||||
fovAngle = serializedObject.FindProperty("FOVAngle");
|
||||
resolution = serializedObject.FindProperty("Resolution");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
EditorGUILayout.PropertyField(length);
|
||||
EditorGUILayout.PropertyField(nearDistance);
|
||||
EditorGUILayout.PropertyField(fovAngle);
|
||||
EditorGUILayout.PropertyField(resolution);
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d78425a8bc834891a4e9419f126e0a83
|
||||
timeCreated: 1491385286
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
40
Assets/SensorToolkit/Sensors/src/Editor/FOVColliderEditor.cs
Normal file
40
Assets/SensorToolkit/Sensors/src/Editor/FOVColliderEditor.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections;
|
||||
|
||||
namespace Micosmo.SensorToolkit
|
||||
{
|
||||
[CustomEditor(typeof(FOVCollider))]
|
||||
[CanEditMultipleObjects]
|
||||
public class FOVColliderEditor : Editor
|
||||
{
|
||||
SerializedProperty length;
|
||||
SerializedProperty nearDistance;
|
||||
SerializedProperty fovAngle;
|
||||
SerializedProperty elevationAngle;
|
||||
SerializedProperty resolution;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (serializedObject == null) return;
|
||||
|
||||
length = serializedObject.FindProperty("Length");
|
||||
nearDistance = serializedObject.FindProperty("NearDistance");
|
||||
fovAngle = serializedObject.FindProperty("FOVAngle");
|
||||
elevationAngle = serializedObject.FindProperty("ElevationAngle");
|
||||
resolution = serializedObject.FindProperty("Resolution");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
EditorGUILayout.PropertyField(length);
|
||||
EditorGUILayout.PropertyField(nearDistance);
|
||||
EditorGUILayout.PropertyField(fovAngle);
|
||||
EditorGUILayout.PropertyField(elevationAngle);
|
||||
EditorGUILayout.PropertyField(resolution);
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9ec8d8335d1c46e9b4b17f9e90ec102a
|
||||
timeCreated: 1491114639
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
259
Assets/SensorToolkit/Sensors/src/Editor/LOSSensor2DEditor.cs
Normal file
259
Assets/SensorToolkit/Sensors/src/Editor/LOSSensor2DEditor.cs
Normal file
@@ -0,0 +1,259 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Micosmo.SensorToolkit.Editors {
|
||||
|
||||
[CustomEditor(typeof(LOSSensor2D))]
|
||||
[CanEditMultipleObjects]
|
||||
public class LOSSensor2DEditor : BaseSensorEditor<LOSSensor2D> {
|
||||
|
||||
static string horizAngleMessage =
|
||||
"Angle can't exceed 90 when Point Sampling Method is set to Quality. Recommend changing " +
|
||||
"Point Sampling Method to Fast. In most cases Fast is superior to Quality anyway.";
|
||||
|
||||
SerializedProperty inputSensor;
|
||||
SerializedProperty blocksLineOfSight;
|
||||
SerializedProperty ignoreTriggerColliders;
|
||||
SerializedProperty pulseMode;
|
||||
SerializedProperty pulseUpdateFunction;
|
||||
SerializedProperty pulseInterval;
|
||||
SerializedProperty pointSamplingMethod;
|
||||
SerializedProperty testLOSTargetsOnly;
|
||||
SerializedProperty numberOfRays;
|
||||
SerializedProperty minimumVisibility;
|
||||
|
||||
SerializedProperty movingAverage;
|
||||
SerializedProperty windowSize;
|
||||
|
||||
SerializedProperty limitDistance;
|
||||
SerializedProperty maxDistance;
|
||||
SerializedProperty visibilityByDistance;
|
||||
|
||||
SerializedProperty limitViewAngle;
|
||||
SerializedProperty maxViewAngle;
|
||||
SerializedProperty visibilityByViewAngle;
|
||||
SerializedProperty fovConstraintMethod;
|
||||
|
||||
SerializedProperty onDetected;
|
||||
SerializedProperty onLostDetection;
|
||||
SerializedProperty onSomeDetection;
|
||||
SerializedProperty onNoDetection;
|
||||
|
||||
bool showEvents = false;
|
||||
|
||||
protected override bool canTest { get { return true; } }
|
||||
|
||||
protected override void OnEnable() {
|
||||
base.OnEnable();
|
||||
|
||||
if (serializedObject == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
inputSensor = serializedObject.FindProperty("inputSensor");
|
||||
blocksLineOfSight = serializedObject.FindProperty("BlocksLineOfSight");
|
||||
ignoreTriggerColliders = serializedObject.FindProperty("IgnoreTriggerColliders");
|
||||
pulseMode = serializedObject.FindProperty("pulseRoutine.Mode");
|
||||
pulseUpdateFunction = serializedObject.FindProperty("pulseRoutine.UpdateFunction");
|
||||
pulseInterval = serializedObject.FindProperty("pulseRoutine.Interval");
|
||||
pointSamplingMethod = serializedObject.FindProperty("PointSamplingMethod");
|
||||
testLOSTargetsOnly = serializedObject.FindProperty("TestLOSTargetsOnly");
|
||||
numberOfRays = serializedObject.FindProperty("NumberOfRays");
|
||||
minimumVisibility = serializedObject.FindProperty("MinimumVisibility");
|
||||
|
||||
movingAverage = serializedObject.FindProperty("MovingAverageEnabled");
|
||||
windowSize = serializedObject.FindProperty("MovingAverageWindowSize");
|
||||
|
||||
limitDistance = serializedObject.FindProperty("LimitDistance");
|
||||
maxDistance = serializedObject.FindProperty("MaxDistance");
|
||||
visibilityByDistance = serializedObject.FindProperty("VisibilityByDistance");
|
||||
|
||||
limitViewAngle = serializedObject.FindProperty("LimitViewAngle");
|
||||
maxViewAngle = serializedObject.FindProperty("MaxViewAngle");
|
||||
visibilityByViewAngle = serializedObject.FindProperty("VisibilityByViewAngle");
|
||||
|
||||
fovConstraintMethod = serializedObject.FindProperty("FOVConstraintMethod");
|
||||
|
||||
onDetected = serializedObject.FindProperty("OnDetected");
|
||||
onLostDetection = serializedObject.FindProperty("OnLostDetection");
|
||||
onSomeDetection = serializedObject.FindProperty("OnSomeDetection");
|
||||
onNoDetection = serializedObject.FindProperty("OnNoDetection");
|
||||
|
||||
sensor.ShowRayCastDebug = new HashSet<GameObject>();
|
||||
}
|
||||
|
||||
protected override void OnDisable() {
|
||||
base.OnDisable();
|
||||
sensor.ShowRayCastDebug = null;
|
||||
}
|
||||
|
||||
protected override void InspectorParameters() {
|
||||
EditorGUILayout.PropertyField(inputSensor);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(blocksLineOfSight);
|
||||
EditorGUILayout.PropertyField(ignoreTriggerColliders);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(testLOSTargetsOnly);
|
||||
if (!testLOSTargetsOnly.boolValue) {
|
||||
EditorGUILayout.PropertyField(numberOfRays);
|
||||
EditorGUILayout.PropertyField(pointSamplingMethod);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(movingAverage, new GUIContent("Moving Average"));
|
||||
if (sensor.MovingAverageEnabled) {
|
||||
EditorGUILayout.PropertyField(windowSize, new GUIContent("Window Size"));
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(limitDistance);
|
||||
if (sensor.LimitDistance) {
|
||||
EditorGUILayout.PropertyField(maxDistance);
|
||||
ScalingFunctionProperty(visibilityByDistance);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(limitViewAngle);
|
||||
if (sensor.LimitViewAngle) {
|
||||
EditorGUILayout.PropertyField(maxViewAngle);
|
||||
if (sensor.MaxViewAngle > 90f && sensor.PointSamplingMethod == PointSamplingMethod.Quality) {
|
||||
EditorGUILayout.HelpBox(horizAngleMessage, MessageType.Error);
|
||||
}
|
||||
ScalingFunctionProperty(visibilityByViewAngle);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (sensor.LimitDistance || sensor.LimitViewAngle) {
|
||||
EditorGUILayout.PropertyField(fovConstraintMethod);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(minimumVisibility);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(pulseMode, new GUIContent("Pulse Mode"));
|
||||
if (sensor.PulseMode != PulseRoutine.Modes.Manual) {
|
||||
EditorGUILayout.PropertyField(pulseUpdateFunction);
|
||||
}
|
||||
if (sensor.PulseMode == PulseRoutine.Modes.FixedInterval) {
|
||||
EditorGUILayout.PropertyField(pulseInterval, new GUIContent("Pulse Interval"));
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (showEvents = EditorGUILayout.Foldout(showEvents, "Events")) {
|
||||
EditorGUILayout.PropertyField(onDetected);
|
||||
EditorGUILayout.PropertyField(onLostDetection);
|
||||
EditorGUILayout.PropertyField(onSomeDetection);
|
||||
EditorGUILayout.PropertyField(onNoDetection);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void InspectorDetectedObjects() {
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
VisibilityTable();
|
||||
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
void ScalingFunctionProperty(SerializedProperty property) {
|
||||
var mode = property.FindPropertyRelative("Mode");
|
||||
var curve = property.FindPropertyRelative("Curve");
|
||||
EditorGUILayout.PropertyField(mode, new GUIContent(property.displayName));
|
||||
var modeVal = (ScalingMode)mode.intValue;
|
||||
if (modeVal == ScalingMode.Curve) {
|
||||
EditorGUILayout.PropertyField(curve, new GUIContent(" "));
|
||||
}
|
||||
}
|
||||
|
||||
void VisibilityTable() {
|
||||
var headerRow = EditorGUILayout.GetControlRect();
|
||||
VisibilityHeaders(headerRow);
|
||||
|
||||
var losResults = sensor.GetAllResults();
|
||||
losResults.Sort(delegate (ILOSResult r1, ILOSResult r2) {
|
||||
if (r1.Visibility < r2.Visibility) {
|
||||
return 1;
|
||||
} else if (r1.Visibility == r2.Visibility) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
});
|
||||
|
||||
foreach (var result in losResults) {
|
||||
var rect = EditorGUILayout.GetControlRect();
|
||||
VisibilityRow(rect, result);
|
||||
}
|
||||
}
|
||||
|
||||
void VisibilityColumns(Rect rect, out Rect showRaysCol, out Rect visibilityCol, out Rect signalCol) {
|
||||
var width = rect.width;
|
||||
var showRaysWidth = Mathf.Min(50f, width / 3);
|
||||
showRaysCol = new Rect(rect.x, rect.y, showRaysWidth, rect.height);
|
||||
|
||||
width -= showRaysWidth;
|
||||
var visibilityWidth = Mathf.Min(80f, width / 2);
|
||||
visibilityCol = new Rect(showRaysCol.xMax, rect.y, visibilityWidth, rect.height);
|
||||
|
||||
width -= visibilityWidth;
|
||||
signalCol = new Rect(visibilityCol.xMax, rect.y, width, rect.height);
|
||||
}
|
||||
|
||||
void VisibilityHeaders(Rect rect) {
|
||||
Rect rRaysCol, rVisCol, rSigCol;
|
||||
VisibilityColumns(rect, out rRaysCol, out rVisCol, out rSigCol);
|
||||
|
||||
var headerColumnStyle = new GUIStyle(EditorStyles.label) {
|
||||
fontStyle = FontStyle.Bold,
|
||||
padding = new RectOffset(0, 16, 0, 0)
|
||||
};
|
||||
|
||||
if (IsActivePulsable) {
|
||||
GUI.Label(rRaysCol, "Show", new GUIStyle(headerColumnStyle) { alignment = TextAnchor.UpperCenter });
|
||||
}
|
||||
GUI.Label(rVisCol, "Visibility", new GUIStyle(headerColumnStyle) { alignment = TextAnchor.UpperRight });
|
||||
GUI.Label(rSigCol, "Output Signal", new GUIStyle(headerColumnStyle) { alignment = TextAnchor.UpperLeft });
|
||||
}
|
||||
|
||||
void VisibilityRow(Rect rect, ILOSResult losResult) {
|
||||
Rect rRaysCol, rVisCol, rSigCol;
|
||||
VisibilityColumns(rect, out rRaysCol, out rVisCol, out rSigCol);
|
||||
|
||||
rRaysCol.xMin += rRaysCol.width / 2f - 16;
|
||||
rVisCol.xMax -= 16;
|
||||
|
||||
var signal = losResult.OutputSignal;
|
||||
|
||||
if (IsActivePulsable) {
|
||||
var debug = sensor.ShowRayCastDebug.Contains(signal.Object);
|
||||
if (debug = EditorGUI.Toggle(rRaysCol, debug)) {
|
||||
sensor.ShowRayCastDebug.Add(signal.Object);
|
||||
} else {
|
||||
sensor.ShowRayCastDebug.Remove(signal.Object);
|
||||
}
|
||||
}
|
||||
|
||||
var visStyle = new GUIStyle(EditorStyles.label) { alignment = TextAnchor.MiddleRight };
|
||||
if (!losResult.IsVisible) {
|
||||
visStyle.normal.textColor = STPrefs.RedEditorTextColour;
|
||||
}
|
||||
GUI.Label(rVisCol, string.Format("{0:P1}", losResult.Visibility), visStyle);
|
||||
|
||||
SignalField(rSigCol, signal);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34e4a36656384a5d8e94b847a7505ad3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
265
Assets/SensorToolkit/Sensors/src/Editor/LOSSensorEditor.cs
Normal file
265
Assets/SensorToolkit/Sensors/src/Editor/LOSSensorEditor.cs
Normal file
@@ -0,0 +1,265 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Micosmo.SensorToolkit.Editors {
|
||||
|
||||
[CustomEditor(typeof(LOSSensor))]
|
||||
[CanEditMultipleObjects]
|
||||
public class LOSSensorEditor : BaseSensorEditor<LOSSensor> {
|
||||
|
||||
static string horizAngleMessage =
|
||||
"Angle can't exceed 90 when Point Sampling Method is set to Quality. Recommend changing " +
|
||||
"Point Sampling Method to Fast. In most cases Fast is superior to Quality anyway.";
|
||||
|
||||
SerializedProperty inputSensor;
|
||||
SerializedProperty blocksLineOfSight;
|
||||
SerializedProperty ignoreTriggerColliders;
|
||||
SerializedProperty pulseMode;
|
||||
SerializedProperty pulseUpdateFunction;
|
||||
SerializedProperty pulseInterval;
|
||||
SerializedProperty pointSamplingMethod;
|
||||
SerializedProperty testLOSTargetsOnly;
|
||||
SerializedProperty numberOfRays;
|
||||
SerializedProperty minimumVisibility;
|
||||
|
||||
SerializedProperty movingAverage;
|
||||
SerializedProperty windowSize;
|
||||
|
||||
SerializedProperty limitDistance;
|
||||
SerializedProperty maxDistance;
|
||||
SerializedProperty visibilityByDistance;
|
||||
|
||||
SerializedProperty limitViewAngle;
|
||||
SerializedProperty maxHorizAngle;
|
||||
SerializedProperty visibilityByHorizAngle;
|
||||
SerializedProperty maxVertAngle;
|
||||
SerializedProperty visibilityByVertAngle;
|
||||
SerializedProperty fovConstraintMethod;
|
||||
|
||||
SerializedProperty onDetected;
|
||||
SerializedProperty onLostDetection;
|
||||
SerializedProperty onSomeDetection;
|
||||
SerializedProperty onNoDetection;
|
||||
|
||||
bool showEvents = false;
|
||||
|
||||
protected override bool canTest { get { return true; } }
|
||||
|
||||
protected override void OnEnable() {
|
||||
base.OnEnable();
|
||||
|
||||
if (serializedObject == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
inputSensor = serializedObject.FindProperty("inputSensor");
|
||||
blocksLineOfSight = serializedObject.FindProperty("BlocksLineOfSight");
|
||||
ignoreTriggerColliders = serializedObject.FindProperty("IgnoreTriggerColliders");
|
||||
pulseMode = serializedObject.FindProperty("pulseRoutine.Mode");
|
||||
pulseUpdateFunction = serializedObject.FindProperty("pulseRoutine.UpdateFunction");
|
||||
pulseInterval = serializedObject.FindProperty("pulseRoutine.Interval");
|
||||
pointSamplingMethod = serializedObject.FindProperty("PointSamplingMethod");
|
||||
testLOSTargetsOnly = serializedObject.FindProperty("TestLOSTargetsOnly");
|
||||
numberOfRays = serializedObject.FindProperty("NumberOfRays");
|
||||
minimumVisibility = serializedObject.FindProperty("MinimumVisibility");
|
||||
|
||||
movingAverage = serializedObject.FindProperty("MovingAverageEnabled");
|
||||
windowSize = serializedObject.FindProperty("MovingAverageWindowSize");
|
||||
|
||||
limitDistance = serializedObject.FindProperty("LimitDistance");
|
||||
maxDistance = serializedObject.FindProperty("MaxDistance");
|
||||
visibilityByDistance = serializedObject.FindProperty("VisibilityByDistance");
|
||||
|
||||
limitViewAngle = serializedObject.FindProperty("LimitViewAngle");
|
||||
maxHorizAngle = serializedObject.FindProperty("MaxHorizAngle");
|
||||
visibilityByHorizAngle = serializedObject.FindProperty("VisibilityByHorizAngle");
|
||||
maxVertAngle = serializedObject.FindProperty("MaxVertAngle");
|
||||
visibilityByVertAngle = serializedObject.FindProperty("VisibilityByVertAngle");
|
||||
|
||||
fovConstraintMethod = serializedObject.FindProperty("FOVConstraintMethod");
|
||||
|
||||
onDetected = serializedObject.FindProperty("OnDetected");
|
||||
onLostDetection = serializedObject.FindProperty("OnLostDetection");
|
||||
onSomeDetection = serializedObject.FindProperty("OnSomeDetection");
|
||||
onNoDetection = serializedObject.FindProperty("OnNoDetection");
|
||||
|
||||
sensor.ShowRayCastDebug = new HashSet<GameObject>();
|
||||
}
|
||||
|
||||
protected override void OnDisable() {
|
||||
base.OnDisable();
|
||||
sensor.ShowRayCastDebug = null;
|
||||
}
|
||||
|
||||
protected override void InspectorParameters() {
|
||||
EditorGUILayout.PropertyField(inputSensor);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(blocksLineOfSight);
|
||||
EditorGUILayout.PropertyField(ignoreTriggerColliders);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(testLOSTargetsOnly);
|
||||
if (!testLOSTargetsOnly.boolValue) {
|
||||
EditorGUILayout.PropertyField(numberOfRays);
|
||||
EditorGUILayout.PropertyField(pointSamplingMethod);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(movingAverage, new GUIContent("Moving Average"));
|
||||
if (sensor.MovingAverageEnabled) {
|
||||
EditorGUILayout.PropertyField(windowSize, new GUIContent("Window Size"));
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(limitDistance);
|
||||
if (sensor.LimitDistance) {
|
||||
EditorGUILayout.PropertyField(maxDistance);
|
||||
ScalingFunctionProperty(visibilityByDistance);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(limitViewAngle);
|
||||
if (sensor.LimitViewAngle) {
|
||||
EditorGUILayout.PropertyField(maxHorizAngle);
|
||||
if (sensor.MaxHorizAngle > 90f && sensor.PointSamplingMethod == PointSamplingMethod.Quality) {
|
||||
EditorGUILayout.HelpBox(horizAngleMessage, MessageType.Error);
|
||||
}
|
||||
ScalingFunctionProperty(visibilityByHorizAngle);
|
||||
EditorGUILayout.PropertyField(maxVertAngle);
|
||||
ScalingFunctionProperty(visibilityByVertAngle);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (sensor.LimitDistance || sensor.LimitViewAngle) {
|
||||
EditorGUILayout.PropertyField(fovConstraintMethod);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(minimumVisibility);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(pulseMode, new GUIContent("Pulse Mode"));
|
||||
if (sensor.PulseMode != PulseRoutine.Modes.Manual) {
|
||||
EditorGUILayout.PropertyField(pulseUpdateFunction);
|
||||
}
|
||||
if (sensor.PulseMode == PulseRoutine.Modes.FixedInterval) {
|
||||
EditorGUILayout.PropertyField(pulseInterval, new GUIContent("Pulse Interval"));
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (showEvents = EditorGUILayout.Foldout(showEvents, "Events")) {
|
||||
EditorGUILayout.PropertyField(onDetected);
|
||||
EditorGUILayout.PropertyField(onLostDetection);
|
||||
EditorGUILayout.PropertyField(onSomeDetection);
|
||||
EditorGUILayout.PropertyField(onNoDetection);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void InspectorDetectedObjects() {
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
VisibilityTable();
|
||||
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
void ScalingFunctionProperty(SerializedProperty property) {
|
||||
var mode = property.FindPropertyRelative("Mode");
|
||||
var curve = property.FindPropertyRelative("Curve");
|
||||
EditorGUILayout.PropertyField(mode, new GUIContent(property.displayName));
|
||||
var modeVal = (ScalingMode)mode.intValue;
|
||||
if (modeVal == ScalingMode.Curve) {
|
||||
EditorGUILayout.PropertyField(curve, new GUIContent(" "));
|
||||
}
|
||||
}
|
||||
|
||||
void VisibilityTable() {
|
||||
var headerRow = EditorGUILayout.GetControlRect();
|
||||
VisibilityHeaders(headerRow);
|
||||
|
||||
var losResults = sensor.GetAllResults();
|
||||
losResults.Sort(delegate (ILOSResult r1, ILOSResult r2) {
|
||||
if (r1.Visibility < r2.Visibility) {
|
||||
return 1;
|
||||
} else if (r1.Visibility == r2.Visibility) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
});
|
||||
|
||||
foreach (var result in losResults) {
|
||||
var rect = EditorGUILayout.GetControlRect();
|
||||
VisibilityRow(rect, result);
|
||||
}
|
||||
}
|
||||
|
||||
void VisibilityColumns(Rect rect, out Rect showRaysCol, out Rect visibilityCol, out Rect signalCol) {
|
||||
var width = rect.width;
|
||||
var showRaysWidth = Mathf.Min(50f, width / 3);
|
||||
showRaysCol = new Rect(rect.x, rect.y, showRaysWidth, rect.height);
|
||||
|
||||
width -= showRaysWidth;
|
||||
var visibilityWidth = Mathf.Min(80f, width / 2);
|
||||
visibilityCol = new Rect(showRaysCol.xMax, rect.y, visibilityWidth, rect.height);
|
||||
|
||||
width -= visibilityWidth;
|
||||
signalCol = new Rect(visibilityCol.xMax, rect.y, width, rect.height);
|
||||
}
|
||||
|
||||
void VisibilityHeaders(Rect rect) {
|
||||
Rect rRaysCol, rVisCol, rSigCol;
|
||||
VisibilityColumns(rect, out rRaysCol, out rVisCol, out rSigCol);
|
||||
|
||||
var headerColumnStyle = new GUIStyle(EditorStyles.label) {
|
||||
fontStyle = FontStyle.Bold,
|
||||
padding = new RectOffset(0,16,0,0)
|
||||
};
|
||||
|
||||
if (IsActivePulsable) {
|
||||
GUI.Label(rRaysCol, "Show", new GUIStyle(headerColumnStyle) { alignment = TextAnchor.UpperCenter });
|
||||
}
|
||||
GUI.Label(rVisCol, "Visibility", new GUIStyle(headerColumnStyle) { alignment = TextAnchor.UpperRight });
|
||||
GUI.Label(rSigCol, "Output Signal", new GUIStyle(headerColumnStyle) { alignment = TextAnchor.UpperLeft });
|
||||
}
|
||||
|
||||
void VisibilityRow(Rect rect, ILOSResult losResult) {
|
||||
Rect rRaysCol, rVisCol, rSigCol;
|
||||
VisibilityColumns(rect, out rRaysCol, out rVisCol, out rSigCol);
|
||||
|
||||
rRaysCol.xMin += rRaysCol.width / 2f - 16;
|
||||
rVisCol.xMax -= 16;
|
||||
|
||||
var signal = losResult.OutputSignal;
|
||||
|
||||
if (IsActivePulsable) {
|
||||
var debug = sensor.ShowRayCastDebug.Contains(signal.Object);
|
||||
if (debug = EditorGUI.Toggle(rRaysCol, debug)) {
|
||||
sensor.ShowRayCastDebug.Add(signal.Object);
|
||||
} else {
|
||||
sensor.ShowRayCastDebug.Remove(signal.Object);
|
||||
}
|
||||
}
|
||||
|
||||
var visStyle = new GUIStyle(EditorStyles.label) { alignment = TextAnchor.MiddleRight };
|
||||
if (!losResult.IsVisible) {
|
||||
visStyle.normal.textColor = STPrefs.RedEditorTextColour;
|
||||
}
|
||||
GUI.Label(rVisCol, string.Format("{0:P1}", losResult.Visibility), visStyle);
|
||||
|
||||
SignalField(rSigCol, signal);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a9e2ee831754f5d9ed0f5f5521354ce
|
||||
timeCreated: 1600682780
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Micosmo.SensorToolkit.Editor",
|
||||
"references": [
|
||||
"GUID:b9d61b92870877a459c95c25c7d15074"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f03c1aaa5578cf4c8db4bdb47ee1a7b
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
40
Assets/SensorToolkit/Sensors/src/Editor/NavMeshMaskDrawer.cs
Normal file
40
Assets/SensorToolkit/Sensors/src/Editor/NavMeshMaskDrawer.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using Micosmo.SensorToolkit;
|
||||
|
||||
namespace Micosmo.SensorToolkit.Editors {
|
||||
|
||||
[CustomPropertyDrawer(typeof(NavMeshMaskAttribute))]
|
||||
public class NavMeshMaskDrawer : PropertyDrawer {
|
||||
public override void OnGUI(Rect position, SerializedProperty serializedProperty, GUIContent label) {
|
||||
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
|
||||
position = EditorGUI.PrefixLabel(position, label);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
string[] areaNames = GameObjectUtility.GetNavMeshAreaNames();
|
||||
List<string> completeAreaNames = new List<string>();
|
||||
|
||||
foreach (string name in areaNames) {
|
||||
var id = GameObjectUtility.GetNavMeshAreaFromName(name);
|
||||
while (id >= completeAreaNames.Count) {
|
||||
completeAreaNames.Add("");
|
||||
}
|
||||
completeAreaNames[id] = name;
|
||||
}
|
||||
|
||||
int mask = serializedProperty.intValue;
|
||||
|
||||
mask = EditorGUI.MaskField(position, mask, completeAreaNames.ToArray());
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
serializedProperty.intValue = mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e6ba988a66d424ea6597d3e437d3400
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,96 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Micosmo.SensorToolkit.Editors {
|
||||
|
||||
[CustomEditor(typeof(NavMeshSensor))]
|
||||
[CanEditMultipleObjects]
|
||||
public class NavMeshSensorEditor : BasePulsableEditor<NavMeshSensor> {
|
||||
SerializedProperty test;
|
||||
SerializedProperty ray;
|
||||
SerializedProperty sphere;
|
||||
SerializedProperty areaMask;
|
||||
SerializedProperty pulseMode;
|
||||
SerializedProperty pulseInterval;
|
||||
SerializedProperty onObstructed;
|
||||
SerializedProperty onClear;
|
||||
|
||||
bool showEvents = false;
|
||||
|
||||
protected NavMeshSensor sensor;
|
||||
|
||||
protected override bool canTest => true;
|
||||
|
||||
protected override void OnEnable() {
|
||||
base.OnEnable();
|
||||
|
||||
if (serializedObject == null) return;
|
||||
sensor = serializedObject.targetObject as NavMeshSensor;
|
||||
|
||||
test = serializedObject.FindProperty("Test");
|
||||
ray = serializedObject.FindProperty("Ray");
|
||||
sphere = serializedObject.FindProperty("Sphere");
|
||||
areaMask = serializedObject.FindProperty("AreaMask");
|
||||
pulseMode = serializedObject.FindProperty("pulseRoutine.Mode");
|
||||
pulseInterval = serializedObject.FindProperty("pulseRoutine.Interval");
|
||||
onObstructed = serializedObject.FindProperty("onObstruction");
|
||||
onClear = serializedObject.FindProperty("onClear");
|
||||
}
|
||||
|
||||
protected override void OnPulsableGUI() {
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
EditorGUILayout.PropertyField(test);
|
||||
if (sensor.Test == NavMeshSensor.TestType.Ray) {
|
||||
EditorUtils.InlinePropertyField(ray);
|
||||
} else if (sensor.Test == NavMeshSensor.TestType.Sample) {
|
||||
EditorUtils.InlinePropertyField(sphere);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(areaMask);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(pulseMode, new GUIContent("Pulse Mode"));
|
||||
if (sensor.PulseMode == PulseRoutine.Modes.FixedInterval) {
|
||||
EditorGUILayout.PropertyField(pulseInterval, new GUIContent("Pulse Interval"));
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (showEvents = EditorGUILayout.Foldout(showEvents, "Events")) {
|
||||
EditorGUILayout.PropertyField(onObstructed);
|
||||
EditorGUILayout.PropertyField(onClear);
|
||||
}
|
||||
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
EditorState.StopAllTesting();
|
||||
}
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
if (showDetections) {
|
||||
EditorUtils.HorizontalLine(new Color(0.5f, 0.5f, 0.5f, 1));
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
InspectorDetectedObjects();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
}
|
||||
|
||||
void InspectorDetectedObjects() {
|
||||
if (!sensor.IsObstructed) return;
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Ray is Obstructed", new GUIStyle() { fontStyle = FontStyle.Bold, normal = new GUIStyleState() { textColor = STPrefs.RedEditorTextColour } });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 152d28158e8a4f6c8472d50e383242fc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
107
Assets/SensorToolkit/Sensors/src/Editor/RangeSensor2DEditor.cs
Normal file
107
Assets/SensorToolkit/Sensors/src/Editor/RangeSensor2DEditor.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Linq;
|
||||
|
||||
namespace Micosmo.SensorToolkit.Editors {
|
||||
[CustomEditor(typeof(RangeSensor2D))]
|
||||
[CanEditMultipleObjects]
|
||||
public class RangeSensor2DEditor : BaseSensorEditor<RangeSensor2D> {
|
||||
SerializedProperty shape;
|
||||
SerializedProperty circle;
|
||||
SerializedProperty box;
|
||||
SerializedProperty capsule;
|
||||
SerializedProperty ignoreList;
|
||||
SerializedProperty tagFilterEnabled;
|
||||
SerializedProperty tagFilter;
|
||||
SerializedProperty detectsOnLayers;
|
||||
SerializedProperty pulseMode;
|
||||
SerializedProperty pulseUpdateFunction;
|
||||
SerializedProperty pulseInterval;
|
||||
SerializedProperty detectionMode;
|
||||
SerializedProperty ignoreTriggerColliders;
|
||||
SerializedProperty signalProcessors;
|
||||
SerializedProperty onDetected;
|
||||
SerializedProperty onLostDetection;
|
||||
SerializedProperty onSomeDetection;
|
||||
SerializedProperty onNoDetection;
|
||||
|
||||
bool showEvents = false;
|
||||
|
||||
protected override bool canTest { get { return true; } }
|
||||
|
||||
protected override void OnEnable() {
|
||||
base.OnEnable();
|
||||
|
||||
if (serializedObject == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
shape = serializedObject.FindProperty("Shape");
|
||||
circle = serializedObject.FindProperty("Circle");
|
||||
box = serializedObject.FindProperty("Box");
|
||||
capsule = serializedObject.FindProperty("Capsule");
|
||||
ignoreList = serializedObject.FindProperty("signalFilter.IgnoreList");
|
||||
tagFilterEnabled = serializedObject.FindProperty("signalFilter.EnableTagFilter");
|
||||
tagFilter = serializedObject.FindProperty("signalFilter.AllowedTags");
|
||||
detectsOnLayers = serializedObject.FindProperty("DetectsOnLayers");
|
||||
pulseMode = serializedObject.FindProperty("pulseRoutine.Mode");
|
||||
pulseUpdateFunction = serializedObject.FindProperty("pulseRoutine.UpdateFunction");
|
||||
pulseInterval = serializedObject.FindProperty("pulseRoutine.Interval");
|
||||
detectionMode = serializedObject.FindProperty("DetectionMode");
|
||||
ignoreTriggerColliders = serializedObject.FindProperty("IgnoreTriggerColliders");
|
||||
signalProcessors = serializedObject.FindProperty("signalProcessors");
|
||||
onDetected = serializedObject.FindProperty("OnDetected");
|
||||
onLostDetection = serializedObject.FindProperty("OnLostDetection");
|
||||
onSomeDetection = serializedObject.FindProperty("OnSomeDetection");
|
||||
onNoDetection = serializedObject.FindProperty("OnNoDetection");
|
||||
}
|
||||
|
||||
protected override void InspectorParameters() {
|
||||
EditorGUILayout.PropertyField(shape);
|
||||
if (sensor.Shape == RangeSensor2D.Shapes.Circle) {
|
||||
EditorUtils.InlinePropertyField(circle);
|
||||
} else if (sensor.Shape == RangeSensor2D.Shapes.Box) {
|
||||
EditorUtils.InlinePropertyField(box);
|
||||
} else if (sensor.Shape == RangeSensor2D.Shapes.Capsule) {
|
||||
EditorUtils.InlinePropertyField(capsule);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(ignoreList, true);
|
||||
EditorGUILayout.PropertyField(tagFilterEnabled);
|
||||
if (tagFilterEnabled.boolValue) {
|
||||
EditorGUILayout.PropertyField(tagFilter, true);
|
||||
}
|
||||
EditorGUILayout.PropertyField(detectsOnLayers);
|
||||
EditorGUILayout.PropertyField(detectionMode);
|
||||
EditorGUILayout.PropertyField(ignoreTriggerColliders);
|
||||
EditorGUILayout.PropertyField(signalProcessors, new GUIContent("Signal Processors"));
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(pulseMode, new GUIContent("Pulse Mode"));
|
||||
if (sensor.PulseMode != PulseRoutine.Modes.Manual) {
|
||||
EditorGUILayout.PropertyField(pulseUpdateFunction);
|
||||
}
|
||||
if (sensor.PulseMode == PulseRoutine.Modes.FixedInterval) {
|
||||
EditorGUILayout.PropertyField(pulseInterval, new GUIContent("Pulse Interval"));
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (showEvents = EditorGUILayout.Foldout(showEvents, "Events")) {
|
||||
EditorGUILayout.PropertyField(onDetected);
|
||||
EditorGUILayout.PropertyField(onLostDetection);
|
||||
EditorGUILayout.PropertyField(onSomeDetection);
|
||||
EditorGUILayout.PropertyField(onNoDetection);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
BufferSizeInfo(sensor.CurrentBufferSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 550d906f63c0467f9e9968050a56c67d
|
||||
timeCreated: 1491309471
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
107
Assets/SensorToolkit/Sensors/src/Editor/RangeSensorEditor.cs
Normal file
107
Assets/SensorToolkit/Sensors/src/Editor/RangeSensorEditor.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Linq;
|
||||
|
||||
namespace Micosmo.SensorToolkit.Editors {
|
||||
[CustomEditor(typeof(RangeSensor))]
|
||||
[CanEditMultipleObjects]
|
||||
public class RangeSensorEditor : BaseSensorEditor<RangeSensor> {
|
||||
SerializedProperty shape;
|
||||
SerializedProperty sphere;
|
||||
SerializedProperty box;
|
||||
SerializedProperty capsule;
|
||||
SerializedProperty ignoreList;
|
||||
SerializedProperty tagFilterEnabled;
|
||||
SerializedProperty tagFilter;
|
||||
SerializedProperty detectsOnLayers;
|
||||
SerializedProperty pulseMode;
|
||||
SerializedProperty pulseUpdateFunction;
|
||||
SerializedProperty pulseInterval;
|
||||
SerializedProperty detectionMode;
|
||||
SerializedProperty ignoreTriggerColliders;
|
||||
SerializedProperty signalProcessors;
|
||||
SerializedProperty onDetected;
|
||||
SerializedProperty onLostDetection;
|
||||
SerializedProperty onSomeDetection;
|
||||
SerializedProperty onNoDetection;
|
||||
|
||||
bool showEvents = false;
|
||||
|
||||
protected override bool canTest { get { return true; } }
|
||||
|
||||
protected override void OnEnable() {
|
||||
base.OnEnable();
|
||||
|
||||
if (serializedObject == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
shape = serializedObject.FindProperty("Shape");
|
||||
sphere = serializedObject.FindProperty("Sphere");
|
||||
box = serializedObject.FindProperty("Box");
|
||||
capsule = serializedObject.FindProperty("Capsule");
|
||||
ignoreList = serializedObject.FindProperty("signalFilter.IgnoreList");
|
||||
tagFilterEnabled = serializedObject.FindProperty("signalFilter.EnableTagFilter");
|
||||
tagFilter = serializedObject.FindProperty("signalFilter.AllowedTags");
|
||||
detectsOnLayers = serializedObject.FindProperty("DetectsOnLayers");
|
||||
pulseMode = serializedObject.FindProperty("pulseRoutine.Mode");
|
||||
pulseUpdateFunction = serializedObject.FindProperty("pulseRoutine.UpdateFunction");
|
||||
pulseInterval = serializedObject.FindProperty("pulseRoutine.Interval");
|
||||
detectionMode = serializedObject.FindProperty("DetectionMode");
|
||||
ignoreTriggerColliders = serializedObject.FindProperty("IgnoreTriggerColliders");
|
||||
signalProcessors = serializedObject.FindProperty("signalProcessors");
|
||||
onDetected = serializedObject.FindProperty("OnDetected");
|
||||
onLostDetection = serializedObject.FindProperty("OnLostDetection");
|
||||
onSomeDetection = serializedObject.FindProperty("OnSomeDetection");
|
||||
onNoDetection = serializedObject.FindProperty("OnNoDetection");
|
||||
}
|
||||
|
||||
protected override void InspectorParameters() {
|
||||
EditorGUILayout.PropertyField(shape);
|
||||
if (sensor.Shape == RangeSensor.Shapes.Sphere) {
|
||||
EditorUtils.InlinePropertyField(sphere);
|
||||
} else if (sensor.Shape == RangeSensor.Shapes.Box) {
|
||||
EditorUtils.InlinePropertyField(box);
|
||||
} else if (sensor.Shape == RangeSensor.Shapes.Capsule) {
|
||||
EditorUtils.InlinePropertyField(capsule);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(ignoreList, true);
|
||||
EditorGUILayout.PropertyField(tagFilterEnabled);
|
||||
if (tagFilterEnabled.boolValue) {
|
||||
EditorGUILayout.PropertyField(tagFilter, true);
|
||||
}
|
||||
EditorGUILayout.PropertyField(detectsOnLayers);
|
||||
EditorGUILayout.PropertyField(detectionMode);
|
||||
EditorGUILayout.PropertyField(ignoreTriggerColliders);
|
||||
EditorGUILayout.PropertyField(signalProcessors, new GUIContent("Signal Processors"));
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(pulseMode, new GUIContent("Pulse Mode"));
|
||||
if (sensor.PulseMode != PulseRoutine.Modes.Manual) {
|
||||
EditorGUILayout.PropertyField(pulseUpdateFunction);
|
||||
}
|
||||
if (sensor.PulseMode == PulseRoutine.Modes.FixedInterval) {
|
||||
EditorGUILayout.PropertyField(pulseInterval, new GUIContent("Pulse Interval"));
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (showEvents = EditorGUILayout.Foldout(showEvents, "Events")) {
|
||||
EditorGUILayout.PropertyField(onDetected);
|
||||
EditorGUILayout.PropertyField(onLostDetection);
|
||||
EditorGUILayout.PropertyField(onSomeDetection);
|
||||
EditorGUILayout.PropertyField(onNoDetection);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
BufferSizeInfo(sensor.CurrentBufferSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user