using System; using System.Collections.Generic; using UnityEngine; using Drawing; using Sirenix.OdinInspector; using Sirenix.Serialization; using Unity.Mathematics; using UnityEngine.Serialization; [Serializable] public class EnvironmentObserver{ enum LabelDrawingLocation{ PlayerOffset, HitLocation, IntersectingLength, } enum ObserverGizmoDrawingCondition{ Always, OnlyActive, Never } public enum CastType{ Ray, BoxOverlap, SphereOverlap, BoxCast, SphereCast } [PropertySpace(0, 5), LabelWidth(60)] public string label; [PropertySpace(0, 10), LabelWidth(60)] public CastType castType; [Button(ButtonSizes.Large), GUIColor("@GetObserverStatusColorStatic(active, hit)"), PropertyOrder(-1), PropertySpace(5, 5)] public void Active(){ active = !active; } [HideInInspector] public bool active; // Parameters for Cast cast types [FoldoutGroup("Settings")] public float length; [FoldoutGroup("Settings")] public Vector3 direction; [FoldoutGroup("Settings")] public Vector3 offset; [PropertySpace(0, 5), FoldoutGroup("Settings")] public LayerMask ignoreLayers = ~0; [ShowIfGroup("Settings/CastsOnly", VisibleIf = "@castType == CastType.SphereCast || castType == CastType.SphereOverlap")] [FoldoutGroup("Settings")] public float width; // Parameters for Overlap cast types [ShowIfGroup("Settings/3DOnly", VisibleIf = "@castType == CastType.BoxCast && castType != CastType.BoxOverlap")] [FoldoutGroup("Settings")] public Vector3 size; [ShowIfGroup("Settings/3DOnly")] public Vector3 rotation; [HideInInspector] public RaycastHit hit; [HideInInspector] public Collider[] overlapHits; [FoldoutGroup("Text")] [BoxGroup("Text/Label")] public bool drawLabel; [ShowInInspector, SerializeField] [BoxGroup("Text/Label")] LabelDrawingLocation labelTextLocation; [BoxGroup("Text/Label")] public float labelSize; [BoxGroup("Text/Label")] public Vector3 labelLocationOffset; [BoxGroup("Text/Label")] public Vector3 labelRotationOffset; [BoxGroup("Text/Hit")] public bool drawHitName; [ShowInInspector, SerializeField] [BoxGroup("Text/Hit")] LabelDrawingLocation hitTextLocation; [BoxGroup("Text/Hit")] public float hitTextSize; [BoxGroup("Text/Hit")] public Vector3 hitLocationOffset; [BoxGroup("Text/Hit")] public Vector3 hitRotationOffset; [FoldoutGroup("Text"), SerializeField, ShowInInspector] ObserverGizmoDrawingCondition gizmoDrawingCondition; [SerializeReference, PropertySpace(5, 5)] public List children; // NOTE: I had a ref for a RaycastHit here that would correspond to hit but idk if it's needed. public bool Evaluate(GameObject source){ if (active) { // Remove player's layer from LayerMask. ignoreLayers -= source.layer; // Set some of the variables used later during casting Vector3 relativeStart = source.transform.position + offset; Vector3 relativeStartWithRotation = source.transform.position + source.transform.rotation * offset ; switch (castType) { case CastType.Ray: Physics.Raycast(relativeStart, source.transform.rotation * direction, out hit, length, ignoreLayers); break; case CastType.BoxOverlap: overlapHits = Physics.OverlapBox(relativeStartWithRotation, size / 2f, source.transform.rotation * Quaternion.Euler(rotation), ignoreLayers); if (overlapHits.Length > 0) { return true; }; break; case CastType.SphereOverlap: break; case CastType.BoxCast: // TODO: Make this not an if statement. Check that it works with NodeCanvas first if (Physics.BoxCast(relativeStartWithRotation, size / 2f, source.transform.rotation * Quaternion.Euler(rotation) * direction, out hit, source.transform.rotation * Quaternion.Euler(rotation), length, ignoreLayers) ) { }; break; case CastType.SphereCast: // TODO: Make this not an if statement. Check that it works with NodeCanvas first if (Physics.SphereCast(relativeStartWithRotation, width / 2f, source.transform.rotation * Quaternion.Euler(rotation) * direction, out hit, length, ignoreLayers) ) { }; break; } if (hit.transform != null) { return true; } } return false; } public void DrawObserverGizmo(GameObject source){ if (gizmoDrawingCondition == ObserverGizmoDrawingCondition.Never || (gizmoDrawingCondition == ObserverGizmoDrawingCondition.OnlyActive ! & active)) { return; } Vector3 relativeStart = source.transform.position + offset; Vector3 relativeStartWithRotation = source.transform.position + source.transform.rotation * (offset); // Setup the variables for boxcast, spherecast, etc // Create an offset start point for the center of the wirebox, since gizmos are drawn with their pivot in the center. Vector3 offsetWithRotationAndLength = (Quaternion.LookRotation(direction) * Quaternion.Euler(rotation)) * (Vector3.forward * (length / 2)); Vector3 offsetFromCenter = relativeStartWithRotation + source.transform.rotation * offsetWithRotationAndLength; // Also create a rotation for use with the gizmos. Mainly just to shorten the lines Quaternion gizmosRotation = source.transform.rotation * Quaternion.LookRotation(direction) * Quaternion.Euler(rotation); Vector3 firstBoxOffset = gizmosRotation * (Vector3.forward * size.z); Color gizmoColor = Evaluate(source) ? Color.green : Color.red; gizmoColor = active ? gizmoColor : Color.gray; using (Draw.ingame.WithColor(gizmoColor)){ switch (castType) { case CastType.Ray: Draw.ingame.Line(relativeStart, relativeStart + (source.transform.rotation * direction.normalized) * length); break; case CastType.BoxOverlap: Draw.ingame.WireBox(relativeStartWithRotation, source.transform.rotation * Quaternion.Euler(rotation), size); break; case CastType.SphereCast: Draw.ingame.SolidCircle(relativeStartWithRotation, relativeStartWithRotation - Camera.main.transform.position, width * 1, gizmoColor.Alpha(.5f)); Draw.ingame.WireCapsule(relativeStartWithRotation, relativeStartWithRotation + gizmosRotation * (Vector3.forward * (length - width / 2)), width); break; case CastType.BoxCast: // Draw the gizmos for the boxcast Draw.ingame.WireBox(offsetFromCenter, gizmosRotation, new float3(size.x, size.y, length)); Draw.ingame.SolidBox(relativeStartWithRotation, gizmosRotation, size, gizmoColor.Alpha(.1f)); Draw.ingame.WireBox(relativeStartWithRotation, gizmosRotation, size); break; default: throw new ArgumentOutOfRangeException(); } Draw.ingame.SolidCircle(relativeStartWithRotation, relativeStartWithRotation - Camera.main.transform.position, .4f); Draw.ingame.SolidCircle(hit.point, hit.point - Camera.main.transform.position, .4f); // Set up variables for label (not hit name) Vector3 labelStartPos = Vector3.zero; switch (labelTextLocation) { case LabelDrawingLocation.PlayerOffset: labelStartPos = source.transform.position; break; case LabelDrawingLocation.IntersectingLength: labelStartPos = offsetFromCenter; break; case LabelDrawingLocation.HitLocation:{ if (hit.transform != null) { labelStartPos = hit.point; } break; } } // Draw label if (drawLabel) { Draw.ingame.Label3D( labelStartPos + labelLocationOffset, gizmosRotation * Quaternion.Euler(labelRotationOffset), label, labelSize, LabelAlignment.MiddleLeft, gizmoColor ); } // Set up variables for hit name // Since the label is already drawn just use the previous startPos switch (labelTextLocation) { case LabelDrawingLocation.PlayerOffset: labelStartPos = source.transform.position; break; case LabelDrawingLocation.IntersectingLength: labelStartPos = offsetFromCenter; break; case LabelDrawingLocation.HitLocation:{ if (hit.transform != null) { labelStartPos = hit.point; } break; } } // Draw hitname if (drawLabel) { Draw.ingame.Label3D( labelStartPos + labelLocationOffset, gizmosRotation * Quaternion.Euler(labelRotationOffset), label, hitTextSize, LabelAlignment.MiddleLeft, gizmoColor ); } } } static Color GetObserverStatusColorStatic(bool active, RaycastHit hit){ if (active) { if (hit.Equals(default(RaycastHit))) { return Color.green; } return Color.red; } return Color.gray; } } public class PlayerEnvironmentManager : MonoBehaviour{ [OdinSerialize] public List observers; void Start(){ CheckDuplicateLabels(observers); } // TODO: Not working. void CheckDuplicateLabels(List sourceList){ foreach (EnvironmentObserver sourceObserver in observers) { foreach (EnvironmentObserver observer in sourceList) { if (sourceObserver == observer) { continue; } if (sourceObserver.label == observer.label) { Debug.LogError($"Duplicate label found in observer: {observer.label} is in use multiple times"); } if (observer.children != null && observer.children.Count > 0) { CheckDuplicateLabels(observer.children); } } } } public bool EvaluateFromString(string searchLabel, List observerList = null){ List listToUse = observers; if (observerList != null) { listToUse = observerList; } foreach (EnvironmentObserver observer in listToUse) { if (observer.label == searchLabel) { return observer.Evaluate(gameObject); } if (observer.children != null && observer.children.Count > 0) { foreach (EnvironmentObserver childObserver in observer.children) { EvaluateFromString(searchLabel, childObserver.children); } } } return false; } public EnvironmentObserver FindObserverFromString(string searchLabel, List observerList = null){ List listToUse = observers; if (observerList != null) { listToUse = observerList; } foreach (EnvironmentObserver observer in listToUse) { if (observer.label == searchLabel) { return observer; } if (observer.children != null && observer.children.Count > 0) { foreach (EnvironmentObserver childObserver in observer.children) { FindObserverFromString(searchLabel, childObserver.children); } } } return null; } void Update(){ } void LateUpdate(){ // Draw Gizmos foreach (EnvironmentObserver observer in observers) { observer.DrawObserverGizmo(gameObject); if (observer.children != null && observer.children.Count > 0) { foreach (EnvironmentObserver childObserver in observer.children) { childObserver.DrawObserverGizmo(gameObject); } } } // Clear hit foreach (EnvironmentObserver observer in observers) { observer.hit = default; if (observer.children != null && observer.children.Count > 0) { foreach (EnvironmentObserver childObserver in observer.children) { childObserver.hit = default; } } } } }