init 1.1.2

This commit is contained in:
2025-07-20 10:01:29 +08:00
commit 2afbbf9be4
1327 changed files with 1596159 additions and 0 deletions
@@ -0,0 +1,285 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Reflection;
using UnityEditorInternal;
using System.Linq;
using UnityEditor.SceneManagement;
using UnityEngine.SceneManagement;
namespace EmeraldAI.Utility
{
[CustomEditor(typeof(EmeraldAnimation))]
[CanEditMultipleObjects]
public class EmeraldAnimationEditor : Editor
{
public static EditorWindow EditorWindowRef;
#region SerializedProperties
List<string> Type1AttackAnimationEnum = new List<string>();
List<string> Type2AttackAnimationEnum = new List<string>();
GUIStyle FoldoutStyle;
Texture AnimationsEditorIcon;
bool IsInScene;
bool IsInPrefabInstance;
SerializedProperty AnimationProfileProp, HideSettingsFoldout, AnimationProfileFoldout;
#endregion
void OnEnable()
{
EmeraldAnimation self = (EmeraldAnimation)target;
self.AIAnimator = self.GetComponent<Animator>();
if (AnimationsEditorIcon == null) AnimationsEditorIcon = Resources.Load("Editor Icons/EmeraldAnimation") as Texture;
ApplyRuntimeAnimatorController(self);
UpdateAbilityAnimationEnums();
InitializeProperties();
IsInScene = self.gameObject.scene.IsValid();
IsInPrefabInstance = StageUtility.GetStage(self.gameObject) != StageUtility.GetMainStage();
}
void InitializeProperties()
{
HideSettingsFoldout = serializedObject.FindProperty("HideSettingsFoldout");
AnimationProfileFoldout = serializedObject.FindProperty("AnimationProfileFoldout");
AnimationProfileProp = serializedObject.FindProperty("m_AnimationProfile");
}
public override void OnInspectorGUI()
{
FoldoutStyle = CustomEditorProperties.UpdateEditorStyles();
EmeraldAnimation self = (EmeraldAnimation)target;
serializedObject.Update();
CustomEditorProperties.BeginScriptHeaderNew("Animations", AnimationsEditorIcon, new GUIContent(), HideSettingsFoldout);
MissingAnimationProfileMessage(self);
if (!HideSettingsFoldout.boolValue)
{
EditorGUILayout.Space();
AnimationProfiles(self);
EditorGUILayout.Space();
UpdateEditor(self);
}
serializedObject.ApplyModifiedProperties();
CustomEditorProperties.EndScriptHeader();
}
/// <summary>
/// Displays a missing Animation Profile message within the EmeraldAnimation.
/// </summary>
void MissingAnimationProfileMessage(EmeraldAnimation self)
{
if (self.m_AnimationProfile == null)
{
CustomEditorProperties.DisplaySetupWarning("This AI needs to have an Animation Profile. Press the 'Create New Animation Profile' button below to create a new one or assign one that has already been created.");
}
else if (self.m_AnimationProfile.AIAnimator == null)
{
CustomEditorProperties.DisplaySetupWarning("This AI has an Animation Profile, but an Animator Controller has not been generated for it. Please create one and assign all needed animations through the Animation Profile object. " +
"You can press the 'Edit Animation Profile' to open up an editor window to begin editing.");
}
}
void UpdateAbilityAnimationEnums()
{
EmeraldAnimation self = (EmeraldAnimation)target;
if (self.m_AnimationProfile == null)
return;
//Populate the Type1AttackEnumAnimations array with the proper animation name.
if (self.m_AnimationProfile.Type1Animations.AttackList.Count > 0)
{
for (int i = 0; i < self.m_AnimationProfile.Type1Animations.AttackList.Count; i++)
{
if (self.m_AnimationProfile.Type1Animations.AttackList[i].AnimationClip != null)
Type1AttackAnimationEnum.Add(self.m_AnimationProfile.Type1Animations.AttackList[i].AnimationClip.name);
}
}
//Populate the Type2AttackEnumAnimations array with the proper animation name.
if (self.m_AnimationProfile.Type2Animations.AttackList.Count > 0)
{
for (int i = 0; i < self.m_AnimationProfile.Type2Animations.AttackList.Count; i++)
{
if (self.m_AnimationProfile.Type2Animations.AttackList[i].AnimationClip != null)
Type2AttackAnimationEnum.Add(self.m_AnimationProfile.Type2Animations.AttackList[i].AnimationClip.name);
}
}
//Pass the array to the EmeraldAnimation script so it can be stored
self.Type1AttackEnumAnimations = Type1AttackAnimationEnum.ToArray();
self.Type2AttackEnumAnimations = Type2AttackAnimationEnum.ToArray();
}
void AnimationProfiles(EmeraldAnimation self)
{
AnimationProfileFoldout.boolValue = CustomEditorProperties.Foldout(AnimationProfileFoldout.boolValue, "Animation Profile Settings", true, FoldoutStyle);
if (AnimationProfileFoldout.boolValue)
{
CustomEditorProperties.BeginFoldoutWindowBox();
CustomEditorProperties.TextTitleWithDescription("Animation Profile", "An Animation Profile holds all of an AI's animation data, including the Animator Controller this AI will use. This allows AI to share the same animation data with only needing to rely on a single " +
"Animation Profile. Any changes made to an Animation Profile will affect any AI using that Animation Profile.", false);
CustomEditorProperties.TextTitleWithDescription("Note", "The animations must be compatible with this model and share the same Rig Type. If your AI doesn't play animations correctly, or falls through the floor, it is likely that you are missing an animation, the " +
"Rig Type is not compatible, or that the animation is not compatible.", true);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(AnimationProfileProp);
CustomEditorProperties.CustomHelpLabelField("The Animation Profile this AI is using. All animations, including the Animator Controller, will be used for this AI and any other AI using it.", false);
if (!IsInScene)
{
CustomEditorProperties.DisplayImportantMessage("The Animation Viewer can't be used in the Project tab. The AI must be within the Scene and in the Hierarchy tab.");
}
if (IsInPrefabInstance)
{
CustomEditorProperties.DisplayImportantMessage("The Animation Viewer can't be used while editing a prefab.");
}
EditorGUI.BeginDisabledGroup(!IsInScene || IsInPrefabInstance);//AA
EditorGUI.BeginDisabledGroup(self.m_AnimationProfile == null || self.m_AnimationProfile.AIAnimator == null);
EditorGUILayout.Space();
if (GUILayout.Button(new GUIContent("Open Animation Viewer", "Preview all animations on the current Animation Profile, in real-time, on this AI within the Unity Scene."), GUILayout.Height(20)))
{
OpenAnimationPreview(self);
}
GUILayout.Space(2.5f);
EditorGUI.EndDisabledGroup();
EditorGUI.EndDisabledGroup();
EditorGUI.BeginDisabledGroup(self.m_AnimationProfile == null);
if (GUILayout.Button(new GUIContent("Edit Animation Profile", "Edit the current Animation Profile in a separate window so you can preview animations while keeping a reference to the current Animation Profile."), GUILayout.Height(20)))
{
EditAnimationProfile(self);
}
GUILayout.Space(2.5f);
EditorGUI.EndDisabledGroup();
EditorGUI.BeginDisabledGroup(self.m_AnimationProfile == null);
if (GUILayout.Button(new GUIContent("Clear Animation Profile", "Clears the Animation Profile slot so a new one can be created. Note: The current Animation Profile object will remain in your project at its current path."), GUILayout.Height(20)))
{
AnimationProfileProp.objectReferenceValue = null;
serializedObject.FindProperty("AIAnimator").objectReferenceValue = null;
self.AnimatorControllerGenerated = false;
serializedObject.ApplyModifiedProperties();
}
GUILayout.Space(2.5f);
EditorGUI.EndDisabledGroup();
EditorGUI.BeginDisabledGroup(self.m_AnimationProfile != null);
if (GUILayout.Button(new GUIContent("Create New Animation Profile", "Creates a new Animation Profile within the Emerald AI/Animation Profiles folder. If you would like to create a new Animation Profile, remove the one in the current slot by pressing the 'Clear Animation Profile' button."), GUILayout.Height(20)))
{
CreateAnimationProfile(self);
}
GUILayout.Space(2.5f);
EditorGUI.EndDisabledGroup();
CustomEditorProperties.EndFoldoutWindowBox();
}
}
/// <summary>
/// Applies the Runtime Animator Controller from the Animation Profile to the AI's Animator.
/// </summary>
void ApplyRuntimeAnimatorController (EmeraldAnimation self)
{
if (self.AIAnimator != null && self.m_AnimationProfile != null && self.AIAnimator.runtimeAnimatorController == null && self.m_AnimationProfile.AIAnimator != null ||
self.AIAnimator != null && self.m_AnimationProfile != null && self.m_AnimationProfile.AIAnimator != null && self.AIAnimator != self.m_AnimationProfile.AIAnimator)
self.AIAnimator.runtimeAnimatorController = self.m_AnimationProfile.AIAnimator;
}
/// <summary>
/// Creates a new Animation Profile object, using the object's name, to the user set folder.
/// </summary>
void CreateAnimationProfile(EmeraldAnimation self)
{
string FilePath = EditorUtility.SaveFilePanelInProject("Save as Animation Profile", "", "asset", "Please enter a file name to save the file to");
if (string.IsNullOrEmpty(FilePath))
{
//For some reason, EditorUtility.SaveFilePanelInProject throws an incorrect EditorGUILayout error when it's used with some custom properties. This fixes it...
CustomEditorProperties.BeginScriptHeader("", null);
CustomEditorProperties.BeginFoldoutWindowBox();
return;
}
if (string.IsNullOrEmpty(AssetDatabase.AssetPathToGUID(FilePath)))
{
AnimationProfile NewAnimationProfile = CreateInstance<AnimationProfile>();
AssetDatabase.CreateAsset(NewAnimationProfile, FilePath);
self.m_AnimationProfile = NewAnimationProfile;
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
serializedObject.ApplyModifiedProperties();
}
else
{
var ExistingAnimationProfile = AssetDatabase.LoadAssetAtPath(FilePath, typeof(AnimationProfile));
self.m_AnimationProfile = (AnimationProfile)ExistingAnimationProfile;
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
//For some reason, EditorUtility.SaveFilePanelInProject throws an incorrect EditorGUILayout error when it's used with some custom properties. This fixes it...
CustomEditorProperties.BeginScriptHeader("", null);
CustomEditorProperties.BeginFoldoutWindowBox();
}
void OpenAnimationPreview (EmeraldAnimation self)
{
var m_AnimationPreviewEditor = (AnimationViewerManager)EditorWindow.GetWindow(typeof(AnimationViewerManager), true, "Animation Viewer Manager");
m_AnimationPreviewEditor.Initialize(self.gameObject);
}
void EditAnimationProfile (EmeraldAnimation self)
{
if (self.m_AnimationProfile == null)
return;
//Close the static reference to any other Animation Profile PropertyEditors before creating a new one
if (EditorWindowRef != null && EditorWindowRef.name == "Animation Profile")
EditorWindowRef.Close();
System.Type propertyEditorType = typeof(Editor).Assembly.GetType("UnityEditor.PropertyEditor");
System.Type[] callTypes = new[] { typeof(Object), typeof(bool) };
object[] callOpenBuffer = { null, true };
//Use reflection to create a PropertyEditor, as there's no API to do so before Unity 2021.2, and pass the Animation Profile to open it in a separate tab.
MethodInfo openPropertyEditorInfo;
openPropertyEditorInfo = propertyEditorType.GetMethod("OpenPropertyEditor", BindingFlags.Static | BindingFlags.NonPublic, null, callTypes, null);
self.m_AnimationProfile.EmeraldAnimationComponent = self; //Used for updating changes the currently edited AI (given that it isn't null)
callOpenBuffer[0] = self.m_AnimationProfile;
openPropertyEditorInfo.Invoke(null, callOpenBuffer);
//Cache the PropertyEditor and name it Sound Profile (only one can be active at a time)
EditorWindowRef = EditorWindow.GetWindow(typeof(Editor).Assembly.GetType("UnityEditor.PropertyEditor"));
EditorWindowRef.name = "Animation Profile";
EditorWindowRef.minSize = new Vector2(Screen.currentResolution.width / 4f, Screen.currentResolution.height / 2f);
}
void UpdateEditor (EmeraldAnimation self)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
Undo.RecordObject(self, "Undo");
if (GUI.changed)
{
EditorUtility.SetDirty(target);
EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
}
}
#endif
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 20a8ffcfdc54c2947a2a59b32358ba5c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,279 @@
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();
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 75ff12b1671dfcd4b9d0726e8b36e052
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,559 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using UnityEditor;
using UnityEditorInternal;
using System.Linq;
namespace EmeraldAI.Utility
{
[CustomEditor(typeof(EmeraldCombat))]
[CanEditMultipleObjects]
public class EmeraldCombatEditor : Editor
{
GUIStyle FoldoutStyle;
float CurrentAttackDistance = 0;
float CurrentTooCloseDistance = 0;
bool DrawDistanceActive = false;
Texture CombatEditorIcon;
EmeraldAnimation EmeraldAnimation;
EmeraldBehaviors EmeraldBehaviors;
SerializedProperty HideSettingsFoldout, DamageSettingsFoldout, WeaponType1SettingsFoldout, WeaponType2SettingsFoldout, SwitchWeaponSettingsFoldout, CombatActionSettingsFoldout;
//Enums
SerializedProperty Type1PickTargetTypeProp, Type2PickTargetTypeProp, SwitchWeaponTypeProp, StartingWeaponTypeProp, Type1AttackPickTypeProp, Type2AttackPickTypeProp;
//Int
SerializedProperty SwitchWeaponTimeMinProp, SwitchWeaponTimeMaxProp, SwitchWeaponTypesDistanceProp, SwitchWeaponTypesCooldownProp, MinResumeWanderProp, MaxResumeWanderProp;
//Float
SerializedProperty Type1AttackCooldownProp, Type2AttackCooldownProp;
ReorderableList Type1Attacks, Type2Attacks, WeaponType1AttackTransforms, WeaponType2AttackTransforms, Type1ActionsList, Type2ActionsList;
string AttackTransformTooltip = "Each Attack Transform can be used individually during an CreateAbility by passing the Attack Transform's " +
"name through the String parameter of the Animation Event. This allows an AI to have customizable points that attacks or abilities can come from, such as a grenade from a hand, a bullet from a barrel, or a spell from an AI's staff." +
"\n\nNote: It is best to keep Attack Transform names consistent to allow them to work across multiple AI.";
void OnEnable()
{
EmeraldCombat self = (EmeraldCombat)target;
EmeraldAnimation = self.GetComponent<EmeraldAnimation>();
EmeraldBehaviors = self.GetComponent<EmeraldBehaviors>();
if (CombatEditorIcon == null) CombatEditorIcon = Resources.Load("Editor Icons/EmeraldCombat") as Texture;
InitializeProperties();
InitializeLists(self);
}
void InitializeProperties()
{
//Bool
HideSettingsFoldout = serializedObject.FindProperty("HideSettingsFoldout");
DamageSettingsFoldout = serializedObject.FindProperty("DamageSettingsFoldout");
CombatActionSettingsFoldout = serializedObject.FindProperty("CombatActionSettingsFoldout");
SwitchWeaponSettingsFoldout = serializedObject.FindProperty("SwitchWeaponSettingsFoldout");
WeaponType1SettingsFoldout = serializedObject.FindProperty("WeaponType1SettingsFoldout");
WeaponType2SettingsFoldout = serializedObject.FindProperty("WeaponType2SettingsFoldout");
//Enums
SwitchWeaponTypeProp = serializedObject.FindProperty("SwitchWeaponType");
StartingWeaponTypeProp = serializedObject.FindProperty("StartingWeaponType");
Type1AttackPickTypeProp = serializedObject.FindProperty("Type1Attacks.AttackPickType");
Type2AttackPickTypeProp = serializedObject.FindProperty("Type2Attacks.AttackPickType");
Type1PickTargetTypeProp = serializedObject.FindProperty("Type1PickTargetType");
Type2PickTargetTypeProp = serializedObject.FindProperty("Type2PickTargetType");
//Int
SwitchWeaponTimeMinProp = serializedObject.FindProperty("SwitchWeaponTimeMin");
SwitchWeaponTimeMaxProp = serializedObject.FindProperty("SwitchWeaponTimeMax");
SwitchWeaponTypesDistanceProp = serializedObject.FindProperty("SwitchWeaponTypesDistance");
SwitchWeaponTypesCooldownProp = serializedObject.FindProperty("SwitchWeaponTypesCooldown");
MinResumeWanderProp = serializedObject.FindProperty("MinResumeWander");
MaxResumeWanderProp = serializedObject.FindProperty("MaxResumeWander");
//Float
Type1AttackCooldownProp = serializedObject.FindProperty("Type1AttackCooldown");
Type2AttackCooldownProp = serializedObject.FindProperty("Type2AttackCooldown");
}
void InitializeLists (EmeraldCombat self)
{
//Type 1 AttackTransforms
WeaponType1AttackTransforms = new ReorderableList(serializedObject, serializedObject.FindProperty("WeaponType1AttackTransforms"), true, true, true, true);
WeaponType1AttackTransforms.drawHeaderCallback = rect =>
{
EditorGUI.LabelField(rect, "Weapon Type 1 Attack Transforms List", EditorStyles.boldLabel);
};
WeaponType1AttackTransforms.drawElementCallback =
(Rect rect, int index, bool isActive, bool isFocused) =>
{
var element = WeaponType1AttackTransforms.serializedProperty.GetArrayElementAtIndex(index);
EditorGUI.PropertyField(new Rect(rect.x, rect.y + 3f, rect.width, EditorGUIUtility.singleLineHeight), element, new GUIContent("Attack Transform " + (index + 1), AttackTransformTooltip));
};
//Type 2 AttackTransforms
WeaponType2AttackTransforms = new ReorderableList(serializedObject, serializedObject.FindProperty("WeaponType2AttackTransforms"), true, true, true, true);
WeaponType2AttackTransforms.drawHeaderCallback = rect =>
{
EditorGUI.LabelField(rect, "Weapon Type 2 Attack Transforms List", EditorStyles.boldLabel);
};
WeaponType2AttackTransforms.drawElementCallback =
(Rect rect, int index, bool isActive, bool isFocused) =>
{
var element = WeaponType2AttackTransforms.serializedProperty.GetArrayElementAtIndex(index);
EditorGUI.PropertyField(new Rect(rect.x, rect.y + 3f, rect.width, EditorGUIUtility.singleLineHeight), element, new GUIContent("Attack Transform " + (index + 1), AttackTransformTooltip));
};
//TODO: Make into function so it can be used with type 1 and type 2
//Type 1 Attacks
Type1Attacks = new ReorderableList(serializedObject, serializedObject.FindProperty("Type1Attacks").FindPropertyRelative("AttackDataList"), true, true, true, true);
Type1Attacks.drawHeaderCallback = rect => {
EditorGUI.LabelField(rect, "Type 1 Attacks", EditorStyles.boldLabel);
};
Type1Attacks.drawElementCallback =
(Rect rect, int index, bool isActive, bool isFocused) => {
var element = Type1Attacks.serializedProperty.GetArrayElementAtIndex(index);
Type1Attacks.elementHeight = EditorGUIUtility.singleLineHeight * 7.5f;
if (self.Type1Attacks.AttackDataList.Count > 0 && EmeraldAnimation.Type1AttackEnumAnimations != null)
{
CustomEditorProperties.CustomListPopup(new Rect(rect.x + 125, rect.y + 10, rect.width - 125, EditorGUIUtility.singleLineHeight), new GUIContent(), element.FindPropertyRelative("AttackAnimation"), "Attack Animation", EmeraldAnimation.Type1AttackEnumAnimations);
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 10, 125, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Animation", "The animation that will be used for this attack.\n\nNote: Animations are based off of your AI's Attack Animation List within its Animation Profile."));
}
else
{
EditorGUI.Popup(new Rect(rect.x + 125, rect.y + 10, rect.width - 125, EditorGUIUtility.singleLineHeight), 0, EmeraldAnimation.Type1AttackBlankOptions);
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 10, 125, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Animation"));
}
if (isActive)
{
CurrentAttackDistance = element.FindPropertyRelative("AttackDistance").floatValue;
CurrentTooCloseDistance = element.FindPropertyRelative("TooCloseDistance").floatValue;
DrawDistanceActive = true;
}
if (element.FindPropertyRelative("AttackDistance").floatValue == 0) element.FindPropertyRelative("AttackDistance").floatValue = 2;
if (element.FindPropertyRelative("TooCloseDistance").floatValue == 0) element.FindPropertyRelative("TooCloseDistance").floatValue = 0.5f;
EditorGUI.ObjectField(new Rect(rect.x, rect.y + 35, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("AbilityObject"), new GUIContent("Ability Object", "The Ability Object that will be used for this attack."));
CustomEditorProperties.CustomListFloatSlider(new Rect(rect.x, rect.y + 60, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Distance", "Controls the distance that this attack can happen within."), element.FindPropertyRelative("AttackDistance"), 0.5f, 75f);
CustomEditorProperties.CustomListFloatSlider(new Rect(rect.x, rect.y + 85, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Too Close Distance", "Controls the distance for when an AI will backup. This is useful for AI keeping their distance from attackers."), element.FindPropertyRelative("TooCloseDistance"), 0f, 35f);
EditorGUI.BeginDisabledGroup(self.Type1Attacks.AttackPickType != AttackPickTypes.Odds);
CustomEditorProperties.CustomListIntSlider(new Rect(rect.x, rect.y + 110, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Odds", "The odds that this attack will be used (when using the Odds Pick type)."), element.FindPropertyRelative("AttackOdds"), 1, 100);
EditorGUI.EndDisabledGroup();
};
Type1Attacks.drawHeaderCallback = rect =>
{
EditorGUI.LabelField(rect, "Type 1 Attacks", EditorStyles.boldLabel);
};
//Type 2 Attacks
Type2Attacks = new ReorderableList(serializedObject, serializedObject.FindProperty("Type2Attacks").FindPropertyRelative("AttackDataList"), true, true, true, true);
Type2Attacks.drawHeaderCallback = rect => {
EditorGUI.LabelField(rect, "Type 2 Attacks", EditorStyles.boldLabel);
};
Type2Attacks.drawElementCallback =
(Rect rect, int index, bool isActive, bool isFocused) => {
var element = Type2Attacks.serializedProperty.GetArrayElementAtIndex(index);
Type2Attacks.elementHeight = EditorGUIUtility.singleLineHeight * 7.5f;
if (self.Type2Attacks.AttackDataList.Count > 0 && EmeraldAnimation.Type2AttackEnumAnimations != null)
{
CustomEditorProperties.CustomListPopup(new Rect(rect.x + 125, rect.y + 10, rect.width - 125, EditorGUIUtility.singleLineHeight), new GUIContent(), element.FindPropertyRelative("AttackAnimation"), "Attack Animation", EmeraldAnimation.Type2AttackEnumAnimations);
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 10, 125, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Animation", "The animation that will be used for this attack.\n\nNote: Animations are based off of your AI's Attack Animation List within its Animation Profile."));
}
else
{
EditorGUI.Popup(new Rect(rect.x + 125, rect.y + 10, rect.width - 125, EditorGUIUtility.singleLineHeight), 0, EmeraldAnimation.Type1AttackBlankOptions);
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 10, 125, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Animation"));
}
if (isActive)
{
CurrentAttackDistance = element.FindPropertyRelative("AttackDistance").floatValue;
CurrentTooCloseDistance = element.FindPropertyRelative("TooCloseDistance").floatValue;
DrawDistanceActive = true;
}
EditorGUI.ObjectField(new Rect(rect.x, rect.y + 35, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("AbilityObject"), new GUIContent("Ability Object", "The Ability Object that will be used for this attack."));
CustomEditorProperties.CustomListFloatSlider(new Rect(rect.x, rect.y + 60, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Distance", "Controls the distance that this attack can happen within"), element.FindPropertyRelative("AttackDistance"), 0.5f, 75f);
CustomEditorProperties.CustomListFloatSlider(new Rect(rect.x, rect.y + 85, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Too Close Distance", "Controls the distance for when an AI will backup. This is useful for AI keeping their distance from attackers."), element.FindPropertyRelative("TooCloseDistance"), 0f, 35f);
EditorGUI.BeginDisabledGroup(self.Type2Attacks.AttackPickType != AttackPickTypes.Odds);
CustomEditorProperties.CustomListIntSlider(new Rect(rect.x, rect.y + 110, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Attack Odds", "The odds that this attack will be used (when using the Odds Pick type)."), element.FindPropertyRelative("AttackOdds"), 1, 100);
EditorGUI.EndDisabledGroup();
};
Type2Attacks.drawHeaderCallback = rect =>
{
EditorGUI.LabelField(rect, "Type 2 Attacks", EditorStyles.boldLabel);
};
//Type 1 Action List
Type1ActionsList = new ReorderableList(serializedObject, serializedObject.FindProperty("Type1CombatActions"), true, true, true, true);
Type1ActionsList.drawElementCallback =
(Rect rect, int index, bool isActive, bool isFocused) =>
{
var element = Type1ActionsList.serializedProperty.GetArrayElementAtIndex(index);
Type1ActionsList.elementHeight = EditorGUIUtility.singleLineHeight * 1.35f;
EditorGUI.PropertyField(new Rect(rect.x + 5f, rect.y + 2, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("Enabled"), GUIContent.none); //Toggle
EditorGUI.LabelField(new Rect(rect.x + 25f, rect.y + 2, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Enabled", "Controls whether or not this action is enabled. If disabled, it will be ignored.")); //Toggle
EditorGUI.BeginDisabledGroup(!element.FindPropertyRelative("Enabled").boolValue);
EditorGUI.PropertyField(new Rect(rect.x + 100, rect.y + 4, rect.width - 100, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("emeraldAction"), GUIContent.none); //Action Object
EditorGUI.EndDisabledGroup();
};
Type1ActionsList.drawHeaderCallback = rect =>
{
EditorGUI.LabelField(rect, "Type 1 Combat Actions List", EditorStyles.boldLabel);
};
//Type 2 Action List
Type2ActionsList = new ReorderableList(serializedObject, serializedObject.FindProperty("Type2CombatActions"), true, true, true, true);
Type2ActionsList.drawElementCallback =
(Rect rect, int index, bool isActive, bool isFocused) =>
{
var element = Type2ActionsList.serializedProperty.GetArrayElementAtIndex(index);
Type2ActionsList.elementHeight = EditorGUIUtility.singleLineHeight * 1.35f;
EditorGUI.PropertyField(new Rect(rect.x + 5f, rect.y + 2, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("Enabled"), GUIContent.none); //Toggle
EditorGUI.LabelField(new Rect(rect.x + 25f, rect.y + 2, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Enabled", "Controls whether or not this action is enabled. If disabled, it will be ignored.")); //Toggle
EditorGUI.BeginDisabledGroup(!element.FindPropertyRelative("Enabled").boolValue);
EditorGUI.PropertyField(new Rect(rect.x + 100, rect.y + 4, rect.width - 100, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("emeraldAction"), GUIContent.none); //Action Object
EditorGUI.EndDisabledGroup();
};
Type2ActionsList.drawHeaderCallback = rect =>
{
EditorGUI.LabelField(rect, "Type 2 Combat Actions List", EditorStyles.boldLabel);
};
}
public override void OnInspectorGUI()
{
FoldoutStyle = CustomEditorProperties.UpdateEditorStyles();
EmeraldCombat self = (EmeraldCombat)target;
serializedObject.Update();
CustomEditorProperties.BeginScriptHeaderNew("Combat", CombatEditorIcon, new GUIContent(), HideSettingsFoldout);
if (EmeraldBehaviors.CurrentBehaviorType != EmeraldBehaviors.BehaviorTypes.Aggressive)
{
CustomEditorProperties.DisplayImportantHeaderMessage("Only AI with an Aggressive behavior type use the Combat Component.");
}
EditorGUI.BeginDisabledGroup(EmeraldBehaviors.CurrentBehaviorType != EmeraldBehaviors.BehaviorTypes.Aggressive);
DisplayWarningMessage(self);
if (!HideSettingsFoldout.boolValue)
{
EditorGUILayout.Space();
DamageSettings(self);
EditorGUILayout.Space();
CombatActionSettings(self);
EditorGUILayout.Space();
if (self.WeaponTypeAmount == EmeraldCombat.WeaponTypeAmounts.Two)
{
SwitchWeaponSettings(self);
EditorGUILayout.Space();
}
WeaponType1Settings(self);
EditorGUILayout.Space();
if (self.WeaponTypeAmount == EmeraldCombat.WeaponTypeAmounts.Two)
{
WeaponType2Settings(self);
EditorGUILayout.Space();
}
}
EditorGUI.EndDisabledGroup();
CustomEditorProperties.EndScriptHeader();
serializedObject.ApplyModifiedProperties();
}
void DamageSettings(EmeraldCombat self)
{
DamageSettingsFoldout.boolValue = EditorGUILayout.Foldout(DamageSettingsFoldout.boolValue, "Combat Settings", true, FoldoutStyle);
if (DamageSettingsFoldout.boolValue)
{
CustomEditorProperties.BeginFoldoutWindowBox();
CustomEditorProperties.TextTitleWithDescription("Combat Settings", "Controls various combat related settings.", true);
self.WeaponTypeAmount = (EmeraldCombat.WeaponTypeAmounts)EditorGUILayout.EnumPopup("Weapon Type Amount", self.WeaponTypeAmount);
CustomEditorProperties.CustomHelpLabelField("Controls whether this AI uses 1 or 2 Weapon Types. This allows an AI to use different animations and abilities for its different weapon types, " +
"such as sword attacks for type 1 and shooting spells for type 2. By default, this is set to 1. Combat Actions are used for both weapon types, if desired.", true);
EditorGUILayout.Space();
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), MinResumeWanderProp, "Min Resume Wandering", 0, 6);
CustomEditorProperties.CustomHelpLabelField("Controls the minimum amount of time an AI will wait before they resume wandering (according to its Wandering Type) after being " +
"in combat. This amount will be randomized with the Maximum Resume Wandering Delay.", true);
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), MaxResumeWanderProp, "Max Resume Wandering", 0, 6);
CustomEditorProperties.CustomHelpLabelField("Controls the maximum amount of time an AI will wait before they resume wandering (according to its Wandering Type) after being " +
"in combat. This amount will be randomized with the Minimum Resume Wandering Delay.", true);
CustomEditorProperties.EndFoldoutWindowBox();
}
}
void DisplayWarningMessage (EmeraldCombat self)
{
if (EmeraldBehaviors.CurrentBehaviorType != EmeraldBehaviors.BehaviorTypes.Aggressive) return;
if (self.WeaponTypeAmount == EmeraldCombat.WeaponTypeAmounts.Two && self.Type2Attacks.AttackDataList.Count == 0)
{
CustomEditorProperties.DisplaySetupWarning("There currently aren't any Type 2 attacks applied to this AI. Please ensure there is at least 1 attack applied to the Type 2 Attacks List. This " +
"can be found within the Weapon Type 2 Settings foldout");
}
else if (self.Type1Attacks.AttackDataList.Count == 0)
{
CustomEditorProperties.DisplaySetupWarning("There currently aren't any Type 1 attacks applied to this AI. Please ensure there is at least 1 attack applied to the Type 1 Attacks List. This " +
"can be found within the Weapon Type 1 Settings foldout.");
}
}
void CombatActionSettings(EmeraldCombat self)
{
CombatActionSettingsFoldout.boolValue = EditorGUILayout.Foldout(CombatActionSettingsFoldout.boolValue, "Combat Actions Settings", true, FoldoutStyle);
if (CombatActionSettingsFoldout.boolValue)
{
CustomEditorProperties.BeginFoldoutWindowBox();
CustomEditorProperties.TextTitleWithDescription("Combat Actions Settings", "Controls the combat actions an AI will use while in combat. Combat Actions that are disabled will be ignored. Only Aggressive AI can use Combat Actions.", true);
EditorGUILayout.Space();
CustomEditorProperties.CustomHelpLabelField("A list of mudular combat actions an AI can use while actively fighting in combat using its Type 1 Weapon Type.", false);
GUILayout.Box(new GUIContent("What's This?", "A list of mudular combat actions an AI can use while actively fighting in combat using its Type 1 Weapon Type."), EditorStyles.toolbarButton, GUILayout.ExpandWidth(false));
Type1ActionsList.DoLayoutList();
EditorGUILayout.Space();
EditorGUILayout.Space();
if (self.WeaponTypeAmount == EmeraldCombat.WeaponTypeAmounts.Two)
{
EditorGUILayout.Space();
CustomEditorProperties.CustomHelpLabelField("A list of mudular combat actions an AI can use while actively fighting in combat using its Type 2 Weapon Type.", false);
GUILayout.Box(new GUIContent("What's This?", "A list of mudular combat actions an AI can use while actively fighting in combat using its Type 2 Weapon Type."), EditorStyles.toolbarButton, GUILayout.ExpandWidth(false));
Type2ActionsList.DoLayoutList();
EditorGUILayout.Space();
EditorGUILayout.Space();
}
CustomEditorProperties.EndFoldoutWindowBox();
}
}
void SwitchWeaponSettings (EmeraldCombat self)
{
if (self.WeaponTypeAmount == EmeraldCombat.WeaponTypeAmounts.Two)
{
SwitchWeaponSettingsFoldout.boolValue = EditorGUILayout.Foldout(SwitchWeaponSettingsFoldout.boolValue, "Switch Weapon Settings", true, FoldoutStyle);
if (SwitchWeaponSettingsFoldout.boolValue)
{
CustomEditorProperties.BeginFoldoutWindowBox();
CustomEditorProperties.TextTitleWithDescription("Switch Weapon Settings", "Switch Weapon Settings.", true);
EditorGUILayout.PropertyField(StartingWeaponTypeProp, new GUIContent("Starting Weapon Type"));
CustomEditorProperties.CustomHelpLabelField("Controls which weapon type the AI will start with, or transition to first, upon entering comabt.", true);
if (self.SwitchWeaponType == EmeraldCombat.SwitchWeaponTypes.Distance)
{
CustomEditorProperties.DisplayImportantMessage("Important - When using the Distance Switch Type, your Starting Weapon Type needs to be the Weapon Type used for ranged combat. This setting is intended to be used with an AI that has close-range and range weapons.");
GUILayout.Space(10);
}
EditorGUILayout.PropertyField(SwitchWeaponTypeProp, new GUIContent("Switch Type"));
CustomEditorProperties.CustomHelpLabelField("Controls how the AI will switch its weapon type between Type 2 and Type 1. If none is used, the AI will always stay on the Starting Weapon Type.", true);
if (self.SwitchWeaponType == EmeraldCombat.SwitchWeaponTypes.Timed)
{
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), SwitchWeaponTimeMinProp, "Switch Time Min", 5, 45);
CustomEditorProperties.CustomHelpLabelField("Controls the minimum amount of time it takes for this AI to switch its weapon.", false);
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), SwitchWeaponTimeMaxProp, "Switch Time Min", 10, 90);
CustomEditorProperties.CustomHelpLabelField("Controls the minimum amount of time it takes for this AI to switch its weapon.", true);
}
else if (self.SwitchWeaponType == EmeraldCombat.SwitchWeaponTypes.Distance)
{
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), SwitchWeaponTypesDistanceProp, "Switch Weapon Type Distance", 2, 15);
CustomEditorProperties.CustomHelpLabelField("Controls the distance in which the AI will switch to between close-ranged and ranged damage. Any distance at or below this amount will be close-ranged" +
" and any value greater will be ranged.", true);
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), SwitchWeaponTypesCooldownProp, "Switch Weapon Type Cooldown", 1, 60);
CustomEditorProperties.CustomHelpLabelField("Controls the cooldown in which an AI will switch between ranged and close-ranged combat, if the Switch Weapon Type Distance has been met. This" +
" is to stop a weapon switch from happening too often.", true);
}
CustomEditorProperties.EndFoldoutWindowBox();
}
}
}
void WeaponType1Settings(EmeraldCombat self)
{
if (self.WeaponTypeAmount == EmeraldCombat.WeaponTypeAmounts.One || self.WeaponTypeAmount == EmeraldCombat.WeaponTypeAmounts.Two)
{
WeaponType1SettingsFoldout.boolValue = EditorGUILayout.Foldout(WeaponType1SettingsFoldout.boolValue, "Weapon Type 1 Settings", true, FoldoutStyle);
if (WeaponType1SettingsFoldout.boolValue)
{
CustomEditorProperties.BeginFoldoutWindowBox();
CustomEditorProperties.TextTitleWithDescription("Weapon Type 1 Settings", "Weapon Type 1 Settings.", true);
PickTargetTypeSetting(Type1PickTargetTypeProp);
CustomEditorProperties.CustomFloatSliderPropertyField(Type1AttackCooldownProp, "Type 1 Attack Cooldown", "Controls the cooldown needed to trigger an attack. " +
"Note: An attack can take longer than the specified cooldown if an AI is busy with another action.", 0.35f, 5, false);
GUILayout.Space(12);
GUILayout.Box(new GUIContent("What's This?", AttackTransformTooltip), EditorStyles.toolbarButton, GUILayout.ExpandWidth(false));
WeaponType1AttackTransforms.DoLayoutList();
GUILayout.Space(12);
CustomEditorProperties.CustomHelpLabelField("Controls how Type 1's Attacks are picked.", false);
CustomEditorProperties.CustomPropertyField(Type1AttackPickTypeProp, "Attack Pick Type", "", false);
if (self.Type1Attacks.AttackPickType == AttackPickTypes.Odds)
{
CustomEditorProperties.CustomHelpLabelField("Odds - Type 1 Attacks are picked based off of each of the Type 1 Attack's odds.", true);
}
else if (self.Type1Attacks.AttackPickType == AttackPickTypes.Order)
{
CustomEditorProperties.CustomHelpLabelField("Order - Type 1 Attacks are picked based on the order of the AI's Type 1 Attacks list.", true);
}
else if (self.Type1Attacks.AttackPickType == AttackPickTypes.Random)
{
CustomEditorProperties.CustomHelpLabelField("Random - Type 1 Attacks are picked randomly from the AI's Type 1 Attacks list.", true);
}
GUILayout.Space(10);
if (EmeraldAnimation.m_AnimationProfile != null && EmeraldAnimation.m_AnimationProfile.Type1Animations.AttackList.Count == 0 || EmeraldAnimation.Type1AttackEnumAnimations == null)
{
CustomEditorProperties.BeginIndent(12);
CustomEditorProperties.DisplaySetupWarning("Please add at least one Type 1 Attack animation to the Type 1 Attack Animation list (located within this AI's Animation Profile) to " +
"choose the type of animations these attacks will use.");
CustomEditorProperties.EndIndent();
}
GUILayout.Box(new GUIContent("What's This?", "A list of an AI's attacks. You can hover the mouse over each setting to view its tooltip. " +
"You can select each attack within the attack list (making it active) to see the attack distance drawn around the AI."), EditorStyles.toolbarButton, GUILayout.ExpandWidth(false));
Type1Attacks.DoLayoutList();
EditorGUILayout.Space();
EditorGUILayout.Space();
CustomEditorProperties.EndFoldoutWindowBox();
}
}
}
void WeaponType2Settings (EmeraldCombat self)
{
if (self.WeaponTypeAmount == EmeraldCombat.WeaponTypeAmounts.Two)
{
WeaponType2SettingsFoldout.boolValue = EditorGUILayout.Foldout(WeaponType2SettingsFoldout.boolValue, "Weapon Type 2 Settings", true, FoldoutStyle);
if (WeaponType2SettingsFoldout.boolValue)
{
EditorGUILayout.BeginVertical("Box");
CustomEditorProperties.TextTitleWithDescription("Weapon Type 2 Settings", "Weapon Type 2 Settings.", true);
PickTargetTypeSetting(Type2PickTargetTypeProp);
CustomEditorProperties.CustomFloatSliderPropertyField(Type2AttackCooldownProp, "Type 2 Attack Cooldown", "Controls the cooldown needed to trigger an attack. " +
"Note: An attack can take longer than the specified cooldown if an AI is busy with another action.", 0.35f, 5, false);
GUILayout.Space(12);
GUILayout.Box(new GUIContent("What's This?", AttackTransformTooltip), EditorStyles.toolbarButton, GUILayout.ExpandWidth(false));
WeaponType2AttackTransforms.DoLayoutList();
GUILayout.Space(12);
CustomEditorProperties.CustomHelpLabelField("Controls how Type 2's Attacks are picked.", false);
CustomEditorProperties.CustomPropertyField(Type2AttackPickTypeProp, "Attack Pick Type", "", false);
if (self.Type2Attacks.AttackPickType == AttackPickTypes.Odds)
{
CustomEditorProperties.CustomHelpLabelField("Odds - Type 2 Attacks are picked based off of each of the Type 2 Attack's odds.", true);
}
else if (self.Type2Attacks.AttackPickType == AttackPickTypes.Order)
{
CustomEditorProperties.CustomHelpLabelField("Order - Type 2 Attacks are picked based on the order of the AI's Type 2 Attacks list.", true);
}
else if (self.Type2Attacks.AttackPickType == AttackPickTypes.Random)
{
CustomEditorProperties.CustomHelpLabelField("Random - Type 2 Attacks are picked randomly from the AI's Type 2 Attacks list.", true);
}
GUILayout.Space(10);
if (EmeraldAnimation.m_AnimationProfile != null && EmeraldAnimation.m_AnimationProfile.Type2Animations.AttackList.Count == 0 || EmeraldAnimation.Type2AttackEnumAnimations == null)
{
CustomEditorProperties.BeginIndent(12);
CustomEditorProperties.DisplaySetupWarning("Please add at least one Type 2 Attack animation to the Type 2 Attack Animation list (located within this AI's Animation Profile) to " +
"choose the type of animations these attacks will use.");
CustomEditorProperties.EndIndent();
}
GUILayout.Box(new GUIContent("What's This?", "A list of an AI's attacks. You can hover the mouse over each setting to view its tooltip. " +
"You can select each attack within the attack list (making it active) to see the attack distance drawn around the AI."), EditorStyles.toolbarButton, GUILayout.ExpandWidth(false));
Type2Attacks.DoLayoutList();
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.EndVertical();
}
}
}
void PickTargetTypeSetting (SerializedProperty PickTargetTypeProp)
{
EditorGUILayout.PropertyField(PickTargetTypeProp);
CustomEditorProperties.CustomHelpLabelField("Controls the method an AI uses to pick a target.", false);
if ((PickTargetTypes)PickTargetTypeProp.enumValueIndex == PickTargetTypes.Closest)
{
CustomEditorProperties.CustomHelpLabelField("Closest - Picks the tagret that is closest and currently visible to the AI.", true);
}
else if ((PickTargetTypes)PickTargetTypeProp.enumValueIndex == PickTargetTypes.FirstDetected)
{
CustomEditorProperties.CustomHelpLabelField("First Detected - Picks the tagret that was first detected and currently visible to the AI.", true);
}
else if ((PickTargetTypes)PickTargetTypeProp.enumValueIndex == PickTargetTypes.Random)
{
CustomEditorProperties.CustomHelpLabelField("Random - Picks a random target from all currently visible targets within an AI's detection radius.", true);
}
}
private void OnSceneGUI()
{
EmeraldCombat self = (EmeraldCombat)target;
DrawCombatRadii(self);
}
void DrawCombatRadii (EmeraldCombat self)
{
if (DrawDistanceActive)
{
Handles.color = new Color(255, 0, 0, 1.0f);
Handles.DrawWireDisc(self.transform.position, Vector3.up, CurrentAttackDistance);
Handles.color = new Color(1, 0.9f, 0, 1.0f);
Handles.DrawWireDisc(self.transform.position, Vector3.up, CurrentTooCloseDistance);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cd27278d10fb3f649a5b452943f2747e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,367 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditorInternal;
namespace EmeraldAI.Utility
{
[CustomEditor(typeof(EmeraldDetection))]
[CanEditMultipleObjects]
public class EmeraldDetectionEditor : Editor
{
#region Variables
GUIStyle FoldoutStyle;
EmeraldBehaviors BehaviorsComponent;
Texture DetectionEditorIcon;
//Ints
SerializedProperty FieldOfViewAngleProp, DetectionRadiusProp, CurrentFactionProp;
//Floats
SerializedProperty ObstructionDetectionFrequencyProp;
//Reorderable List
ReorderableList FactionsList;
//String
SerializedProperty PlayerTagProp, RagdollTagProp;
//Bool
SerializedProperty HideSettingsFoldout, DetectionFoldout, TagFoldout, FactionFoldout;
//Float
SerializedProperty DetectionFrequencyProp, ObstructionSecondsProp;
//Object
SerializedProperty HeadTransformProp;
//LayerMasks
SerializedProperty DetectionLayerMaskProp, ObstructionDetectionLayerMaskProp;
#endregion
void OnEnable()
{
EmeraldDetection self = (EmeraldDetection)target;
BehaviorsComponent = self.GetComponent<EmeraldBehaviors>();
if (DetectionEditorIcon == null) DetectionEditorIcon = Resources.Load("Editor Icons/EmeraldDetection") as Texture;
RefreshFactionData();
}
void RefreshFactionData ()
{
LoadFactionData();
InitializeProperties();
InitializeFactionList();
}
void MissingComponentsMessage (EmeraldDetection self)
{
if (!self.HeadTransform)
{
CustomEditorProperties.DisplaySetupWarning("The AI's Head Transform has not been applied and is needed for accurate raycast calculations, please apply it. This is located within the Detection Settings foldout.");
}
else if (self.FactionRelationsList.Count == 0 && BehaviorsComponent.CurrentBehaviorType != EmeraldBehaviors.BehaviorTypes.Passive)
{
CustomEditorProperties.DisplaySetupWarning("This AI needs at least 1 Faction Relation to function properly. Please apply one through the Faction Settings foldout below.");
}
}
void InitializeProperties()
{
//Ints
FieldOfViewAngleProp = serializedObject.FindProperty("FieldOfViewAngle");
DetectionRadiusProp = serializedObject.FindProperty("DetectionRadius");
CurrentFactionProp = serializedObject.FindProperty("CurrentFaction");
//Bool
HideSettingsFoldout = serializedObject.FindProperty("HideSettingsFoldout");
DetectionFoldout = serializedObject.FindProperty("DetectionFoldout");
TagFoldout = serializedObject.FindProperty("TagFoldout");
FactionFoldout = serializedObject.FindProperty("FactionFoldout");
//String
PlayerTagProp = serializedObject.FindProperty("PlayerTag");
RagdollTagProp = serializedObject.FindProperty("RagdollTag");
//Float
DetectionFrequencyProp = serializedObject.FindProperty("DetectionFrequency");
ObstructionDetectionFrequencyProp = serializedObject.FindProperty("ObstructionDetectionFrequency");
ObstructionSecondsProp = serializedObject.FindProperty("ObstructionSeconds");
//Object
HeadTransformProp = serializedObject.FindProperty("HeadTransform");
//LayerMasks
DetectionLayerMaskProp = serializedObject.FindProperty("DetectionLayerMask");
ObstructionDetectionLayerMaskProp = serializedObject.FindProperty("ObstructionDetectionLayerMask");
}
void InitializeFactionList()
{
//Factions List
FactionsList = new ReorderableList(serializedObject, serializedObject.FindProperty("FactionRelationsList"), true, true, true, true);
FactionsList.drawElementCallback =
(Rect rect, int index, bool isActive, bool isFocused) =>
{
var element = FactionsList.serializedProperty.GetArrayElementAtIndex(index);
FactionsList.elementHeight = EditorGUIUtility.singleLineHeight * 3.75f;
if (element.FindPropertyRelative("RelationType").intValue == 0)
{
EditorGUI.DrawRect(new Rect(rect.x - 16, rect.y + 2f, rect.width + 17, EditorGUIUtility.singleLineHeight * 3.5f), new Color(1.0f, 0.0f, 0.0f, 0.15f));
}
else if (element.FindPropertyRelative("RelationType").intValue == 1)
{
EditorGUI.DrawRect(new Rect(rect.x - 16, rect.y + 2f, rect.width + 17, EditorGUIUtility.singleLineHeight * 3.5f), new Color(0.1f, 0.1f, 0.1f, 0.1f));
}
else if (element.FindPropertyRelative("RelationType").intValue == 2)
{
EditorGUI.DrawRect(new Rect(rect.x - 16, rect.y + 2f, rect.width + 17, EditorGUIUtility.singleLineHeight * 3.5f), new Color(0.0f, 1.0f, 0.0f, 0.15f));
}
EditorGUI.PropertyField(new Rect(rect.x, rect.y + 35, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("RelationType"), new GUIContent("Relation Type", "The type of relation this AI has with this faction."));
CustomEditorProperties.CustomListPopup(new Rect(rect.x + 125, rect.y + 10, rect.width - 125, EditorGUIUtility.singleLineHeight), new GUIContent(), element.FindPropertyRelative("FactionIndex"), "Faction", EmeraldDetection.StringFactionList.ToArray());
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 10, 125, EditorGUIUtility.singleLineHeight),
new GUIContent("Faction", "Factions are based on all factions within the Faction Manager. An AI can have as many faction relations as needed."));
};
FactionsList.drawHeaderCallback = rect =>
{
EditorGUI.LabelField(rect, "AI Faction Relations", EditorStyles.boldLabel);
};
}
public override void OnInspectorGUI()
{
FoldoutStyle = CustomEditorProperties.UpdateEditorStyles();
EmeraldDetection self = (EmeraldDetection)target;
serializedObject.Update();
CustomEditorProperties.BeginScriptHeaderNew("Detection", DetectionEditorIcon, new GUIContent(), HideSettingsFoldout);
MissingComponentsMessage(self);
if (!HideSettingsFoldout.boolValue)
{
EditorGUILayout.Space();
DetectionSettings(self);
EditorGUILayout.Space();
TagSettings(self);
EditorGUILayout.Space();
FactionSettings(self);
EditorGUILayout.Space();
}
CustomEditorProperties.EndScriptHeader();
serializedObject.ApplyModifiedProperties();
}
void DetectionSettings(EmeraldDetection self)
{
DetectionFoldout.boolValue = EditorGUILayout.Foldout(DetectionFoldout.boolValue, "Detection Settings", true, FoldoutStyle);
if (DetectionFoldout.boolValue)
{
CustomEditorProperties.BeginFoldoutWindowBox();
CustomEditorProperties.TextTitleWithDescription("Detection Settings", "Controls various detection settings such as radius distances, target detection, and field of view.", true);
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), FieldOfViewAngleProp, "Field of View", 1, 360);
CustomEditorProperties.CustomHelpLabelField("Controls the field of view an AI uses to detect targets.", true);
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), DetectionRadiusProp, "Detection Distance", 1, 100);
CustomEditorProperties.CustomHelpLabelField("Controls the distance of the field of view as well as the AI's detection radius.", true);
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), DetectionFrequencyProp, "Detection Frequency", 0.1f, 2f);
CustomEditorProperties.CustomHelpLabelField("Controls how often the AI's detection calculations update.", true);
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), ObstructionSecondsProp, "Obstruction Seconds", 0.5f, 5f);
CustomEditorProperties.CustomHelpLabelField("Controls how many seconds must pass, while obstructed, before an AI will switch to a new target.", true);
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), ObstructionDetectionFrequencyProp, "Obstruction Detection Frequency", 0.05f, 1f);
CustomEditorProperties.CustomHelpLabelField("Controls how often the AI checks for obstructions between them and their current target.", false);
CustomEditorProperties.BeginIndent();
EditorGUILayout.PropertyField(ObstructionDetectionLayerMaskProp, new GUIContent("Obstruction Ignore Layers"));
CustomEditorProperties.CustomHelpLabelField("The layers that should be ignored when an AI is using its obstruction detection for attacking." +
"These are objects that may prevent an AI from seeing its target. If your target has nothing that will block the AI's sight, you can " +
"set the layermask to Nothing.", true);
CustomEditorProperties.EndIndent();
EditorGUILayout.Space();
EditorGUILayout.PropertyField(HeadTransformProp, new GUIContent("Head Transform"));
CustomEditorProperties.CustomHelpLabelField("The head transform of your AI. This is used for accurate head looking and raycast calculations related to sight and obstruction detection. " +
"This should be your AI's head object within its bone objects.", false);
CustomEditorProperties.AutoFindHeadTransform(new Rect(), new GUIContent(), HeadTransformProp, self.transform);
EditorGUILayout.Space();
EditorGUILayout.Space();
CustomEditorProperties.EndFoldoutWindowBox();
}
}
void TagSettings(EmeraldDetection self)
{
TagFoldout.boolValue = EditorGUILayout.Foldout(TagFoldout.boolValue, "Tag & Layer Settings", true, FoldoutStyle);
if (TagFoldout.boolValue)
{
CustomEditorProperties.BeginFoldoutWindowBox();
CustomEditorProperties.TextTitleWithDescription("Tag & Layer Settings", "Controls an AI's Detection Layers. These are used to allow the AI to know what Layers are detectable targets.", true);
CustomEditorProperties.TutorialButton("For a tutorial on setting up an AI's Detection Layers and Player Tag, please see the tutorial below.",
"https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-required/detection-component/setting-up-the-detection-layers-and-player-tag");
CustomEditorProperties.NoticeTextTitleWithDescription("Important", "The Player Relation is handled through the AI's Faction Relations List (within the Faction Settings foldout below). The Player Unity Tag is used to determine certain internal functionality.", false);
CustomEditorProperties.CustomTagField(new Rect(), new GUIContent(), PlayerTagProp, "Player Unity Tag");
CustomEditorProperties.CustomHelpLabelField("The Unity Tag used to define Player objects. This is the tag that was assigned using Unity's Tag pulldown at the top of " +
"the gameobject.", true);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(DetectionLayerMaskProp, new GUIContent("Detection Layers"));
CustomEditorProperties.CustomHelpLabelField("The Detection Layers controls what layers this AI can detect as possible targets.", false);
if (DetectionLayerMaskProp.intValue == 0 || DetectionLayerMaskProp.intValue == 1)
{
GUI.backgroundColor = new Color(10f, 0.0f, 0.0f, 0.25f);
EditorGUILayout.LabelField("The Detection Layers cannot contain Nothing, Default, or Everything.", EditorStyles.helpBox);
GUI.backgroundColor = Color.white;
}
EditorGUILayout.Space();
EditorGUILayout.Space();
CustomEditorProperties.EndFoldoutWindowBox();
}
}
void FactionSettings(EmeraldDetection self)
{
FactionFoldout.boolValue = EditorGUILayout.Foldout(FactionFoldout.boolValue, "Faction Settings", true, FoldoutStyle);
if (FactionFoldout.boolValue)
{
CustomEditorProperties.BeginFoldoutWindowBox();
CustomEditorProperties.TextTitleWithDescription("Faction Settings", "The Faction Settings allow you to control which Factions your AI " +
"sees as enemies or allies, including the relations with the AI and the player.", true);
CustomEditorProperties.TutorialButton("For a tutorial on setting up an AI's faction relations, please see the tutorial below.",
"https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-required/detection-component/faction-relations#setting-up-an-ais-faction-relations");
EditorGUILayout.Space();
CustomEditorProperties.CustomEnum(new Rect(), new GUIContent(), CurrentFactionProp, "Faction");
CustomEditorProperties.CustomHelpLabelField("An AI's Faction is the name used to control combat reaction with other AI. This is the name other AI will use when " +
"looking for opposing targets.", true);
CustomEditorProperties.CustomHelpLabelField("Factions can be created and removed using the Faction Manager. ", false);
if (GUILayout.Button("Open Faction Manager"))
{
EditorWindow APS = EditorWindow.GetWindow(typeof(EmeraldFactionManager));
APS.minSize = new Vector2(600f, 775f);
}
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.LabelField("AI Faction Relations", EditorStyles.boldLabel);
CustomEditorProperties.CustomHelpLabelField("Controls which factions this AI sees as enemies and allies. You can hover the mouse over each setting to view its tooltip.", false);
GUI.backgroundColor = new Color(1f, 1, 0.25f, 0.25f);
EditorGUILayout.LabelField("Note: The AI Faction Relations use an AI's Faction not Unity tags. You can add and remove factions through the Faction Manager. " +
"This can be opened by pressing the button below.", EditorStyles.helpBox);
GUI.backgroundColor = Color.white;
EditorGUILayout.Space();
if (GUILayout.Button("Open Faction Manager"))
{
EditorWindow APS = EditorWindow.GetWindow(typeof(EmeraldFactionManager));
APS.minSize = new Vector2(600f, 775f);
}
EditorGUILayout.Space();
EditorGUILayout.Space();
FactionsList.DoLayoutList();
EditorGUILayout.Space();
EditorGUILayout.Space();
CustomEditorProperties.EndFoldoutWindowBox();
}
}
private void OnSceneGUI()
{
EmeraldDetection self = (EmeraldDetection)target;
DrawDetectionSettings(self);
}
public Vector3 DirFromAngle(Transform transform, float angleInDegrees, bool angleIsGlobal, EmeraldDetection self)
{
if (!angleIsGlobal)
angleInDegrees += transform.eulerAngles.y;
return transform.rotation * Quaternion.Euler(new Vector3(0, -transform.eulerAngles.y, 0)) * new Vector3(Mathf.Sin(angleInDegrees * Mathf.Deg2Rad), 0, Mathf.Cos(angleInDegrees * Mathf.Deg2Rad));
}
void DrawDetectionSettings (EmeraldDetection self)
{
if (DetectionFoldout.boolValue && !HideSettingsFoldout.boolValue)
{
//Red areas not covered by the line of sight, but the areas in yellow are.
Handles.color = Color.red;
Handles.DrawWireArc(self.transform.position, self.transform.up, self.transform.forward, (float)self.FieldOfViewAngle / 2f, self.DetectionRadius, 3f);
Handles.DrawWireArc(self.transform.position, self.transform.up, self.transform.forward, -(float)self.FieldOfViewAngle / 2f, self.DetectionRadius, 3f);
Handles.color = Color.yellow;
Handles.DrawWireArc(self.transform.position, self.transform.up, -self.transform.forward, (360 - self.FieldOfViewAngle) / 2f, self.DetectionRadius, 3f);
Handles.DrawWireArc(self.transform.position, self.transform.up, -self.transform.forward, -(360 - self.FieldOfViewAngle) / 2f, self.DetectionRadius, 3f);
Vector3 viewAngleA = DirFromAngle(self.transform, -self.FieldOfViewAngle / 2f, false, self);
Vector3 viewAngleB = DirFromAngle(self.transform, self.FieldOfViewAngle / 2f, false, self);
Handles.color = Color.red;
if (self.FieldOfViewAngle < 360)
{
Handles.DrawLine(self.transform.position, self.transform.position + viewAngleA * self.DetectionRadius, 3f);
Handles.DrawLine(self.transform.position, self.transform.position + viewAngleB * self.DetectionRadius, 3f);
}
Handles.color = Color.white;
}
}
void LoadFactionData()
{
EmeraldDetection.StringFactionList.Clear();
string path = AssetDatabase.GetAssetPath(Resources.Load("Faction Data"));
EmeraldFactionData FactionData = (EmeraldFactionData)AssetDatabase.LoadAssetAtPath(path, typeof(EmeraldFactionData));
if (FactionData != null)
{
foreach (string s in FactionData.FactionNameList)
{
if (!EmeraldDetection.StringFactionList.Contains(s) && s != "")
{
EmeraldDetection.StringFactionList.Add(s);
}
}
}
}
void CustomTag(Rect position, GUIContent label, SerializedProperty property)
{
label = EditorGUI.BeginProperty(position, label, property);
EditorGUI.BeginChangeCheck();
var newValue = EditorGUI.TagField(position, property.stringValue);
if (EditorGUI.EndChangeCheck())
property.stringValue = newValue;
EditorGUI.EndProperty();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: afd0b139d27162d45ac1c5d7b806afd4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,194 @@
using UnityEngine;
using UnityEditor;
using UnityEditorInternal;
namespace EmeraldAI.Utility
{
[CustomEditor(typeof(EmeraldHealth))]
[CanEditMultipleObjects]
public class EmeraldHealthEditor : Editor
{
GUIStyle FoldoutStyle;
Texture HealthEditorIcon;
//Int
SerializedProperty StartingHealthProp, HealRateProp;
//Enum
SerializedProperty UseHitEffectProp;
//Bool
SerializedProperty HideSettingsFoldout, HealthFoldout, HitEffectFoldout, ImmortalProp;
//Float
SerializedProperty HitEffectTimeoutSecondsProp;
//Vector
SerializedProperty HitEffectPosOffsetProp;
ReorderableList HitEffectsList;
void OnEnable()
{
if (HealthEditorIcon == null) HealthEditorIcon = Resources.Load("Editor Icons/EmeraldHealth") as Texture;
InitializeProperties();
InitializeList();
}
void InitializeProperties()
{
//Ints
StartingHealthProp = serializedObject.FindProperty("StartingHealth");
HealRateProp = serializedObject.FindProperty("HealRate");
//Bool
HideSettingsFoldout = serializedObject.FindProperty("HideSettingsFoldout");
HealthFoldout = serializedObject.FindProperty("HealthFoldout");
HitEffectFoldout = serializedObject.FindProperty("HitEffectFoldout");
ImmortalProp = serializedObject.FindProperty("Immortal");
//Float
HitEffectTimeoutSecondsProp = serializedObject.FindProperty("HitEffectTimeoutSeconds");
//Vector
HitEffectPosOffsetProp = serializedObject.FindProperty("HitEffectPosOffset");
//Enum
UseHitEffectProp = serializedObject.FindProperty("UseHitEffect");
}
void InitializeList ()
{
//Hit Effects List
HitEffectsList = new ReorderableList(serializedObject, serializedObject.FindProperty("HitEffectsList"), true, true, true, true);
HitEffectsList.drawHeaderCallback = rect =>
{
EditorGUI.LabelField(rect, "Hit Effects List", EditorStyles.boldLabel);
};
HitEffectsList.drawElementCallback =
(Rect rect, int index, bool isActive, bool isFocused) =>
{
var element = HitEffectsList.serializedProperty.GetArrayElementAtIndex(index);
EditorGUI.ObjectField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), element, GUIContent.none);
};
}
public override void OnInspectorGUI()
{
FoldoutStyle = CustomEditorProperties.UpdateEditorStyles();
EmeraldHealth self = (EmeraldHealth)target;
serializedObject.Update();
CustomEditorProperties.BeginScriptHeaderNew("Health", HealthEditorIcon, new GUIContent(), HideSettingsFoldout);
if (!HideSettingsFoldout.boolValue)
{
EditorGUILayout.Space();
HealthSettings(self);
EditorGUILayout.Space();
HitEffectSettings(self);
EditorGUILayout.Space();
}
CustomEditorProperties.EndScriptHeader();
serializedObject.ApplyModifiedProperties();
}
void HealthSettings(EmeraldHealth self)
{
HealthFoldout.boolValue = EditorGUILayout.Foldout(HealthFoldout.boolValue, "Health Settings", true, FoldoutStyle);
if (HealthFoldout.boolValue)
{
CustomEditorProperties.BeginFoldoutWindowBox();
CustomEditorProperties.TextTitleWithDescription("Health Settings", "Controls various health related settings.", true);
EditorGUILayout.PropertyField(ImmortalProp, new GUIContent("Immortal"));
CustomEditorProperties.CustomHelpLabelField("Controls whether or not an AI is immune to damage and is unkillable. If this is enabled, it will disable other settings.", true);
EditorGUI.BeginDisabledGroup(self.Immortal);
CustomEditorProperties.CustomIntField(new Rect(), new GUIContent(), StartingHealthProp, "Starting Health");
CustomEditorProperties.CustomHelpLabelField("Controls how much starting health an AI will have.", true);
CustomEditorProperties.CustomPropertyField(HealRateProp, "Heal Rate", "Controls how much an AI will heal per second when not actively in combat, given their health is below its max.", true);
EditorGUI.EndDisabledGroup();
DrawHealthBar(self);
CustomEditorProperties.EndFoldoutWindowBox();
}
}
void DrawHealthBar (EmeraldHealth self)
{
GUILayout.Space(45);
GUIStyle LabelStyle = new GUIStyle();
LabelStyle.alignment = TextAnchor.MiddleCenter;
LabelStyle.padding.bottom = 4;
LabelStyle.fontStyle = FontStyle.Bold;
LabelStyle.normal.textColor = Color.white;
Rect r = EditorGUILayout.BeginVertical();
GUI.backgroundColor = Color.white;
float CurrentHealth = ((float)self.CurrentHealth / (float)self.StartingHealth);
EditorGUI.DrawRect(new Rect(r.x, r.position.y - 39f, ((r.width)), 32), new Color(0.05f, 0.05f, 0.05f, 0.5f)); //Health Bar BG Outline
EditorGUI.DrawRect(new Rect(r.x + 4, r.position.y - 35f, ((r.width - 8)), 24), new Color(0.16f, 0.16f, 0.16f, 1f)); //Health Bar BG
Color HealthBarColor = Color.Lerp(new Color(0.6f, 0.1f, 0.1f, 1f), new Color(0.15f, 0.42f, 0.15f, 1f), CurrentHealth);
EditorGUI.DrawRect(new Rect(r.x + 4, r.position.y - 35f, ((r.width - 8) * CurrentHealth), 24), HealthBarColor); //Health Bar Main
if (self.CurrentHealth > 0)
{
EditorGUI.LabelField(new Rect(r.x, r.position.y - 35f, (r.width), 26), "Current Health: " + self.CurrentHealth + "/" + self.StartingHealth, LabelStyle);
}
else
{
EditorGUI.LabelField(new Rect(r.x, r.position.y - 35f, (r.width), 26), "Current Health: " + self.CurrentHealth + "/" + self.StartingHealth + " (Dead)", LabelStyle);
}
EditorGUILayout.EndVertical();
if (!Application.isPlaying)
{
self.CurrentHealth = self.StartingHealth;
}
}
void HitEffectSettings(EmeraldHealth self)
{
HitEffectFoldout.boolValue = EditorGUILayout.Foldout(HitEffectFoldout.boolValue, "Hit Effect Settings", true, FoldoutStyle);
if (HitEffectFoldout.boolValue)
{
CustomEditorProperties.BeginFoldoutWindowBox();
CustomEditorProperties.TextTitleWithDescription("Hit Effect Settings", "Allows an AI to display a random hit affect after receiving damage.", true);
EditorGUILayout.PropertyField(UseHitEffectProp, new GUIContent("Use Hit Effect"));
CustomEditorProperties.CustomHelpLabelField("Controls whether or not this AI will use a hit effect when it receives melee damage.", true);
if (self.UseHitEffect == YesOrNo.Yes)
{
CustomEditorProperties.BeginIndent();
CustomEditorProperties.CustomHelpLabelField("The hit effect that will appear when this AI receives damage.", true);
HitEffectsList.DoLayoutList();
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.PropertyField(HitEffectTimeoutSecondsProp, new GUIContent("Hit Effect Timeout Seconds"));
CustomEditorProperties.CustomHelpLabelField("Controls how long the hit effect will be visible before being deactivated.", true);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(HitEffectPosOffsetProp, new GUIContent("Hit Effect Position Offset"));
CustomEditorProperties.CustomHelpLabelField("Controls the offset position of the hit effect using the AI's Hit Transform position.", true);
EditorGUILayout.Space();
CustomEditorProperties.EndIndent();
}
CustomEditorProperties.EndFoldoutWindowBox();
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3ed4641fe6c076e40af6304b8c677cf1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,743 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditorInternal;
namespace EmeraldAI.Utility
{
[CustomEditor(typeof(EmeraldMovement))]
[CanEditMultipleObjects]
public class EmeraldMovementEditor : Editor
{
GUIStyle FoldoutStyle;
EmeraldAnimation EmeraldAnimation;
Texture MovementEditorIcon;
int CurrentWaypointIndex = -1;
#region SerializedProperties
//Foldouts
SerializedProperty HideSettingsFoldout, WanderFoldout, WaypointsFoldout, WaypointsListFoldout, MovementFoldout, AlignmentFoldout, TurnFoldout;
//Int
SerializedProperty StationaryIdleSecondsMinProp, StationaryIdleSecondsMaxProp, WanderRadiusProp, MaxSlopeLimitProp, WalkSpeedProp, RunSpeedProp, MinimumWaitTimeProp, MaximumWaitTimeProp,
WalkBackwardsSpeedProp, StationaryTurningSpeedCombatProp, MovingTurningSpeedCombatProp, BackupTurningSpeedProp;
//Floats
SerializedProperty StoppingDistanceProp, NonCombatAngleToTurnProp, CombatAngleToTurnProp, StationaryTurningSpeedNonCombatProp, MovingTurnSpeedNonCombatProp, MovementTurningSensitivityProp, MaxNormalAngleProp, NonCombatAlignSpeedProp,
CombatAlignSpeedProp, ForceWalkDistanceProp, DecelerationDampTimeProp;
//Enums
SerializedProperty WanderTypeProp, WaypointTypeProp, AlignAIWithGroundProp, CurrentMovementStateProp, AnimatorTypeProp, AlignmentQualityProp, AlignAIOnStartProp;
//LayerMask
SerializedProperty DynamicWanderLayerMaskProp, BackupLayerMaskProp, AlignmentLayerMaskProp;
//Bool
SerializedProperty UseRandomRotationOnStartProp, AnimationsUpdatedProp;
//Objects
SerializedProperty WaypointObjectProp;
#endregion
void OnEnable()
{
EmeraldMovement self = (EmeraldMovement)target;
EmeraldAnimation = self.GetComponent<EmeraldAnimation>();
if (MovementEditorIcon == null) MovementEditorIcon = Resources.Load("Editor Icons/EmeraldMovement") as Texture;
InitializeProperties();
}
void InitializeProperties ()
{
//Enums
WanderTypeProp = serializedObject.FindProperty("WanderType");
WaypointTypeProp = serializedObject.FindProperty("WaypointType");
AlignAIWithGroundProp = serializedObject.FindProperty("AlignAIWithGround");
CurrentMovementStateProp = serializedObject.FindProperty("CurrentMovementState");
AnimatorTypeProp = serializedObject.FindProperty("MovementType");
AlignmentQualityProp = serializedObject.FindProperty("AlignmentQuality");
AlignAIOnStartProp = serializedObject.FindProperty("AlignAIOnStart");
//Ints
StationaryIdleSecondsMinProp = serializedObject.FindProperty("StationaryIdleSecondsMin");
StationaryIdleSecondsMaxProp = serializedObject.FindProperty("StationaryIdleSecondsMax");
WanderRadiusProp = serializedObject.FindProperty("WanderRadius");
MaxSlopeLimitProp = serializedObject.FindProperty("MaxSlopeLimit");
WanderRadiusProp = serializedObject.FindProperty("WanderRadius");
MinimumWaitTimeProp = serializedObject.FindProperty("MinimumWaitTime");
MaximumWaitTimeProp = serializedObject.FindProperty("MaximumWaitTime");
WalkSpeedProp = serializedObject.FindProperty("WalkSpeed");
WalkBackwardsSpeedProp = serializedObject.FindProperty("WalkBackwardsSpeed");
RunSpeedProp = serializedObject.FindProperty("RunSpeed");
BackupTurningSpeedProp = serializedObject.FindProperty("BackupTurningSpeed");
CombatAngleToTurnProp = serializedObject.FindProperty("CombatAngleToTurn");
NonCombatAngleToTurnProp = serializedObject.FindProperty("NonCombatAngleToTurn");
StationaryTurningSpeedNonCombatProp = serializedObject.FindProperty("StationaryTurningSpeedNonCombat");
StationaryTurningSpeedCombatProp = serializedObject.FindProperty("StationaryTurningSpeedCombat");
MovingTurnSpeedNonCombatProp = serializedObject.FindProperty("MovingTurnSpeedNonCombat");
MovingTurningSpeedCombatProp = serializedObject.FindProperty("MovingTurnSpeedCombat");
//Floats
StoppingDistanceProp = serializedObject.FindProperty("StoppingDistance");
MovementTurningSensitivityProp = serializedObject.FindProperty("MovementTurningSensitivity");
DecelerationDampTimeProp = serializedObject.FindProperty("DecelerationDampTime");
MaxNormalAngleProp = serializedObject.FindProperty("MaxNormalAngle");
NonCombatAlignSpeedProp = serializedObject.FindProperty("NonCombatAlignmentSpeed");
CombatAlignSpeedProp = serializedObject.FindProperty("CombatAlignmentSpeed");
ForceWalkDistanceProp = serializedObject.FindProperty("ForceWalkDistance");
//LayerMask
DynamicWanderLayerMaskProp = serializedObject.FindProperty("DynamicWanderLayerMask");
BackupLayerMaskProp = serializedObject.FindProperty("BackupLayerMask");
AlignmentLayerMaskProp = serializedObject.FindProperty("AlignmentLayerMask");
//Bool
HideSettingsFoldout = serializedObject.FindProperty("HideSettingsFoldout");
WanderFoldout = serializedObject.FindProperty("WanderFoldout");
WaypointsFoldout = serializedObject.FindProperty("WaypointsFoldout");
WaypointsListFoldout = serializedObject.FindProperty("WaypointsListFoldout");
MovementFoldout = serializedObject.FindProperty("MovementFoldout");
AlignmentFoldout = serializedObject.FindProperty("AlignmentFoldout");
TurnFoldout = serializedObject.FindProperty("TurnFoldout");
UseRandomRotationOnStartProp = serializedObject.FindProperty("UseRandomRotationOnStart");
AnimationsUpdatedProp = serializedObject.FindProperty("AnimationsUpdated"); //Note: Used by multiple scripts currently, ensure this doesn't cause issues.
//Objects
WaypointObjectProp = serializedObject.FindProperty("m_WaypointObject");
}
public override void OnInspectorGUI()
{
FoldoutStyle = CustomEditorProperties.UpdateEditorStyles();
EmeraldMovement self = (EmeraldMovement)target;
serializedObject.Update();
CustomEditorProperties.BeginScriptHeaderNew("Movement", MovementEditorIcon, new GUIContent(), HideSettingsFoldout);
if (!HideSettingsFoldout.boolValue)
{
EditorGUILayout.Space();
MovementSettings(self);
EditorGUILayout.Space();
TurnSettings(self);
EditorGUILayout.Space();
AlignmentSettings(self);
EditorGUILayout.Space();
WanderSettings(self);
EditorGUILayout.Space();
WaypointSettings(self);
if (self.WanderType == EmeraldMovement.WanderTypes.Waypoints) EditorGUILayout.Space();
}
CustomEditorProperties.EndScriptHeader();
serializedObject.ApplyModifiedProperties();
}
/// <summary>
/// Holds all waypoint related settings and displays them through the EmeraldAIMovementEditor.
/// </summary>
void WaypointSettings (EmeraldMovement self)
{
if (self.WanderType == EmeraldMovement.WanderTypes.Waypoints)
{
WaypointsFoldout.boolValue = EditorGUILayout.Foldout(WaypointsFoldout.boolValue, "Waypoint Settings", true, FoldoutStyle);
if (WaypointsFoldout.boolValue)
{
CustomEditorProperties.BeginFoldoutWindowBox();
CustomEditorProperties.TextTitleWithDescription("Waypoint Editor", "Below you can define waypoints for your AI to follow. Simply press the 'Add Waypoint' button to create a waypoint. The AI will follow each created waypoint in the order " +
"they are created. A line will be drawn to visually represent this.", true);
if (self.WaypointsList != null && Selection.objects.Length == 1)
{
EditorGUILayout.LabelField("Controls what an AI will do when it reaches its last waypoint.", EditorStyles.helpBox);
EditorGUILayout.PropertyField(WaypointTypeProp, new GUIContent("Waypoint Type"));
GUI.backgroundColor = new Color(0.1f, 0.1f, 0.1f, 0.19f);
GUI.backgroundColor = Color.white;
if (self.WaypointType == (EmeraldMovement.WaypointTypes.Loop))
{
CustomEditorProperties.CustomHelpLabelField("Loop - Allows an AI to continiously move to each waypoint, in order, without ever stopping. When an AI reaches its last waypoint, it will set the first waypoint as its next waypoint thus creating a loop.", false);
}
else if (self.WaypointType == (EmeraldMovement.WaypointTypes.Reverse))
{
CustomEditorProperties.CustomHelpLabelField("Reverse - Allows an AI to continiously move to each waypoint, in order, without stopping until it reaches its last waypoint. When this happens, it will idle " +
"for the length of its Wait Time seconds then reverse the AI's waypoints making the last waypoint its first and repeat this process.", false);
}
else if (self.WaypointType == (EmeraldMovement.WaypointTypes.Random))
{
CustomEditorProperties.CustomHelpLabelField("Random - Allows an AI to patrol randomly through all waypoints. An AI will stop and idle each time it reaches a waypoint for as long as its Wait Time seconds are set.", false);
}
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.Space();
CustomEditorProperties.CustomHelpLabelField("Imports all waypoints from the current Waypoint Object.", false);
EditorGUILayout.PropertyField(WaypointObjectProp);
if (GUILayout.Button("Import Waypoint Data") && EditorUtility.DisplayDialog("Import Waypoint Data?", "Are you sure you want to clear all of this AI's waypoints and import waypoints from the applied Waypoint Object? This process cannot be undone.", "Yes", "Cancel"))
{
if (self.m_WaypointObject == null)
{
Debug.LogError("There's no Waypoint Object applied. Please apply one to import waypoint data.");
return;
}
self.WaypointsList = new List<Vector3>(self.m_WaypointObject.Waypoints);
EditorUtility.SetDirty(self);
}
EditorGUILayout.Space();
CustomEditorProperties.CustomHelpLabelField("Exports all waypoints to a Waypoint Object to be imported and shared with other AI so waypoints don't have to be recreated manually.", false);
if (GUILayout.Button("Export Waypoint Data"))
{
//Export all of the AI's current waypoints to a Waypoint Object so it can be imported to other AI.
string SavePath = EditorUtility.SaveFilePanelInProject("Save Waypoint Data", "New Waypoint Object", "asset", "Please enter a file name to save the file to");
if (SavePath != string.Empty)
{
var m_WaypointObject = CreateInstance<EmeraldWaypointObject>();
m_WaypointObject.Waypoints = new List<Vector3>(self.WaypointsList);
AssetDatabase.CreateAsset(m_WaypointObject, SavePath);
}
//For some reason, EditorUtility.SaveFilePanelInProject throws an incorrect EditorGUILayout error when it's used with some custom properties. This fixes it...
CustomEditorProperties.BeginScriptHeader("", null);
CustomEditorProperties.BeginFoldoutWindowBox();
}
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.Space();
if (GUILayout.Button("Add Waypoint"))
{
Vector3 newPoint = new Vector3(0, 0, 0);
if (self.WaypointsList.Count == 0)
{
newPoint = self.transform.position + Vector3.forward * (self.StoppingDistance * 2);
}
else if (self.WaypointsList.Count > 0)
{
newPoint = self.WaypointsList[self.WaypointsList.Count - 1] + Vector3.forward * (self.StoppingDistance * 2);
}
Undo.RecordObject(self, "Add Waypoint");
self.WaypointsList.Add(newPoint);
EditorUtility.SetDirty(self);
}
var style = new GUIStyle(GUI.skin.button);
style.normal.textColor = Color.red;
if (GUILayout.Button("Clear All Waypoints", style) && EditorUtility.DisplayDialog("Clear Waypoints?", "Are you sure you want to clear all of this AI's waypoints? This process cannot be undone.", "Yes", "Cancel"))
{
self.WaypointsList.Clear();
EditorUtility.SetDirty(self);
}
GUI.contentColor = Color.white;
GUI.backgroundColor = Color.white;
EditorGUILayout.Space();
EditorGUILayout.Space();
CustomEditorProperties.BeginIndent();
WaypointsListFoldout.boolValue = CustomEditorProperties.Foldout(WaypointsListFoldout.boolValue, "Waypoints List", true, FoldoutStyle);
if (WaypointsListFoldout.boolValue)
{
CustomEditorProperties.BeginFoldoutWindowBox();
CustomEditorProperties.TextTitleWithDescription("Waypoints List", "All of this AI's current waypoints. Waypoints can be individually removed by pressing the ''Remove Point'' button.", true);
EditorGUILayout.Space();
if (self.WaypointsList.Count > 0)
{
for (int j = 0; j < self.WaypointsList.Count; ++j)
{
GUI.backgroundColor = new Color(0.1f, 0.1f, 0.1f, 0.19f);
EditorGUILayout.LabelField("Waypoint " + (j + 1), EditorStyles.toolbarButton);
GUI.backgroundColor = Color.white;
EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
if (j < self.WaypointsList.Count - 1)
{
if (GUILayout.Button(new GUIContent("Insert", "Inserts a point between this point and the next point."), EditorStyles.miniButton, GUILayout.Height(18)))
{
Undo.RecordObject(self, "Insert Waypoint Above this Point");
self.WaypointsList.Insert(j + 1, (self.WaypointsList[j] + self.WaypointsList[j + 1]) / 2f);
CurrentWaypointIndex = j + 1;
EditorUtility.SetDirty(self);
HandleUtility.Repaint();
}
}
if (GUILayout.Button(new GUIContent("Remove", "Remove this point from the waypoint list."), EditorStyles.miniButton, GUILayout.Height(18)))
{
Undo.RecordObject(self, "Remove Point");
self.WaypointsList.RemoveAt(j);
EditorUtility.SetDirty(self);
HandleUtility.Repaint();
}
EditorGUILayout.EndHorizontal();
GUILayout.Space(10);
}
}
CustomEditorProperties.EndFoldoutWindowBox();
}
CustomEditorProperties.EndIndent();
EditorGUILayout.Space();
EditorGUILayout.Space();
}
else if (self.WaypointsList != null && Selection.objects.Length > 1)
{
CustomEditorProperties.DisplayWarningMessage("Waypoints do not support multi-object editing. If you'd like to edit an AI's waypoints, please only have 1 AI selected at a time.");
}
CustomEditorProperties.EndFoldoutWindowBox();
}
}
}
/// <summary>
/// Holds all movement related settings and displays them through the EmeraldAIMovementEditor.
/// </summary>
void MovementSettings (EmeraldMovement self)
{
MovementFoldout.boolValue = EditorGUILayout.Foldout(MovementFoldout.boolValue, "Movement Settings", true, FoldoutStyle);
if (MovementFoldout.boolValue)
{
CustomEditorProperties.BeginFoldoutWindowBox();
CustomEditorProperties.TextTitleWithDescription("Movement Settings", "Controls all speed and distance related settings.", true);
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(AnimatorTypeProp, new GUIContent("Movement Type"));
CustomEditorProperties.CustomHelpLabelField("Controls how an AI is moved. This is either driven by the Root Motion animation or by the NavMesh component.", true);
if (EditorGUI.EndChangeCheck())
{
if (EmeraldAnimation.m_AnimationProfile.AnimatorControllerGenerated)
{
//Assign this directly as AnimatorTypeProp becomes desynced with self.AnimatorType when regenerating the Animator for this setting.
self.MovementType = (EmeraldMovement.MovementTypes)AnimatorTypeProp.intValue;
EmeraldAnimatorGenerator.GenerateAnimatorController(EmeraldAnimation.m_AnimationProfile);
}
}
//Movement Type
CustomEditorProperties.BeginIndent();
GUI.backgroundColor = new Color(5f, 0.5f, 0.5f, 1f);
EditorGUILayout.LabelField("When using the Root Motion Movement Type, an AI's Movement Speed is controlled by its animation speed. You can adjust this through an AI's Animation Profile.", EditorStyles.helpBox);
GUI.backgroundColor = Color.white;
EditorGUI.BeginDisabledGroup(self.MovementType == EmeraldMovement.MovementTypes.RootMotion);
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), WalkSpeedProp, "Walk Speed", 0.5f, 5);
CustomEditorProperties.CustomHelpLabelField("Controls how fast your AI walks.", true);
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), RunSpeedProp, "Run Speed", 0.5f, 10);
CustomEditorProperties.CustomHelpLabelField("Controls how fast your AI runs.", true);
CustomFloatAnimationField(new Rect(), new GUIContent(), WalkBackwardsSpeedProp, "Walk Backwards Speed", 0.5f, 3f);
CustomEditorProperties.CustomHelpLabelField("Controls how fast your AI walks backwards.", true);
//Update the Animator as this is required when updating NavMesh speed settings.
if (EmeraldAnimation.m_AnimationProfile != null && EmeraldAnimation.m_AnimationProfile.AnimatorControllerGenerated && self.MovementType == EmeraldMovement.MovementTypes.NavMeshDriven)
{
if (EmeraldAnimation.m_AnimationProfile.AnimationsUpdated || EmeraldAnimation.m_AnimationProfile.AnimationListsChanged)
{
EmeraldAnimatorGenerator.GenerateAnimatorController(EmeraldAnimation.m_AnimationProfile);
}
}
EditorGUI.EndDisabledGroup();
CustomEditorProperties.EndIndent();
//Movement Type
EditorGUILayout.PropertyField(CurrentMovementStateProp, new GUIContent("Movement Animation"));
CustomEditorProperties.CustomHelpLabelField("Controls the type of animation your AI will use when using waypoints, moving to its destination, or wandering. " +
"Note: If needed, this can be changed programmatically during runtime.", true);
EditorGUILayout.Space();
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), ForceWalkDistanceProp, "Force Walk Distance", 0.0f, 8.0f);
CustomEditorProperties.CustomHelpLabelField("Controls the distance in which an AI will start walking instead of running as it approaches its target or destination. This can be set to 0 if you would like this feature disabled.", true);
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), StoppingDistanceProp, "Stopping Distance", 0.25f, 40);
CustomEditorProperties.CustomHelpLabelField("Controls the distance in which an AI will stop before waypoints and non-combat related destinations.", true);
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), DecelerationDampTimeProp, "Deceleration Damp Time", 0.1f, 0.4f);
CustomEditorProperties.CustomHelpLabelField("Controls the damp time of an AI's animations when decelerating. Lower values mean faster blending of animations between movement and stopping.", true);
EditorGUILayout.PropertyField(BackupLayerMaskProp, new GUIContent("Backup Layers"));
CustomEditorProperties.CustomHelpLabelField("Controls which layers will affect the AI's backing up process. Colliders detected within a few units behind the AI will stop the backing up process.", true);
if (BackupLayerMaskProp.intValue == 0)
{
GUI.backgroundColor = new Color(10f, 0.0f, 0.0f, 0.25f);
EditorGUILayout.LabelField("The Backup LayerMask cannot contain Nothing.", EditorStyles.helpBox);
GUI.backgroundColor = Color.white;
}
CustomEditorProperties.EndFoldoutWindowBox();
}
}
void TurnSettings (EmeraldMovement self)
{
TurnFoldout.boolValue = EditorGUILayout.Foldout(TurnFoldout.boolValue, "Turn Settings", true, FoldoutStyle);
if (TurnFoldout.boolValue)
{
CustomEditorProperties.BeginFoldoutWindowBox();
CustomEditorProperties.TextTitleWithDescription("Turn Settings", "Controls all settings and speeds related to turning.", true);
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), NonCombatAngleToTurnProp, "Turning Angle", 15, 90);
CustomEditorProperties.CustomHelpLabelField("Controls the angle needed to play a turn animation while an AI is not in combat. Emerald can automatically detect whether an AI is " +
"turing left or right. Note: You can use a walking animation in place of a turning animation if your AI doesn't one.", true);
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), CombatAngleToTurnProp, "Combat Turning Angle", 20, 90);
CustomEditorProperties.CustomHelpLabelField("Controls the angle needed to play a turn animation while an AI is in combat. Emerald can automatically detect whether an AI is " +
"turing left or right. Note: You can use a walking animation in place of a turning animation if your AI doesn't one.", true);
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), StationaryTurningSpeedNonCombatProp, "Stationary Turn Speed", 1, 200);
CustomEditorProperties.CustomHelpLabelField("Controls how fast your AI turns while not in combat and is stationary. Note: Lower speeds are meant for the Root Motion setting" +
" where the turning animations help assist an AI's turning. If you find an AI not turning quick enough while wandering, even with Root Motion enabled, you will most likely need to increasing this setting.", true);
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), StationaryTurningSpeedCombatProp, "Stationary Combat Turn Speed", 1, 200);
CustomEditorProperties.CustomHelpLabelField("Controls how fast your AI turns while in combat and is stationary.", true);
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), MovingTurnSpeedNonCombatProp, "Moving Turn Speed", 50, 750);
CustomEditorProperties.CustomHelpLabelField("Controls how fast your AI turns while not in combat and is moving. Note: Lower speeds are meant for the Root Motion setting" +
" where the turning animations help assist an AI's turning. If you find an AI not turning quick enough while wandering, even with Root Motion enabled, you will most likely need to increasing this setting.", true);
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), MovingTurningSpeedCombatProp, "Moving Combat Turn Speed", 50, 750);
CustomEditorProperties.CustomHelpLabelField("Controls how fast your AI turns while in combat and is moving.", true);
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), BackupTurningSpeedProp, "Backup Turn Speed", 5, 750);
CustomEditorProperties.CustomHelpLabelField("Controls how fast your AI turns while backing up.", true);
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), MovementTurningSensitivityProp, "Movement Turning Sensitivity", 0.5f, 3f);
CustomEditorProperties.CustomHelpLabelField("Controls how sensitive the movement blend trees are when playing movement turning animations. This is especially noticeable for quadruped models with turning animations.", true);
EditorGUILayout.PropertyField(UseRandomRotationOnStartProp, new GUIContent("Random Roation on Start"));
CustomEditorProperties.CustomHelpLabelField("Controls whether or not AI will be randomly rotated on Start.", true);
CustomEditorProperties.EndFoldoutWindowBox();
}
}
void AlignmentSettings (EmeraldMovement self)
{
AlignmentFoldout.boolValue = EditorGUILayout.Foldout(AlignmentFoldout.boolValue, "Alignment Settings", true, FoldoutStyle);
if (AlignmentFoldout.boolValue)
{
CustomEditorProperties.BeginFoldoutWindowBox();
CustomEditorProperties.TextTitleWithDescription("Alignment Settings", "Allows AI to align themselves to slopes and surfaces (Disable if you are using a full body IK system like Final IK)", true);
EditorGUILayout.PropertyField(AlignAIWithGroundProp, new GUIContent("Align AI"));
CustomEditorProperties.CustomHelpLabelField("Aligns the AI to the angle of the terrain and other objects for added realism. Disable this feature for improved performance per AI.", true);
if (self.AlignAIWithGround == YesOrNo.Yes)
{
CustomEditorProperties.BeginIndent();
EditorGUILayout.PropertyField(AlignmentLayerMaskProp, new GUIContent("Alignment Layers"));
CustomEditorProperties.CustomHelpLabelField("The layers the AI will use for aligning itself with the angles of surfaces. Any layers not included above will be ignred.", true);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(AlignmentQualityProp, new GUIContent("Align Quality"));
CustomEditorProperties.CustomHelpLabelField("Controls the quality of the Align AI feature by controlling how often it's updated.", true);
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), NonCombatAlignSpeedProp, "Non-Combat Align Speed", 5, 200);
CustomEditorProperties.CustomHelpLabelField("Controls the speed in which the AI is aligned with the ground while not in combat.", true);
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), CombatAlignSpeedProp, "Combat Align Speed", 5, 200);
CustomEditorProperties.CustomHelpLabelField("Controls the speed in which the AI is aligned with the ground while in combat.", true);
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), MaxNormalAngleProp, "Max Angle", 5, 50);
CustomEditorProperties.CustomHelpLabelField("Controls the maximum angle for an AI to rotate to when aligning with the ground.", true);
EditorGUILayout.PropertyField(AlignAIOnStartProp, new GUIContent("Align on Start"));
CustomEditorProperties.CustomHelpLabelField("Calculates the Align AI feature on Start.", true);
CustomEditorProperties.EndIndent();
}
CustomEditorProperties.EndFoldoutWindowBox();
}
}
/// <summary>
/// Holds all wander type settings and displays them through the EmeraldAIMovementEditor.
/// </summary>
void WanderSettings (EmeraldMovement self)
{
WanderFoldout.boolValue = EditorGUILayout.Foldout(WanderFoldout.boolValue, "Wander Type Settings", true, FoldoutStyle);
if (WanderFoldout.boolValue)
{
CustomEditorProperties.BeginFoldoutWindowBox();
CustomEditorProperties.TextTitleWithDescription("Wander Type Settings", "Controls how an AI wanders when not in combat. Using the Waypoints Wander Type will make the waypoint editor visible.", true);
EditorGUILayout.LabelField("Controls the type of wandering mechanics this AI will use. While wandering, AI will react to targets according to their Behavior Type, given they are visible and within their field of view.", EditorStyles.helpBox);
EditorGUILayout.PropertyField(WanderTypeProp, new GUIContent("Wander Type"));
CustomEditorProperties.BeginIndent();
if (self.WanderType == EmeraldMovement.WanderTypes.Dynamic)
{
CustomEditorProperties.CustomHelpLabelField("Dynamic - Allows an AI to randomly wander by dynamically generate waypoints around their Wander Radius.", true);
}
else if (self.WanderType == EmeraldMovement.WanderTypes.Waypoints)
{
CustomEditorProperties.CustomHelpLabelField("Waypoints - Allows you to define waypoints that the AI will move between. Note: The Waypoint Settings can be found in the foldout below this foldout.", true);
if (GUILayout.Button("Open Waypoint Settings"))
{
self.WanderFoldout = false;
self.WaypointsFoldout = true;
}
}
else if (self.WanderType == EmeraldMovement.WanderTypes.Stationary)
{
CustomEditorProperties.CustomHelpLabelField("Stationary - Allows an AI to stay stationary in the same position and will not move unless a target enters their trigger radius.", true);
CustomEditorProperties.CustomIntField(new Rect(), new GUIContent(), StationaryIdleSecondsMinProp, "Min Idle Animation Seconds");
CustomEditorProperties.CustomHelpLabelField("When using more than 1 idle animation, this controls the minimum amount of seconds needed before switching to the next idle " +
"animation. This will be randomized with the Max Idle Animation Seconds.", true);
CustomEditorProperties.CustomIntField(new Rect(), new GUIContent(), StationaryIdleSecondsMaxProp, "Max Idle Animation Seconds");
CustomEditorProperties.CustomHelpLabelField("When using more than 1 idle animation, this controls the maximum amount of seconds needed before switching to the next idle " +
"animation. This will be randomized with the Min Idle Animation Seconds.", true);
}
else if (self.WanderType == EmeraldMovement.WanderTypes.Destination)
{
CustomEditorProperties.CustomHelpLabelField("Destination - Allows an AI to travel to a single destination relying on Unity's NavMesh Pathfinding to get there. Once it reaches the destination, it will stay stationary.", true);
if (GUILayout.Button("Reset Destination Point"))
{
self.SingleDestination = self.transform.position + self.transform.forward * 2;
}
}
else if (self.WanderType == EmeraldMovement.WanderTypes.Custom)
{
CustomEditorProperties.CustomHelpLabelField("Custom - Allows an AI to travel to a destination set through code, which relies on Unity's NavMesh Pathfinding to get there. Once it reaches the destination, it will stay stationary.", false);
}
CustomEditorProperties.EndIndent();
EditorGUILayout.Space();
if (self.WanderType == EmeraldMovement.WanderTypes.Dynamic)
{
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), WanderRadiusProp, "Dynamic Wander Radius", ((int)self.StoppingDistance + 3), 300);
CustomEditorProperties.CustomHelpLabelField("Controls the radius that the AI uses to wander. The AI will randomly pick waypoints within this radius.", true);
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), MaxSlopeLimitProp, "Max Slope Limit", 10, 60);
CustomEditorProperties.CustomHelpLabelField("Controls the maximum slope that a waypoint can be generated on.", true);
EditorGUILayout.PropertyField(DynamicWanderLayerMaskProp, new GUIContent("Dynamic Wander Layers"));
CustomEditorProperties.CustomHelpLabelField("Controls what layers will be used when generating Dynamic Waypoints.", false);
if (DynamicWanderLayerMaskProp.intValue == 0)
{
GUI.backgroundColor = new Color(10f, 0.0f, 0.0f, 0.25f);
EditorGUILayout.LabelField("The Dynamic Wander LayerMask cannot contain Nothing.", EditorStyles.helpBox);
GUI.backgroundColor = Color.white;
}
}
EditorGUILayout.Space();
if (self.WanderType == EmeraldMovement.WanderTypes.Dynamic || self.WanderType == EmeraldMovement.WanderTypes.Waypoints)
{
CustomEditorProperties.CustomIntField(new Rect(), new GUIContent(), MinimumWaitTimeProp, "Min Wait Time");
CustomEditorProperties.CustomHelpLabelField("Controls the minimum amount of seconds before generating a new waypoint, when using the Dynamic and Random waypoint Wander Type. This amount is " +
"randomized with the Maximim Wait Time.", true);
CustomEditorProperties.CustomIntField(new Rect(), new GUIContent(), MaximumWaitTimeProp, "Max Wait Time");
CustomEditorProperties.CustomHelpLabelField("Controls the maximum amount of seconds before generating a new waypoint, when using the Dynamic and Random waypoint Wander Type. This amount " +
"is randomized with the Minimum Wait Time.", true);
}
CustomEditorProperties.EndFoldoutWindowBox();
}
if (self.WanderType == EmeraldMovement.WanderTypes.Destination)
{
if (self.SingleDestination == Vector3.zero)
{
self.SingleDestination = new Vector3(self.transform.position.x, self.transform.position.y, self.transform.position.z + 5);
}
}
}
/// <summary>
/// Used for drawing all of the Emerald AI Movement Settings.
/// </summary>
void OnSceneGUI()
{
EmeraldMovement self = (EmeraldMovement)target;
DrawWaypoints(self);
DrawWanderArea(self);
DrawSingleDestinationPoint(self);
}
/// <summary>
/// Draw the user created waypoints, when using the Waypoint Wander Type.
/// </summary>
void DrawWaypoints (EmeraldMovement self)
{
if (Event.current != null && Event.current.isKey && Event.current.type.Equals(EventType.KeyDown) && Event.current.keyCode == KeyCode.Delete)
{
Event.current.Use();
if (CurrentWaypointIndex != -1)
{
Undo.RecordObject(self, "Deleted Waypoint");
self.WaypointsList.RemoveAt(CurrentWaypointIndex);
}
}
if (self.WanderType == EmeraldMovement.WanderTypes.Waypoints && WaypointsFoldout.boolValue && !HideSettingsFoldout.boolValue)
{
if (self.WaypointsList.Count > 0 && self.WaypointsList != null)
{
Handles.color = Color.blue;
Handles.DrawLine(self.transform.position, self.WaypointsList[0]);
Handles.color = Color.white;
Handles.color = Color.green;
if (self.WaypointType != (EmeraldMovement.WaypointTypes.Random))
{
for (int i = 0; i < self.WaypointsList.Count - 1; i++)
{
Handles.DrawLine(self.WaypointsList[i], self.WaypointsList[i + 1]);
}
}
else if (self.WaypointType == (EmeraldMovement.WaypointTypes.Random))
{
for (int i = 0; i < self.WaypointsList.Count; i++)
{
for (int j = (i + 1); j < self.WaypointsList.Count; j++)
{
Handles.DrawLine(self.WaypointsList[i], self.WaypointsList[j]);
}
}
}
Handles.color = Color.white;
Handles.color = Color.green;
if (self.WaypointType == (EmeraldMovement.WaypointTypes.Loop))
{
Handles.DrawLine(self.WaypointsList[0], self.WaypointsList[self.WaypointsList.Count - 1]);
}
//Track last grabbed waypoint. If delete button is pressed (using EventType) delete point (will need undo and redo)
Handles.color = new Color(0, 1, 0, 0.25f);
for (int i = 0; i < self.WaypointsList.Count; i++)
{
if (CurrentWaypointIndex != i)
Handles.color = new Color(1, 1, 1, 0.05f);
else
Handles.color = new Color(1, 1, 0, 0.05f);
Handles.zTest = UnityEngine.Rendering.CompareFunction.LessEqual;
Handles.DrawSolidDisc(self.WaypointsList[i], Vector3.up, self.StoppingDistance);
Handles.color = new Color(0, 0, 0, 0.5f);
Handles.zTest = UnityEngine.Rendering.CompareFunction.Always;
Handles.DrawSolidDisc(self.WaypointsList[i], Vector3.up, 0.25f);
}
//Handles.zTest = UnityEngine.Rendering.CompareFunction.Always;
for (int i = 0; i < self.WaypointsList.Count; i++)
{
EditorGUI.BeginChangeCheck();
Vector3 Pos = Handles.PositionHandle(self.WaypointsList[i], Quaternion.identity);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(self, "Changed Waypoint Position");
self.WaypointsList[i] = Pos;
CurrentWaypointIndex = i;
}
Handles.color = Color.white;
CustomEditorProperties.DrawString("Waypoint " + (i + 1), self.WaypointsList[i] + Vector3.up, Color.white);
}
}
}
}
/// <summary>
/// Draws the wander area, when using the Dynamic Wander Type.
/// </summary>
void DrawWanderArea (EmeraldMovement self)
{
if (self.WanderType == EmeraldMovement.WanderTypes.Dynamic && WanderFoldout.boolValue && !HideSettingsFoldout.boolValue)
{
Handles.color = new Color(0, 0.6f, 0, 1f);
Handles.DrawWireDisc(self.transform.position, Vector3.up, (float)self.WanderRadius, 3f);
Handles.color = Color.white;
}
}
/// <summary>
/// Draws the destination point, when using the Destination Wander Type.
/// </summary>
void DrawSingleDestinationPoint (EmeraldMovement self)
{
if (self.WanderType == EmeraldMovement.WanderTypes.Destination && self.SingleDestination != Vector3.zero && WanderFoldout.boolValue && !HideSettingsFoldout.boolValue)
{
Handles.color = Color.green;
Handles.DrawLine(self.transform.position, self.SingleDestination);
Handles.color = Color.white;
Handles.zTest = UnityEngine.Rendering.CompareFunction.LessEqual;
Handles.SphereHandleCap(0, self.SingleDestination, Quaternion.identity, 0.5f, EventType.Repaint);
CustomEditorProperties.DrawString("Destination Point", self.SingleDestination + Vector3.up, Color.white);
Handles.zTest = UnityEngine.Rendering.CompareFunction.Always;
self.SingleDestination = Handles.PositionHandle(self.SingleDestination, Quaternion.identity);
EditorGUI.BeginChangeCheck();
Vector3 Pos = Handles.PositionHandle(self.SingleDestination, Quaternion.identity);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(self, "Changed Destination Position");
self.SingleDestination = Pos;
}
#if UNITY_EDITOR
EditorUtility.SetDirty(self);
#endif
}
}
void CustomFloatAnimationField(Rect position, GUIContent label, SerializedProperty property, string Name, float Min, float Max)
{
label = EditorGUI.BeginProperty(position, label, property);
EditorGUI.BeginChangeCheck();
var newValue = EditorGUILayout.Slider(Name, property.floatValue, Min, Max);
if (newValue != property.floatValue)
{
AnimationsUpdatedProp.boolValue = true;
}
if (EditorGUI.EndChangeCheck())
{
property.floatValue = newValue;
}
EditorGUI.EndProperty();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 32751c7551a63ee43bd61a32bc698b23
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,174 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditorInternal;
using System.Reflection;
namespace EmeraldAI.Utility
{
[CustomEditor(typeof(EmeraldSounds))]
[CanEditMultipleObjects]
public class EmeraldSoundsEditor : Editor
{
public static EditorWindow EditorWindowRef;
GUIStyle FoldoutStyle;
Texture SoundsEditorIcon;
#region SerializedProperties
SerializedProperty HideSettingsFoldout, SoundProfileProp, SoundProfileFoldout;
#endregion
void OnEnable()
{
if (SoundsEditorIcon == null) SoundsEditorIcon = Resources.Load("Editor Icons/EmeraldSounds") as Texture;
InitializeProperties();
}
void InitializeProperties ()
{
HideSettingsFoldout = serializedObject.FindProperty("HideSettingsFoldout");
SoundProfileFoldout = serializedObject.FindProperty("SoundProfileFoldout");
SoundProfileProp = serializedObject.FindProperty("SoundProfile");
}
public override void OnInspectorGUI()
{
FoldoutStyle = CustomEditorProperties.UpdateEditorStyles();
EmeraldSounds self = (EmeraldSounds)target;
serializedObject.Update();
CustomEditorProperties.BeginScriptHeaderNew("Sounds", SoundsEditorIcon, new GUIContent(), HideSettingsFoldout);
MissingSoundProfileMessage(self);
if (!HideSettingsFoldout.boolValue)
{
EditorGUILayout.Space();
DisplaySoundProfile(self);
EditorGUILayout.Space();
}
serializedObject.ApplyModifiedProperties();
CustomEditorProperties.EndScriptHeader();
}
/// <summary>
/// Displays a missing Sound Profile message within the EmeraldAISoundsEditor.
/// </summary>
void MissingSoundProfileMessage(EmeraldSounds self)
{
if (self.SoundProfile == null)
{
CustomEditorProperties.DisplaySetupWarning("This AI needs to have a Sound Profile. Press the 'Create New Sound Profile' button below to create a new one or assign one that has already been created.");
}
}
void DisplaySoundProfile(EmeraldSounds self)
{
SoundProfileFoldout.boolValue = CustomEditorProperties.Foldout(SoundProfileFoldout.boolValue, "Sound Profile Settings", true, FoldoutStyle);
if (SoundProfileFoldout.boolValue)
{
CustomEditorProperties.BeginFoldoutWindowBox();
CustomEditorProperties.TextTitleWithDescription("Sound Profile", "A Sound Profile holds all of an AI's sound data. This allows AI to share the same sound data with only needing to rely on a single " +
"Sound Profile. Any changes made to a Sound Profile will affect any AI using that Sound Profile. However, as many sound profiles can be created as needed. You can hover over the buttons below for more info.", true);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(SoundProfileProp);
CustomEditorProperties.CustomHelpLabelField("The Sound Profile this AI is using. All sounds and volumes will be used for this AI and any other AI using it.", false);
EditorGUI.BeginDisabledGroup(self.SoundProfile == null);
EditorGUILayout.Space();
if (GUILayout.Button(new GUIContent("Edit Sound Profile", "Edit the current Sound Profile in a separate window so you can preview sounds while keeping a reference to the current Sound Profile."), GUILayout.Height(20)))
{
EditSoundProfile(self);
}
GUILayout.Space(2.5f);
if (GUILayout.Button(new GUIContent("Clear Sound Profile", "Clears the Sound Profile slot so a new one can be created. Note: The current Sound Profile object will remain in your project at its current path."), GUILayout.Height(20)))
{
SoundProfileProp.objectReferenceValue = null;
serializedObject.ApplyModifiedProperties();
}
EditorGUI.EndDisabledGroup();
GUILayout.Space(2.5f);
EditorGUI.BeginDisabledGroup(self.SoundProfile != null);
if (GUILayout.Button(new GUIContent("Create New Sound Profile", "Creates a new Sound Profile within the Emerald AI/Sound Profiles folder. If you would like to create a new Sound Profile, remove the one in the current slot by pressing the 'Clear Sound Profile' button."), GUILayout.Height(20)))
{
CreateSoundProfile(self);
}
EditorGUI.EndDisabledGroup();
GUILayout.Space(2.5f);
CustomEditorProperties.EndFoldoutWindowBox();
}
}
/// <summary>
/// Creates a new Sound Profile object, using the object's name, to the user set folder.
/// </summary>
void CreateSoundProfile(EmeraldSounds self)
{
string FilePath = EditorUtility.SaveFilePanelInProject("Save as Sound Profile", "", "asset", "Please enter a file name to save the file to");
if (string.IsNullOrEmpty(FilePath))
{
//For some reason, EditorUtility.SaveFilePanelInProject throws an incorrect EditorGUILayout error when it's used with some custom properties. This fixes it...
CustomEditorProperties.BeginScriptHeader("", null);
CustomEditorProperties.BeginFoldoutWindowBox();
return;
}
if (string.IsNullOrEmpty(AssetDatabase.AssetPathToGUID(FilePath)))
{
EmeraldSoundProfile NewSoundProfile = CreateInstance<EmeraldSoundProfile>();
AssetDatabase.CreateAsset(NewSoundProfile, FilePath);
self.SoundProfile = NewSoundProfile;
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
serializedObject.ApplyModifiedProperties();
}
else
{
var ExistingSoundProfile = AssetDatabase.LoadAssetAtPath(FilePath, typeof(EmeraldSoundProfile));
self.SoundProfile = (EmeraldSoundProfile)ExistingSoundProfile;
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
//For some reason, EditorUtility.SaveFilePanelInProject throws an incorrect EditorGUILayout error when it's used with some custom properties. This fixes it...
CustomEditorProperties.BeginScriptHeader("", null);
CustomEditorProperties.BeginFoldoutWindowBox();
}
/// <summary>
/// Opens the current Sound Profile in a separate window so users can preview sounds while keeping a reference to the Sound Profile.
/// </summary>
void EditSoundProfile (EmeraldSounds self)
{
if (self.SoundProfile == null)
return;
//Close the static reference to any other Sound Profile PropertyEditors before creating a new one
if (EditorWindowRef != null && EditorWindowRef.name == "Sound Profile")
EditorWindowRef.Close();
System.Type propertyEditorType = typeof(Editor).Assembly.GetType("UnityEditor.PropertyEditor");
System.Type[] callTypes = new[] { typeof(Object), typeof(bool) };
object[] callOpenBuffer = { null, true };
//Use reflection to create a PropertyEditor, as there's no API to do so before Unity 2021.2, and pass the Sound Profile to open it in a separate tab.
MethodInfo openPropertyEditorInfo;
openPropertyEditorInfo = propertyEditorType.GetMethod("OpenPropertyEditor",BindingFlags.Static | BindingFlags.NonPublic, null, callTypes, null);
callOpenBuffer[0] = self.SoundProfile;
openPropertyEditorInfo.Invoke(null, callOpenBuffer);
//Cache the PropertyEditor and name it Sound Profile (only one can be active at a time)
EditorWindowRef = EditorWindow.GetWindow(typeof(Editor).Assembly.GetType("UnityEditor.PropertyEditor"));
EditorWindowRef.name = "Sound Profile";
EditorWindowRef.minSize = new Vector2(Screen.currentResolution.width / 4f, Screen.currentResolution.height / 2f);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1691202b02595364dbd33f48b1abe0c5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: