using MalbersAnimations.Events; using System.Collections.Generic; using UnityEngine; namespace MalbersAnimations { [HelpURL("https://malbersanimations.gitbook.io/animal-controller/main-components/ai/wander-area")] /// Wander Area waypoint used on the Animal to wander around. [AddComponentMenu("Malbers/AI/AI Wander Area")] public class AIWanderArea : MWayPoint { public enum AreaType { Circle, Box }; [Tooltip("Type of Area to wander")] public AreaType m_AreaType = AreaType.Circle; [Min(0)] public float radius = 5; public Vector3 BoxArea = new Vector3(10, 1, 10); [Range(0, 1), Tooltip("Probability of keep wandering on this WayPoint Area")] public float WanderWeight = 1f; public Vector3 Destination { get; internal set; } private Transform currentNextTarget; // [SerializeField] private bool isChild; [SerializeField] private AIWanderArea MainArea; [SerializeField] private AIWanderArea[] ChildWanderAreas; bool IsChild => MainArea != this; protected override void OnEnable() { base.OnEnable(); FindWanderAreas(); if (!IsChild) GetNextDestination(); //Find the first random destination if it is a Main Wander Area currentNextTarget = MainArea.transform; //Store the current next target as this transform } private void FindWanderAreas() { MainArea = transform.parent != null ? (transform.parent.GetComponentInParent()) : this; if (MainArea == null) MainArea = this; //Re-check in case this wander area is child of something else ChildWanderAreas = null; if (!IsChild) { ChildWanderAreas = GetComponentsInChildren(); if (ChildWanderAreas != null) foreach (var wa in ChildWanderAreas) { wa.DebugColor = DebugColor; wa.stoppingDistance = stoppingDistance; } } } public Vector3 GetNextDestination() { if (!IsChild && ChildWanderAreas != null && ChildWanderAreas.Length > 1) //Means this area has multiple areas inside { return ChildWanderAreas[Random.Range(0, ChildWanderAreas.Length)].GetNextDestinationArea(); //Get a random point inlcuding the Main Wander Area } else { return GetNextDestinationArea(); } } private Vector3 GetNextDestinationArea() { switch (m_AreaType) { case AreaType.Circle: Vector2 vector2 = (Random.insideUnitCircle * radius); Destination = transform.TransformPoint(new Vector3(vector2.x, 0, vector2.y)); //Get the world position inside the circle break; case AreaType.Box: Destination = transform.TransformPoint(RandomPointInBox(BoxArea)); //Get the world position inside the Box break; default: Destination = transform.position; break; } MainArea.Destination = Destination; //Super Important MTools.DrawWireSphere(Destination, Color.red, 0.1f, 3); return MainArea.Destination; } public override Vector3 GetPosition() => GetNextDestination(); // => MainArea.Destination; public override float StopDistance() => MainArea.stoppingDistance; public override float SlowDistance() => MainArea.slowingDistance; public override Transform NextTarget() => MainArea.currentNextTarget; public override void TargetArrived(GameObject target) { MainArea.OnTargetArrived.Invoke(target); if (NextTargets != null && NextTargets.Count > 0) { var probability = UnityEngine.Random.Range(0f, 1f); if (probability <= WanderWeight) //Find the next destination on the same wander Area. { GetNextDestination(); currentNextTarget = transform; //Keep itself as the target } else //Find the next on one of the Next Targets. { currentNextTarget = NextTargets[UnityEngine.Random.Range(0, NextTargets.Count)]; //Get the next target } } else { GetNextDestination(); } } private Vector3 RandomPointInBox(Vector3 size) { return new Vector3( (Random.value - 0.5f) * size.x, (Random.value - 0.5f) * size.y, (Random.value - 0.5f) * size.z); } [HideInInspector, SerializeField] private bool ShowRadius; private void Reset() { DebugColor.a = 0.2f; } private void OnValidate() { FindWanderAreas(); //for the colors if (BoxArea.x < 0) BoxArea.x = 0; if (BoxArea.y < 0) BoxArea.y = 0; if (BoxArea.z < 0) BoxArea.z = 0; } #if UNITY_EDITOR private void OnDrawGizmos() { var DebugColorWire = DebugColor; DebugColorWire.a = 1; UnityEditor.Handles.color = DebugColorWire; UnityEditor.Handles.DrawWireDisc(transform.position, transform.up, stoppingDistance); UnityEditor.Handles.DrawWireDisc(transform.position, transform.up, slowingDistance); switch (m_AreaType) { case AreaType.Circle: UnityEditor.Handles.color = DebugColorWire; UnityEditor.Handles.matrix = Matrix4x4.TRS(transform.position, transform.rotation, transform.lossyScale); UnityEditor.Handles.DrawWireDisc(Vector3.zero, Vector3.up, radius); UnityEditor.Handles.color = DebugColor; UnityEditor.Handles.DrawSolidDisc(Vector3.zero, Vector3.up, radius); break; case AreaType.Box: var sizeX = transform.lossyScale.x * BoxArea.x; var sizeY = transform.lossyScale.y * BoxArea.y; var sizeZ = transform.lossyScale.z * BoxArea.z; Matrix4x4 rotationMatrix = Matrix4x4.TRS(transform.position, transform.rotation, new Vector3(sizeX, sizeY, sizeZ)); Gizmos.matrix = rotationMatrix; Gizmos.color = DebugColor; Gizmos.DrawCube(Vector3.zero, Vector3.one); Gizmos.color = DebugColorWire; Gizmos.DrawWireCube(Vector3.zero, Vector3.one); break; } } private void OnDrawGizmosSelected() { var DebugColorWire = DebugColor; DebugColorWire.a = 1; Gizmos.color = DebugColorWire; Gizmos.DrawRay(transform.position, transform.up * Height); Gizmos.DrawWireSphere(transform.position + transform.up * Height, Height * 0.1f); Gizmos.DrawWireSphere(transform.position, Height * 0.1f); if (nextWayPoints != null) { foreach (var item in nextWayPoints) { if (item) Gizmos.DrawLine(transform.position, item.position); } } } #endif } //INSPECTOR-------------- #region Inspector #if UNITY_EDITOR [UnityEditor.CustomEditor(typeof(AIWanderArea))] [UnityEditor.CanEditMultipleObjects] public class AIWanderAreaEditor : UnityEditor.Editor { UnityEditor.SerializedProperty pointType, stoppingDistance, slowingDistance, m_AreaType, m_height, radius, BoxArea, WaitTime, WanderWeight, nextWayPoints, DebugColor, OnTargetArrived; AIWanderArea M; private bool isChild; private void OnEnable() { M = (AIWanderArea)target; pointType = serializedObject.FindProperty("pointType"); stoppingDistance = serializedObject.FindProperty("stoppingDistance"); slowingDistance = serializedObject.FindProperty("slowingDistance"); m_AreaType = serializedObject.FindProperty("m_AreaType"); radius = serializedObject.FindProperty("radius"); BoxArea = serializedObject.FindProperty("BoxArea"); WaitTime = serializedObject.FindProperty("m_WaitTime"); WanderWeight = serializedObject.FindProperty("WanderWeight"); nextWayPoints = serializedObject.FindProperty("nextWayPoints"); DebugColor = serializedObject.FindProperty("DebugColor"); OnTargetArrived = serializedObject.FindProperty("OnTargetArrived"); m_height = serializedObject.FindProperty("m_height"); isChild = M.transform.parent != null && (M.transform.parent.GetComponentInParent() != null); } public override void OnInspectorGUI() { serializedObject.Update(); MalbersEditor.DrawDescription("Type of Waypoint that uses an Area to get the Destination point"); UnityEditor.EditorGUILayout.BeginVertical(MTools.StyleGray); { if (!isChild) { UnityEditor.EditorGUILayout.BeginVertical(UnityEditor.EditorStyles.helpBox); { UnityEditor.EditorGUILayout.BeginHorizontal(); UnityEditor.EditorGUILayout.PropertyField(pointType); UnityEditor.EditorGUILayout.PropertyField(DebugColor, GUIContent.none, GUILayout.Width(40)); UnityEditor.EditorGUILayout.EndHorizontal(); UnityEditor.EditorGUILayout.PropertyField(m_height); UnityEditor.EditorGUILayout.PropertyField(stoppingDistance); UnityEditor.EditorGUILayout.PropertyField(slowingDistance); UnityEditor.EditorGUILayout.PropertyField(WaitTime); } UnityEditor.EditorGUILayout.EndVertical(); } UnityEditor.EditorGUILayout.BeginVertical(UnityEditor.EditorStyles.helpBox); { UnityEditor.EditorGUILayout.PropertyField(m_AreaType); var aretype = (AIWanderArea.AreaType)m_AreaType.intValue; switch (aretype) { case AIWanderArea.AreaType.Circle: UnityEditor.EditorGUILayout.PropertyField(radius); break; case AIWanderArea.AreaType.Box: UnityEditor.EditorGUILayout.PropertyField(BoxArea); break; default: break; } } UnityEditor.EditorGUILayout.EndVertical(); if (isChild) { UnityEditor.EditorGUILayout.HelpBox("Type, Stop Distance, Wait Time, and Next Destination properties are handled by the parent Wander Area", UnityEditor.MessageType.Info); } else { UnityEditor.EditorGUILayout.BeginVertical(UnityEditor.EditorStyles.helpBox); { UnityEditor.EditorGUILayout.LabelField("Next Destination", UnityEditor.EditorStyles.boldLabel); UnityEditor.EditorGUILayout.PropertyField(WanderWeight); UnityEditor.EditorGUI.indentLevel++; UnityEditor.EditorGUILayout.PropertyField(nextWayPoints, true); UnityEditor.EditorGUI.indentLevel--; } UnityEditor.EditorGUILayout.EndVertical(); UnityEditor.EditorGUILayout.PropertyField(OnTargetArrived); } } UnityEditor.EditorGUILayout.EndVertical(); serializedObject.ApplyModifiedProperties(); } } #endif #endregion }