Files
EmeraidAI/Runtime/Scripts/Components/Required/Editor/EmeraldBehaviorsEditor.cs
T
2025-07-20 10:01:29 +08:00

279 lines
16 KiB
C#

using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using System.Reflection;
using System;
namespace EmeraldAI.Utility
{
[CustomEditor(typeof(EmeraldBehaviors), true)]
[CanEditMultipleObjects]
public class EmeraldBehaviorsEditor : Editor
{
GUIStyle FoldoutStyle;
Texture BehaviorsEditorIcon;
FieldInfo[] CustomFields;
SerializedProperty HideSettingsFoldout, BehaviorSettingsFoldout, CurrentBehaviorType, CustomSettingsFoldout, TargetToFollow, CautiousSeconds, ChaseSeconds,
FleeSeconds, RequireObstruction, InfititeChase, FleeOnLowHealth, StayNearStartingArea, MaxDistanceFromStartingArea, UpdateFleePositionSeconds, PercentToFlee, FollowingStoppingDistance;
void OnEnable()
{
if (BehaviorsEditorIcon == null) BehaviorsEditorIcon = Resources.Load("Editor Icons/EmeraldBehaviors") as Texture;
InitializeProperties();
}
void InitializeProperties()
{
HideSettingsFoldout = serializedObject.FindProperty("HideSettingsFoldout");
BehaviorSettingsFoldout = serializedObject.FindProperty("BehaviorSettingsFoldout");
CustomSettingsFoldout = serializedObject.FindProperty("CustomSettingsFoldout");
CurrentBehaviorType = serializedObject.FindProperty("CurrentBehaviorType");
TargetToFollow = serializedObject.FindProperty("TargetToFollow");
CautiousSeconds = serializedObject.FindProperty("CautiousSeconds");
ChaseSeconds = serializedObject.FindProperty("ChaseSeconds");
FleeSeconds = serializedObject.FindProperty("FleeSeconds");
RequireObstruction = serializedObject.FindProperty("RequireObstruction");
InfititeChase = serializedObject.FindProperty("InfititeChase");
FleeOnLowHealth = serializedObject.FindProperty("FleeOnLowHealth");
StayNearStartingArea = serializedObject.FindProperty("StayNearStartingArea");
UpdateFleePositionSeconds = serializedObject.FindProperty("UpdateFleePositionSeconds");
PercentToFlee = serializedObject.FindProperty("PercentToFlee");
MaxDistanceFromStartingArea = serializedObject.FindProperty("MaxDistanceFromStartingArea");
FollowingStoppingDistance = serializedObject.FindProperty("FollowingStoppingDistance");
//Get all variables that are not part of the parent class.
CustomFields = target.GetType().GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
}
public override void OnInspectorGUI()
{
FoldoutStyle = CustomEditorProperties.UpdateEditorStyles();
EmeraldBehaviors self = (EmeraldBehaviors)target;
serializedObject.Update();
CustomEditorProperties.BeginScriptHeaderNew("Behaviors", BehaviorsEditorIcon, new GUIContent(), HideSettingsFoldout);
if (!HideSettingsFoldout.boolValue)
{
EditorGUILayout.Space();
BehaviorSettings(self);
if (self.GetType().ToString() != "EmeraldAI.EmeraldBehaviors")
{
EditorGUILayout.Space();
CustomSettings(self);
}
EditorGUILayout.Space();
}
CustomEditorProperties.EndScriptHeader();
serializedObject.ApplyModifiedProperties();
}
void BehaviorSettings (EmeraldBehaviors self)
{
BehaviorSettingsFoldout.boolValue = EditorGUILayout.Foldout(BehaviorSettingsFoldout.boolValue, "Behavior Settings", true, FoldoutStyle);
if (BehaviorSettingsFoldout.boolValue)
{
CustomEditorProperties.BeginFoldoutWindowBox();
CustomEditorProperties.TextTitleWithDescription("Behavior Settings", "Choose from 1 of the 3 available base behavior types. Companion and Pet options are avaialble witin these options by setting a Target to Follow.", true);
EditorGUILayout.Space();
EditorGUILayout.Space();
CustomEditorProperties.CustomPropertyField(CurrentBehaviorType, "Current Behavior Type", "The behavior this AI will use.", true); //Overridding the method will allow users to create their own version of the specified behavior.
PassiveSettings(self);
CowardSettings(self);
AggressiveSettings(self);
CustomEditorProperties.EndFoldoutWindowBox();
}
}
/// <summary>
/// Displays all options that are intended for the Passive Behavior Type.
/// </summary>
void PassiveSettings (EmeraldBehaviors self)
{
if (self.CurrentBehaviorType != EmeraldBehaviors.BehaviorTypes.Passive)
return;
CustomEditorProperties.TextTitleWithDescription("Passive Settings", "Passive AI will not attack or flee from targets. They will wander according to their Wander Type set within the Movement Component.", true);
CustomEditorProperties.CustomPropertyField(TargetToFollow, "Target to Follow", "Assigning a Target to Follow will turn an AI into a Pet AI (or a non-combat Componanion AI). Note: If a Target to Follow is assigned, they will ignore their Wander Type and follow their follower instead.", true);
if (self.TargetToFollow)
{
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), FollowingStoppingDistance, "Following Stopping Distance", 1, 15);
CustomEditorProperties.CustomHelpLabelField("Controls the distance in which an AI will stop from their Target to Follow.", true);
}
}
/// <summary>
/// Displays all options that are intended for the Aggressive Behavior Type.
/// </summary>
void CowardSettings(EmeraldBehaviors self)
{
if (self.CurrentBehaviorType != EmeraldBehaviors.BehaviorTypes.Coward)
return;
CustomEditorProperties.TextTitleWithDescription("Coward Settings", "Coward AI will flee from targets who they have an Enemy Relation Type with.", true);
CustomEditorProperties.CustomIntSliderPropertyField(CautiousSeconds, "Cautious Seconds", "Controls the amount of time an AI will remain in the Cautious State before fleeing from their target. " +
"If an AI has a warning animation, this will automatically be played while in this state. If this value is set to 0, the cautious state will be ignored.", 0, 15, false);
if (self.CautiousSeconds > 0)
{
CustomEditorProperties.DisplayImportantMessage("Be aware that AI who are in a cautious state will not flee from a detected target until after the duration of their Cautious Seconds (unless they're attacked).");
}
EditorGUI.BeginDisabledGroup(self.InfititeChase);
CustomEditorProperties.CustomIntSliderPropertyField(FleeSeconds, "Flee Seconds", "Controls the amount of time an AI will flee from a target for returning its non-combat state. This happens when the current target is outside of an AI's detection radius.", 1, 60, true);
CustomEditorProperties.CustomPropertyField(RequireObstruction, "Require Obstruction", "Only allow the flee time to increase if the AI's current target is obstructed. This allows the AI to continuously flee the target while they are visible, " +
"but give up if the target has been obstructed (or not visible) for the duration of the Flee Seconds.", true);
EditorGUILayout.Space();
CustomEditorProperties.CustomFloatSliderPropertyField(UpdateFleePositionSeconds, "Update Flee Position Seconds", "Controls how often the flee position will be updated.", 0.25f, 5f, true);
EditorGUILayout.Space();
}
/// <summary>
/// Displays all options that are intended for the Aggressive Behavior Type.
/// </summary>
void AggressiveSettings (EmeraldBehaviors self)
{
if (self.CurrentBehaviorType != EmeraldBehaviors.BehaviorTypes.Aggressive)
return;
CustomEditorProperties.TextTitleWithDescription("Aggressive Settings", "Aggressive AI will attack targets who they have an Enemy Relation Type with.", true);
CustomEditorProperties.CustomPropertyField(TargetToFollow, "Target to Follow", "Assigning a Target to Follow will turn an AI into a Companion AI. They will also ignore their Wander Type and follow their the specified instead. Note: AI who have currently have a Target to Follow cannot use any of the settings below.", true);
if (self.TargetToFollow)
{
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), FollowingStoppingDistance, "Following Stopping Distance", 1, 15);
CustomEditorProperties.CustomHelpLabelField("Controls the distance in which an AI will stop from their Target to Follow.", true);
}
EditorGUI.BeginDisabledGroup(self.TargetToFollow);
CustomEditorProperties.CustomIntSliderPropertyField(CautiousSeconds, "Cautious Seconds", "Controls the amount of time an AI will remain in the Cautious State before attacking their target. " +
"If an AI has a warning animation, this will automatically be played while in this state. If this value is set to 0, the cautious state will be ignored.", 0, 15, false);
if (self.CautiousSeconds > 0)
{
CustomEditorProperties.DisplayImportantMessage("Be aware that AI who are in a cautious state will not attack a detected target until after the duration of their Cautious Seconds (unless they're attacked).");
}
EditorGUILayout.Space();
CustomEditorProperties.CustomPropertyField(InfititeChase, "Infitite Chase", "Controls whether or not the AI will chase their target without any distance or time resrtictions. Note: This will disable the Chase Seconds and Stay Near Starting Area settings.", true);
EditorGUI.BeginDisabledGroup(self.InfititeChase);
CustomEditorProperties.CustomIntSliderPropertyField(ChaseSeconds, "Chase Seconds", "Controls the amount of time an AI will chase a target for before giving up and exiting its combat state. This happens when the current target is outside of an AI's detection radius.", 1, 60, true);
CustomEditorProperties.CustomPropertyField(RequireObstruction, "Require Obstruction", "Only allow the chase time to increase if the AI's current target is obstructed. This allows the AI to continuously chase the target while they are visible, " +
"but give up if the target has been obstructed (or not visible) for the duration of the Chase Seconds.", true);
CustomEditorProperties.CustomPropertyField(StayNearStartingArea, "Stay Near Starting Area", "Controls whether or not an AI will give up on a target if it gets too far away from its starting area.", true);
if (self.StayNearStartingArea == YesOrNo.Yes)
{
CustomEditorProperties.BeginIndent();
CustomEditorProperties.CustomIntSliderPropertyField(MaxDistanceFromStartingArea, "Max Distance From Starting Area", "Controls the maximum distance an AI is allowed to be from its starting area before giving up on a target and return to its starting position or area.", 10, 100, true);
CustomEditorProperties.EndIndent();
}
EditorGUI.EndDisabledGroup();
CustomEditorProperties.CustomPropertyField(FleeOnLowHealth, "Flee on Low Health", "Controls whether or not an AI will flee upon low health while in combat.", true);
if (self.FleeOnLowHealth == YesOrNo.Yes)
{
CustomEditorProperties.BeginIndent();
CustomEditorProperties.CustomIntSliderPropertyField(PercentToFlee, "Percent to Flee", "Controls the percentage of low health needed to flee.", 1, 99, true);
CustomEditorProperties.CustomFloatSliderPropertyField(UpdateFleePositionSeconds, "Update Flee Position Seconds", "Controls how often the flee position will be updated.", 0.25f, 5f, true);
CustomEditorProperties.EndIndent();
}
EditorGUI.EndDisabledGroup();
EditorGUILayout.Space();
}
void OnSceneGUI()
{
EmeraldBehaviors self = (EmeraldBehaviors)target;
DrawStartingAreaDistance(self);
}
/// <summary>
/// Draws the wander area, when using the Dynamic Wander Type.
/// </summary>
void DrawStartingAreaDistance(EmeraldBehaviors self)
{
if (self.StayNearStartingArea == YesOrNo.Yes && BehaviorSettingsFoldout.boolValue && !HideSettingsFoldout.boolValue)
{
Handles.color = new Color(0, 0.6f, 0, 1f);
Handles.DrawWireDisc(self.transform.position, Vector3.up, (float)self.MaxDistanceFromStartingArea, 3f);
Handles.color = Color.white;
}
}
/// <summary>
/// Displays all custom variables in a separate part of the editor.
/// </summary>
void CustomSettings(EmeraldBehaviors self)
{
CustomSettingsFoldout.boolValue = EditorGUILayout.Foldout(CustomSettingsFoldout.boolValue, "Custom Settings", true, FoldoutStyle);
if (CustomSettingsFoldout.boolValue)
{
CustomEditorProperties.BeginFoldoutWindowBox();
CustomEditorProperties.TextTitleWithDescription("Custom Settings", "Any variables added through a child class of EmeraldBehavior will be added here.", true);
foreach (FieldInfo field in CustomFields)
{
//Offset Arrays with extra space
if (field.FieldType.GetElementType() != null)
{
EditorGUILayout.BeginHorizontal();
GUILayout.Space(15);
EditorGUILayout.PropertyField(serializedObject.FindProperty(field.Name));
GUILayout.Space(1);
EditorGUILayout.EndHorizontal();
}
//Offset Lists with extra space
else if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(List<>))
{
EditorGUILayout.BeginHorizontal();
GUILayout.Space(15);
EditorGUILayout.PropertyField(serializedObject.FindProperty(field.Name));
GUILayout.Space(1);
EditorGUILayout.EndHorizontal();
}
else if (field.FieldType.IsClass && field.FieldType.ToString() != "System.String" && !field.FieldType.ToString().Contains("Unity"))
{
Debug.Log(field.FieldType);
EditorGUILayout.BeginHorizontal();
GUILayout.Space(15);
EditorGUILayout.PropertyField(serializedObject.FindProperty(field.Name));
GUILayout.Space(1);
EditorGUILayout.EndHorizontal();
}
//Don't apply an offset to single variables
else
{
if (serializedObject.FindProperty(field.Name) != null)
{
EditorGUILayout.PropertyField(serializedObject.FindProperty(field.Name));
}
}
}
EditorGUILayout.Space();
CustomEditorProperties.EndFoldoutWindowBox();
}
}
}
}