init 1.1.2
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81fa81d93380d90499ccff5089552caa
|
||||
folderAsset: yes
|
||||
timeCreated: 1548370851
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ac46cd6fb9244bd4cb5b022c28fc5a7f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da1e0f0d64835e34cb43457b8be89548
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,130 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace EmeraldAI.Utility
|
||||
{
|
||||
[CustomEditor(typeof(EmeraldDebugger))]
|
||||
[CanEditMultipleObjects]
|
||||
public class EmeraldDebuggerEditor : Editor
|
||||
{
|
||||
GUIStyle FoldoutStyle;
|
||||
Texture EventsEditorIcon;
|
||||
|
||||
//Bools
|
||||
SerializedProperty SettingsFoldoutProp, HideSettingsFoldoutProp;
|
||||
|
||||
SerializedProperty EnableDebuggingToolsProp, DrawLineOfSightLinesProp, DrawNavMeshPathProp, DrawNavMeshDestinationProp, DrawLookAtPointsProp, DrawUndetectedTargetsLineProp, DebugLogTargetsProp, DebugLogObstructionsProp, NavMeshPathColorProp, NavMeshDestinationColorProp,
|
||||
DrawFootstepPositions, DebugLogFootsteps;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (EventsEditorIcon == null) EventsEditorIcon = Resources.Load("Editor Icons/EmeraldDebugger") as Texture;
|
||||
InitializeProperties();
|
||||
}
|
||||
|
||||
void InitializeProperties()
|
||||
{
|
||||
//Bool
|
||||
SettingsFoldoutProp = serializedObject.FindProperty("SettingsFoldout");
|
||||
HideSettingsFoldoutProp = serializedObject.FindProperty("HideSettingsFoldout");
|
||||
|
||||
EnableDebuggingToolsProp = serializedObject.FindProperty("EnableDebuggingTools");
|
||||
DrawLineOfSightLinesProp = serializedObject.FindProperty("DrawLineOfSightLines");
|
||||
DrawNavMeshPathProp = serializedObject.FindProperty("DrawNavMeshPath");
|
||||
DrawNavMeshDestinationProp = serializedObject.FindProperty("DrawNavMeshDestination");
|
||||
DrawLookAtPointsProp = serializedObject.FindProperty("DrawLookAtPoints");
|
||||
DrawUndetectedTargetsLineProp = serializedObject.FindProperty("DrawUndetectedTargetsLine");
|
||||
DebugLogTargetsProp = serializedObject.FindProperty("DebugLogTargets");
|
||||
DebugLogObstructionsProp = serializedObject.FindProperty("DebugLogObstructions");
|
||||
NavMeshPathColorProp = serializedObject.FindProperty("NavMeshPathColor");
|
||||
NavMeshDestinationColorProp = serializedObject.FindProperty("NavMeshDestinationColor");
|
||||
DrawFootstepPositions = serializedObject.FindProperty("DrawFootstepPositions");
|
||||
DebugLogFootsteps = serializedObject.FindProperty("DebugLogFootsteps");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
FoldoutStyle = CustomEditorProperties.UpdateEditorStyles();
|
||||
serializedObject.Update();
|
||||
|
||||
CustomEditorProperties.BeginScriptHeaderNew("Debugger", EventsEditorIcon, new GUIContent(), HideSettingsFoldoutProp);
|
||||
|
||||
if (!HideSettingsFoldoutProp.boolValue)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
GeneralEvents();
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
CustomEditorProperties.EndScriptHeader();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
void GeneralEvents()
|
||||
{
|
||||
EmeraldDebugger self = (EmeraldDebugger)target;
|
||||
|
||||
SettingsFoldoutProp.boolValue = EditorGUILayout.Foldout(SettingsFoldoutProp.boolValue, "Debugging Settings", true, FoldoutStyle);
|
||||
|
||||
if (SettingsFoldoutProp.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
CustomEditorProperties.TextTitleWithDescription("Debugging Settings", "Allows users to see lots of internal functionality and useful information to help identify issues or bugs. Control which debugging settings will be enabled. " +
|
||||
"You can use the Enable Debugging Tools setting to disable all options so you can keep this component on AI until it's needed.", true);
|
||||
|
||||
EditorGUILayout.PropertyField(EnableDebuggingToolsProp);
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls whether or not the debugging tools are enabled.", true);
|
||||
|
||||
if (self.EnableDebuggingTools == YesOrNo.Yes)
|
||||
{
|
||||
CustomEditorProperties.BeginIndent(15);
|
||||
EditorGUILayout.PropertyField(DrawLineOfSightLinesProp);
|
||||
CustomEditorProperties.CustomHelpLabelField("Allows Line of Sight raycasts to be draw, while in the Unity Editor. This can be useful for ensuring raycast are being positioned correctly as well as seeing if an AI's sight is being obstructed.", true);
|
||||
|
||||
EditorGUILayout.PropertyField(DrawNavMeshPathProp);
|
||||
CustomEditorProperties.CustomHelpLabelField("Allows an AI's current path to be drawn.", true);
|
||||
if (self.DrawNavMeshPath == YesOrNo.Yes)
|
||||
{
|
||||
CustomEditorProperties.BeginIndent();
|
||||
EditorGUILayout.PropertyField(NavMeshPathColorProp);
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the color of the NavMesh Path.", true);
|
||||
CustomEditorProperties.EndIndent();
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(DrawNavMeshDestinationProp);
|
||||
CustomEditorProperties.CustomHelpLabelField("Allows an AI's current destination to be drawn. Note: This requires the Emerald Debugger to not be minimized.", true);
|
||||
if (self.DrawNavMeshDestination == YesOrNo.Yes)
|
||||
{
|
||||
CustomEditorProperties.BeginIndent(15);
|
||||
EditorGUILayout.PropertyField(NavMeshDestinationColorProp);
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the color of the NavMesh Destination.", true);
|
||||
CustomEditorProperties.EndIndent();
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(DrawLookAtPointsProp);
|
||||
CustomEditorProperties.CustomHelpLabelField("Allows the AI's current look at point to be draw (when using the InverseK inematics component). " +
|
||||
"This can be useful to ensuring AI are looking at the right points of a target, including positions that are modified with a Target Position Modifier component.", true);
|
||||
|
||||
EditorGUILayout.PropertyField(DrawUndetectedTargetsLineProp);
|
||||
CustomEditorProperties.CustomHelpLabelField("Allows targets within an AI's detection radius, that haven't been detected yet, to have a line drawn to them. This can happen if an undetected target is still obstructed or is outside of an AI's Field of View.", true);
|
||||
|
||||
EditorGUILayout.PropertyField(DebugLogObstructionsProp);
|
||||
CustomEditorProperties.CustomHelpLabelField("Allows the AI's obstructions to be displayed in the Unity Console. This can be useful for identifying the AI's current obstruction between it and its target.", true);
|
||||
|
||||
EditorGUILayout.PropertyField(DebugLogTargetsProp);
|
||||
CustomEditorProperties.CustomHelpLabelField("Allows the target objects to be displayed in the Unity Console. This can be useful for ensuring the proper object is being detected when the AI is targeting an object.", true);
|
||||
|
||||
EditorGUILayout.PropertyField(DrawFootstepPositions);
|
||||
CustomEditorProperties.CustomHelpLabelField("Allows the AI's footsteps collision positions to be displayed (drawn as yellow circles). If no Footsteps Component is present, this setting will be ignored.", true);
|
||||
|
||||
EditorGUILayout.PropertyField(DebugLogFootsteps);
|
||||
CustomEditorProperties.CustomHelpLabelField("Allows the AI's footsteps collision objects and used Footstep Surface Object to be displayed in the Unity Console. This can be helpful to ensure there are no unintended collisions " +
|
||||
"and that the footstep caculations are detecting properly. If no Footsteps Component is present, this setting will be ignored.", true);
|
||||
CustomEditorProperties.EndIndent();
|
||||
}
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 295035266ae8e2341a752fe321ec1835
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,172 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
|
||||
namespace EmeraldAI.Utility
|
||||
{
|
||||
[CustomEditor(typeof(EmeraldEvents))]
|
||||
[CanEditMultipleObjects]
|
||||
public class EmeraldEventsEditor : Editor
|
||||
{
|
||||
GUIStyle FoldoutStyle;
|
||||
Texture EventsEditorIcon;
|
||||
|
||||
//Bools
|
||||
SerializedProperty HideSettingsFoldout, GeneralEventsFoldout, CombatEventsFoldout;
|
||||
|
||||
//Events
|
||||
SerializedProperty OnDeathEventProp, OnTakeDamageEventProp, OnTakeCritDamageEventProp, OnReachedDestinationEventProp, OnReachedWaypointEventProp, OnGeneratedWaypointEventProp, OnStartEventProp, OnAttackStartEventProp, OnFleeEventProp, OnStartCombatEventProp, OnEndCombatEventProp,
|
||||
OnEnabledEventProp, OnPlayerDetectedEventProp, OnKilledTargetEventProp, OnDoDamageEventProp, OnDoCritDamageEventProp, OnAttackEndEventProp, OnEnemyTargetDetectedEventProp;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (EventsEditorIcon == null) EventsEditorIcon = Resources.Load("Editor Icons/EmeraldEvents") as Texture;
|
||||
InitializeProperties();
|
||||
}
|
||||
|
||||
void InitializeProperties()
|
||||
{
|
||||
//Bools
|
||||
HideSettingsFoldout = serializedObject.FindProperty("HideSettingsFoldout");
|
||||
GeneralEventsFoldout = serializedObject.FindProperty("GeneralEventsFoldout");
|
||||
CombatEventsFoldout = serializedObject.FindProperty("CombatEventsFoldout");
|
||||
|
||||
//Events
|
||||
OnDeathEventProp = serializedObject.FindProperty("OnDeathEvent");
|
||||
OnTakeDamageEventProp = serializedObject.FindProperty("OnTakeDamageEvent");
|
||||
OnTakeCritDamageEventProp = serializedObject.FindProperty("OnTakeCritDamageEvent");
|
||||
OnDoDamageEventProp = serializedObject.FindProperty("OnDoDamageEvent");
|
||||
OnReachedDestinationEventProp = serializedObject.FindProperty("OnReachedDestinationEvent");
|
||||
OnReachedWaypointEventProp = serializedObject.FindProperty("OnReachedWaypointEvent");
|
||||
OnGeneratedWaypointEventProp = serializedObject.FindProperty("OnGeneratedWaypointEvent");
|
||||
OnStartEventProp = serializedObject.FindProperty("OnStartEvent");
|
||||
OnPlayerDetectedEventProp = serializedObject.FindProperty("OnPlayerDetectedEvent");
|
||||
OnEnemyTargetDetectedEventProp = serializedObject.FindProperty("OnEnemyTargetDetectedEvent");
|
||||
OnEnabledEventProp = serializedObject.FindProperty("OnEnabledEvent");
|
||||
OnAttackStartEventProp = serializedObject.FindProperty("OnAttackStartEvent");
|
||||
OnAttackEndEventProp = serializedObject.FindProperty("OnAttackEndEvent");
|
||||
OnFleeEventProp = serializedObject.FindProperty("OnFleeEvent");
|
||||
OnStartCombatEventProp = serializedObject.FindProperty("OnStartCombatEvent");
|
||||
OnEndCombatEventProp = serializedObject.FindProperty("OnEndCombatEvent");
|
||||
OnKilledTargetEventProp = serializedObject.FindProperty("OnKilledTargetEvent");
|
||||
OnDoCritDamageEventProp = serializedObject.FindProperty("OnDoCritDamageEvent");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
FoldoutStyle = CustomEditorProperties.UpdateEditorStyles();
|
||||
EmeraldEvents self = (EmeraldEvents)target;
|
||||
serializedObject.Update();
|
||||
|
||||
CustomEditorProperties.BeginScriptHeaderNew("Events", EventsEditorIcon, new GUIContent(), HideSettingsFoldout);
|
||||
|
||||
if (!HideSettingsFoldout.boolValue)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
GeneralEvents(self);
|
||||
EditorGUILayout.Space();
|
||||
CombatEvents(self);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
CustomEditorProperties.EndScriptHeader();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
void GeneralEvents(EmeraldEvents self)
|
||||
{
|
||||
GeneralEventsFoldout.boolValue = EditorGUILayout.Foldout(GeneralEventsFoldout.boolValue, "General Events", true, FoldoutStyle);
|
||||
|
||||
if (GeneralEventsFoldout.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
CustomEditorProperties.TextTitleWithDescription("General Events", "Holds all general related events.", true);
|
||||
|
||||
CustomEditorProperties.CustomHelpLabelField("Triggers an event when this AI is enabled. This can be useful for events that need to be called when an AI is being respawned.", false);
|
||||
EditorGUILayout.PropertyField(OnEnabledEventProp);
|
||||
|
||||
CustomEditorProperties.CustomHelpLabelField("Triggers an event on Start. This can be useful for initializing custom mechanics and quests as well as spawning animations.", false);
|
||||
EditorGUILayout.PropertyField(OnStartEventProp);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.CustomHelpLabelField("Triggers an event when this AI reaches their destination when using the Destination Wander Type.", false);
|
||||
EditorGUILayout.PropertyField(OnReachedDestinationEventProp, new GUIContent("On Reached Destination Event"));
|
||||
|
||||
CustomEditorProperties.CustomHelpLabelField("Triggers an event each time this AI arrives at a waypoint (for both Dynamic and Waypoint Wander Types).", false);
|
||||
EditorGUILayout.PropertyField(OnReachedWaypointEventProp, new GUIContent("On Reached Waypoint Event"));
|
||||
|
||||
CustomEditorProperties.CustomHelpLabelField("Triggers an event each time this AI generates a waypoint (for both Dynamic and Waypoint Wander Types).", false);
|
||||
EditorGUILayout.PropertyField(OnGeneratedWaypointEventProp, new GUIContent("On Generated Waypoint Event"));
|
||||
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.CustomHelpLabelField("Triggers an event when this AI detects the player when not in combat mode. This can be useful for quests, initializing dialogue, or greetings. " +
|
||||
"This event is dependent on the AI's Detection Radius and is triggered when the player enters it.", false);
|
||||
EditorGUILayout.PropertyField(OnPlayerDetectedEventProp);
|
||||
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
}
|
||||
}
|
||||
|
||||
void CombatEvents(EmeraldEvents self)
|
||||
{
|
||||
CombatEventsFoldout.boolValue = EditorGUILayout.Foldout(CombatEventsFoldout.boolValue, "Combat Events", true, FoldoutStyle);
|
||||
|
||||
if (CombatEventsFoldout.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
CustomEditorProperties.TextTitleWithDescription("Combat Events", "Holds all combat related events.", true);
|
||||
|
||||
CustomEditorProperties.CustomHelpLabelField("Triggers an event when the AI first starts combat and will not be called again until the AI re-enters combat.", false);
|
||||
EditorGUILayout.PropertyField(OnStartCombatEventProp);
|
||||
|
||||
CustomEditorProperties.CustomHelpLabelField("Triggers an event when the AI ends combat and there are no detectable enemy targets nearby.", false);
|
||||
EditorGUILayout.PropertyField(OnEndCombatEventProp);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.CustomHelpLabelField("Triggers an event each time the AI successfully detects a target when while in combat.", false);
|
||||
EditorGUILayout.PropertyField(OnEnemyTargetDetectedEventProp, new GUIContent("On Detect Target Event"));
|
||||
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.CustomHelpLabelField("Triggers an event when the AI's attack starts. Note: This event will trigger even if the AI misses its target.", false);
|
||||
EditorGUILayout.PropertyField(OnAttackStartEventProp, new GUIContent("On Attack Start Event"));
|
||||
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.CustomHelpLabelField("Triggers an event when the AI's attack ends. Note: This event will trigger even if the AI misses its target.", false);
|
||||
EditorGUILayout.PropertyField(OnAttackEndEventProp);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.CustomHelpLabelField("Triggers an event when the AI is damaged.", false);
|
||||
EditorGUILayout.PropertyField(OnTakeDamageEventProp, new GUIContent("On Take Damage Event"));
|
||||
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.CustomHelpLabelField("Triggers an event when the AI is damaged and takes a critical hit.", false);
|
||||
EditorGUILayout.PropertyField(OnTakeCritDamageEventProp, new GUIContent("On Take Crit Damage Event"));
|
||||
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.CustomHelpLabelField("Triggers an event when the AI successfully deals any kind of damage.", false);
|
||||
EditorGUILayout.PropertyField(OnDoDamageEventProp);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.CustomHelpLabelField("Triggers an event when the AI deals damage and it's a critical hit.", false);
|
||||
EditorGUILayout.PropertyField(OnDoCritDamageEventProp);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.CustomHelpLabelField("Triggers an event when the AI flees. This can be useful for fleeing sounds or other added functionality.", false);
|
||||
EditorGUILayout.PropertyField(OnFleeEventProp);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.CustomHelpLabelField("Triggers an event when the AI kills a target.", false);
|
||||
EditorGUILayout.PropertyField(OnKilledTargetEventProp);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.CustomHelpLabelField("Triggers an event when the AI dies. This can be useful for triggering loot generation, quest mechanics, or other death related events.", false);
|
||||
EditorGUILayout.PropertyField(OnDeathEventProp, new GUIContent("On Death Event"));
|
||||
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d532acd61a9e93241b65e8c68adc9fb3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,169 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
|
||||
namespace EmeraldAI.Utility
|
||||
{
|
||||
[CustomEditor(typeof(EmeraldFootsteps))]
|
||||
[CanEditMultipleObjects]
|
||||
public class EmeraldFootstepsEditor : Editor
|
||||
{
|
||||
GUIStyle FoldoutStyle;
|
||||
Texture FootstepsEditorIcon;
|
||||
|
||||
//Bools
|
||||
SerializedProperty HideSettingsFoldout, FootstepsFoldout, SurfaceFoldout;
|
||||
|
||||
//Variables
|
||||
SerializedProperty FootstepSurfaces, IgnoreLayers, FeetTransforms;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (FootstepsEditorIcon == null) FootstepsEditorIcon = Resources.Load("Editor Icons/EmeraldFootsteps") as Texture;
|
||||
InitializeProperties();
|
||||
}
|
||||
|
||||
void InitializeProperties()
|
||||
{
|
||||
//Bools
|
||||
HideSettingsFoldout = serializedObject.FindProperty("HideSettingsFoldout");
|
||||
FootstepsFoldout = serializedObject.FindProperty("FootstepsFoldout");
|
||||
SurfaceFoldout = serializedObject.FindProperty("SurfaceFoldout");
|
||||
|
||||
//Variables
|
||||
FootstepSurfaces = serializedObject.FindProperty("FootstepSurfaces");
|
||||
IgnoreLayers = serializedObject.FindProperty("IgnoreLayers");
|
||||
FeetTransforms = serializedObject.FindProperty("FeetTransforms");
|
||||
}
|
||||
|
||||
void DisplayWarningMessages (EmeraldFootsteps self)
|
||||
{
|
||||
if (self.FeetTransforms.Count == 0)
|
||||
{
|
||||
CustomEditorProperties.DisplaySetupWarning("The Feet Transforms list is empty, please assign your AI's feet transforms to Feet Transforms list.");
|
||||
}
|
||||
else if (self.FootstepSurfaces.Count == 0)
|
||||
{
|
||||
CustomEditorProperties.DisplaySetupWarning("The Footstep Surfaces list is empty, please assign at least 1 Footstep Surface Object.");
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
FoldoutStyle = CustomEditorProperties.UpdateEditorStyles();
|
||||
EmeraldFootsteps self = (EmeraldFootsteps)target;
|
||||
serializedObject.Update();
|
||||
|
||||
CustomEditorProperties.BeginScriptHeaderNew("Footsteps", FootstepsEditorIcon, new GUIContent(), HideSettingsFoldout);
|
||||
|
||||
DisplayWarningMessages(self);
|
||||
|
||||
if (!HideSettingsFoldout.boolValue)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
FootstepSettings(self);
|
||||
EditorGUILayout.Space();
|
||||
SurfaceSettings(self);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
CustomEditorProperties.EndScriptHeader();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
void FootstepSettings(EmeraldFootsteps self)
|
||||
{
|
||||
FootstepsFoldout.boolValue = EditorGUILayout.Foldout(FootstepsFoldout.boolValue, "Footstep Settings", true, FoldoutStyle);
|
||||
|
||||
if (FootstepsFoldout.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
CustomEditorProperties.TextTitleWithDescription("Foostep Settings", "Controls various settings for the Foosteps component.", false);
|
||||
CustomEditorProperties.ImportantTutorialButton("You will need to create Footstep Animation Events on your AI's animations in order for footsteps to trigger.", "https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-optional/footsteps-component/setting-up-the-footsteps-component");
|
||||
EditorGUILayout.Space();
|
||||
|
||||
CustomEditorProperties.CustomPropertyField(IgnoreLayers, "Ignore Layers", "Controls which layers will be ignored when calculating footsteps. Your AI's own layer and its LBD's Collider Layer will automatically be included during runtime.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (GUILayout.Button(new GUIContent("Auto Grab Feet Transforms", "Attemps to automatically grab the AI's feet transforms.")))
|
||||
{
|
||||
GetFeetTransforms(self);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.BeginIndent(12);
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the transforms for this AI's feet. Raycasts will be fired from these points to calculate footsteps. If Step Effects are used, they will be spawned at the position of the foot closest to the ground.", false);
|
||||
EditorGUILayout.PropertyField(FeetTransforms);
|
||||
CustomEditorProperties.EndIndent();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
}
|
||||
}
|
||||
|
||||
void SurfaceSettings(EmeraldFootsteps self)
|
||||
{
|
||||
SurfaceFoldout.boolValue = EditorGUILayout.Foldout(SurfaceFoldout.boolValue, "Surface Settings", true, FoldoutStyle);
|
||||
|
||||
if (SurfaceFoldout.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
CustomEditorProperties.TextTitleWithDescription("Surface Settings", "Controls the Footstep Surface Objects that will be used. A Footstep Surface Object can be created by right clicking in the Project tab and going to Create>Emerald AI>Footstep Surface Object.", true);
|
||||
|
||||
CustomEditorProperties.CustomHelpLabelField("A list of Footstep Surfaces used to determine which footstep sound and effect should play given the received information.", false);
|
||||
CustomEditorProperties.BeginIndent(12);
|
||||
EditorGUILayout.PropertyField(FootstepSurfaces);
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.EndIndent();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
}
|
||||
}
|
||||
|
||||
void GetFeetTransforms (EmeraldFootsteps self)
|
||||
{
|
||||
//Search all the transforms within an AI and look for the word root
|
||||
foreach (Transform t in self.GetComponentsInChildren<Transform>())
|
||||
{
|
||||
if (t.name.Contains("foot") || t.name.Contains("Foot") || t.name.Contains("FOOT")) //Look for the word foot within all transforms within the AI
|
||||
{
|
||||
if (!t.name.Contains("ik") && !t.name.Contains("Ik") && !t.name.Contains("IK") && !t.name.Contains("Foot Collider"))
|
||||
{
|
||||
if (!self.FeetTransforms.Contains(t))
|
||||
{
|
||||
self.FeetTransforms.Add(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Transform root in self.GetComponentsInChildren<Transform>())
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
if (i < root.childCount && root.GetChild(i).name == "root" || i < root.childCount && root.GetChild(i).name == "Root" || i < root.childCount && root.GetChild(i).name == "ROOT") //Only look in the root transform - 3 child index in
|
||||
{
|
||||
foreach (Transform t in root.GetChild(i).GetComponentsInChildren<Transform>())
|
||||
{
|
||||
if (t.name.Contains("foot") || t.name.Contains("Foot") || t.name.Contains("FOOT")) //Look for the word foot within all transforms within the AI
|
||||
{
|
||||
//Exclude transforms with IK, as well as the word Foot Collider, as these aren't usually bone transforms.
|
||||
if (!t.name.Contains("ik") && !t.name.Contains("Ik") && !t.name.Contains("IK") && !t.name.Contains("Foot Collider"))
|
||||
{
|
||||
if (!self.FeetTransforms.Contains(t))
|
||||
{
|
||||
self.FeetTransforms.Add(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c257186aedb9a2e44b877547b14730a9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,193 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using System.Linq;
|
||||
using UnityEngine.Animations.Rigging;
|
||||
|
||||
namespace EmeraldAI.Utility
|
||||
{
|
||||
[CustomEditor(typeof(EmeraldInverseKinematics))]
|
||||
[CanEditMultipleObjects]
|
||||
public class EmeraldInverseKinematicsEditor : Editor
|
||||
{
|
||||
GUIStyle FoldoutStyle;
|
||||
Texture InverseKinematicsEditorIcon;
|
||||
|
||||
SerializedProperty WanderingLookAtLimit, WanderingLookSpeed, WanderingLookDistance, WanderingLookHeightOffset, CombatLookAtLimit, CombatLookSpeed, CombatLookDistance, CombatLookHeightOffset;
|
||||
SerializedProperty HideSettingsFoldout, GeneralIKSettingsFoldout, RigSettingsFoldout;
|
||||
ReorderableList UpperBodyRigsList;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (InverseKinematicsEditorIcon == null) InverseKinematicsEditorIcon = Resources.Load("Editor Icons/EmeraldInverseKinematics") as Texture;
|
||||
InitializeProperties();
|
||||
InitializeLists();
|
||||
}
|
||||
|
||||
void InitializeProperties ()
|
||||
{
|
||||
HideSettingsFoldout = serializedObject.FindProperty("HideSettingsFoldout");
|
||||
GeneralIKSettingsFoldout = serializedObject.FindProperty("GeneralIKSettingsFoldout");
|
||||
RigSettingsFoldout = serializedObject.FindProperty("RigSettingsFoldout");
|
||||
|
||||
WanderingLookAtLimit = serializedObject.FindProperty("WanderingLookAtLimit");
|
||||
WanderingLookSpeed = serializedObject.FindProperty("WanderingLookSpeed");
|
||||
WanderingLookDistance = serializedObject.FindProperty("WanderingLookDistance");
|
||||
WanderingLookHeightOffset = serializedObject.FindProperty("WanderingLookHeightOffset");
|
||||
CombatLookAtLimit = serializedObject.FindProperty("CombatLookAtLimit");
|
||||
CombatLookSpeed = serializedObject.FindProperty("CombatLookSpeed");
|
||||
CombatLookDistance = serializedObject.FindProperty("CombatLookDistance");
|
||||
CombatLookHeightOffset = serializedObject.FindProperty("CombatLookHeightOffset");
|
||||
}
|
||||
|
||||
void InitializeLists ()
|
||||
{
|
||||
UpperBodyRigsList = new ReorderableList(serializedObject, serializedObject.FindProperty("UpperBodyRigsList"), true, true, true, true);
|
||||
UpperBodyRigsList.drawHeaderCallback = rect =>
|
||||
{
|
||||
EditorGUI.LabelField(rect, "Upper Body Rigs List", EditorStyles.boldLabel);
|
||||
};
|
||||
UpperBodyRigsList.drawElementCallback =
|
||||
(Rect rect, int index, bool isActive, bool isFocused) =>
|
||||
{
|
||||
var element = UpperBodyRigsList.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();
|
||||
EmeraldInverseKinematics self = (EmeraldInverseKinematics)target;
|
||||
serializedObject.Update();
|
||||
|
||||
CustomEditorProperties.BeginScriptHeaderNew("Inverse Kinematics", InverseKinematicsEditorIcon, new GUIContent(), HideSettingsFoldout);
|
||||
|
||||
MissingRigMessage(self);
|
||||
|
||||
if (!HideSettingsFoldout.boolValue)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
RigSettings(self);
|
||||
EditorGUILayout.Space();
|
||||
GeneralIKSettings(self);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
CustomEditorProperties.EndScriptHeader();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a missing rig message within the EmeraldInverseKinematicsEditor.
|
||||
/// </summary>
|
||||
void MissingRigMessage(EmeraldInverseKinematics self)
|
||||
{
|
||||
if (self.UpperBodyRigsList.Count == 0)
|
||||
{
|
||||
CustomEditorProperties.DisplaySetupWarning("This AI doesn't any applied Rigs so they will not be controlled by this IK component. Please add the Rigs you would like to be controlled to the UpperBodyRigsList within the Rig Settings Foldout.");
|
||||
}
|
||||
}
|
||||
|
||||
void RigSettings(EmeraldInverseKinematics self)
|
||||
{
|
||||
RigSettingsFoldout.boolValue = CustomEditorProperties.Foldout(RigSettingsFoldout.boolValue, "Rig Settings", true, FoldoutStyle);
|
||||
|
||||
if (RigSettingsFoldout.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
|
||||
CustomEditorProperties.TextTitleWithDescription("Rig Settings", "Assign all Rig components this AI will be using for its IK. Upper Body Rigs will be used for looking and aiming at targets. Lower Body Rigs will be used for positioning an AI's " +
|
||||
"legs and feet with the ground. You will be able to control these settings further with the settings below", true);
|
||||
|
||||
if (GUILayout.Button(new GUIContent("Create New Rig", "Creates a new Rig, in addition to any others that have been created, and automatically assigns and parents it to this AI." +
|
||||
"\n\nNote: You will still need to add a constraint component on a child object within your Rig.")))
|
||||
{
|
||||
CustomRigSetup(self);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.CustomHelpLabelField("The Upper Body Rig components (Head, Spine, Chest, Arms, etc.) that this AI uses for its aiming and looking IK.", false);
|
||||
UpperBodyRigsList.DoLayoutList();
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
}
|
||||
}
|
||||
|
||||
void CustomRigSetup (EmeraldInverseKinematics self)
|
||||
{
|
||||
var rigBuilder = self.transform.GetComponent<RigBuilder>();
|
||||
|
||||
if (rigBuilder == null)
|
||||
rigBuilder = Undo.AddComponent<RigBuilder>(self.transform.gameObject);
|
||||
else
|
||||
Undo.RecordObject(rigBuilder, "Rig Builder Component Added.");
|
||||
|
||||
var name = "Rig";
|
||||
var cnt = 1;
|
||||
while (rigBuilder.transform.Find(string.Format("{0} {1}", name, cnt)) != null)
|
||||
{
|
||||
cnt++;
|
||||
}
|
||||
name = string.Format("{0} {1}", name, cnt);
|
||||
var rigGameObject = new GameObject(name);
|
||||
Undo.RegisterCreatedObjectUndo(rigGameObject, name);
|
||||
rigGameObject.transform.SetParent(rigBuilder.transform);
|
||||
rigGameObject.transform.localPosition = Vector3.zero;
|
||||
rigGameObject.transform.localScale = Vector3.one;
|
||||
|
||||
var rig = Undo.AddComponent<Rig>(rigGameObject);
|
||||
rigBuilder.layers.Add(new RigLayer(rig));
|
||||
|
||||
if (PrefabUtility.IsPartOfPrefabInstance(rigBuilder))
|
||||
EditorUtility.SetDirty(rigBuilder);
|
||||
|
||||
self.UpperBodyRigsList.Add(rig);
|
||||
}
|
||||
|
||||
void GeneralIKSettings (EmeraldInverseKinematics self)
|
||||
{
|
||||
GeneralIKSettingsFoldout.boolValue = CustomEditorProperties.Foldout(GeneralIKSettingsFoldout.boolValue, "General IK Settings", true, FoldoutStyle);
|
||||
|
||||
if (GeneralIKSettingsFoldout.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
|
||||
CustomEditorProperties.TextTitleWithDescription("General IK Settings", "Controls the speeds and angles for this AI's IK.", true);
|
||||
|
||||
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), WanderingLookAtLimit, "Wandering Angle Limit", 1, 90);
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the angle limit for looking at targets.", true);
|
||||
|
||||
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), CombatLookAtLimit, "Combat Angle Limit", 1, 90);
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the angle limit for looking at targets.", true);
|
||||
|
||||
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), WanderingLookSpeed, "Wandering Look Speed", 1f, 15f);
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls how quickly an AI will look at targets.", true);
|
||||
|
||||
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), CombatLookSpeed, "Combat Look Speed", 1f, 15f);
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls how quickly an AI will look at targets.", true);
|
||||
|
||||
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), WanderingLookDistance, "Wandering Look Distance", 5, 40);
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the distance for looking at targets.", true);
|
||||
|
||||
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), CombatLookDistance, "Combat Look Distance", 5, 40);
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the distance for looking at targets.", true);
|
||||
|
||||
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), WanderingLookHeightOffset, "Wandering Look Height Offset", -3f, 3f);
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the height offset for the default position when looking at targets.", true);
|
||||
|
||||
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), CombatLookHeightOffset, "Combat Look Height Offset", -3f, 3f);
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the height offset for the default position when looking at targets.", true);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 175906794189e44449125276b4c64b8c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,173 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
|
||||
namespace EmeraldAI.Utility
|
||||
{
|
||||
[CustomEditor(typeof(EmeraldItems))]
|
||||
[CanEditMultipleObjects]
|
||||
public class EmeraldItemsEditor : Editor
|
||||
{
|
||||
GUIStyle FoldoutStyle;
|
||||
EmeraldCombat EmeraldCombat;
|
||||
Texture ItemsEditorIcon;
|
||||
|
||||
#region SerializedProperties
|
||||
//Bools
|
||||
SerializedProperty HideSettingsFoldout, WeaponsFoldout, ItemsFoldout;
|
||||
|
||||
ReorderableList ItemList, Type1EquippableWeaponsList, Type2EquippableWeaponsList;
|
||||
#endregion
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
EmeraldItems self = (EmeraldItems)target;
|
||||
EmeraldCombat = self.GetComponent<EmeraldCombat>();
|
||||
if (ItemsEditorIcon == null) ItemsEditorIcon = Resources.Load("Editor Icons/EmeraldItems") as Texture;
|
||||
|
||||
InitializeProperties();
|
||||
InitializeLists();
|
||||
}
|
||||
|
||||
void InitializeProperties()
|
||||
{
|
||||
//Bools
|
||||
HideSettingsFoldout = serializedObject.FindProperty("HideSettingsFoldout");
|
||||
WeaponsFoldout = serializedObject.FindProperty("WeaponsFoldout");
|
||||
ItemsFoldout = serializedObject.FindProperty("ItemsFoldout");
|
||||
}
|
||||
|
||||
void InitializeLists()
|
||||
{
|
||||
//Type 1
|
||||
Type1EquippableWeaponsList = DrawEquippableWeaponsList(Type1EquippableWeaponsList, "Type1EquippableWeapons", "Type 1 Equippable Weapons");
|
||||
|
||||
//Type 2
|
||||
Type2EquippableWeaponsList = DrawEquippableWeaponsList(Type2EquippableWeaponsList, "Type2EquippableWeapons", "Type 2 Equippable Weapons");
|
||||
|
||||
//Item Objects
|
||||
ItemList = new ReorderableList(serializedObject, serializedObject.FindProperty("ItemList"), true, true, true, true);
|
||||
ItemList.drawElementCallback =
|
||||
(Rect rect, int index, bool isActive, bool isFocused) =>
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var element = ItemList.serializedProperty.GetArrayElementAtIndex(index);
|
||||
EditorGUI.PropertyField(new Rect(rect.x + 60, rect.y, rect.width - 120, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("ItemObject"), GUIContent.none);
|
||||
EditorGUI.PropertyField(new Rect(rect.x, rect.y, 50, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("ItemID"), GUIContent.none);
|
||||
};
|
||||
|
||||
ItemList.drawHeaderCallback = rect =>
|
||||
{
|
||||
EditorGUI.LabelField(rect, " ID " + " Item Object", EditorStyles.boldLabel);
|
||||
};
|
||||
}
|
||||
|
||||
ReorderableList DrawEquippableWeaponsList(ReorderableList WeaponsList, string WeaponsPropertyName, string WeaponsDisplayName)
|
||||
{
|
||||
WeaponsList = new ReorderableList(serializedObject, serializedObject.FindProperty(WeaponsPropertyName), true, true, true, true);
|
||||
WeaponsList.drawElementCallback =
|
||||
(Rect rect, int index, bool isActive, bool isFocused) =>
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var element = WeaponsList.serializedProperty.GetArrayElementAtIndex(index);
|
||||
|
||||
EditorGUI.LabelField(new Rect(rect.x + 10, rect.y, rect.width, EditorGUIUtility.singleLineHeight), "Item " + (index + 1).ToString());
|
||||
|
||||
float Height = (EditorGUIUtility.singleLineHeight * 1.25f);
|
||||
|
||||
EditorGUI.LabelField(new Rect(rect.x + 10, rect.y + Height, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Held", "The Held object is used to reference the position for spawning droppable weapons and for equipping items.")); //Title
|
||||
EditorGUI.PropertyField(new Rect(rect.x + 100, rect.y + Height, rect.width - 100, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("HeldObject"), GUIContent.none); //Object
|
||||
|
||||
EditorGUI.PropertyField(new Rect(rect.x - 12.5f, rect.y + Height * 2, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("HolsteredToggle"), GUIContent.none); //Toggle
|
||||
EditorGUI.LabelField(new Rect(rect.x + 10, rect.y + Height * 2, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Holstered", "The weapon object that's holstered on an AI. This is used when using Equipping Animations." +
|
||||
"\n\nNote: If an Animation Profile does not have any Equipping Animations, this setting will be ignored.")); //Title
|
||||
EditorGUI.BeginDisabledGroup(!element.FindPropertyRelative("HolsteredToggle").boolValue);
|
||||
EditorGUI.PropertyField(new Rect(rect.x + 100, rect.y + Height * 2, rect.width - 100, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("HolsteredObject"), GUIContent.none); //Object
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
EditorGUI.PropertyField(new Rect(rect.x - 12.5f, rect.y + Height * 3, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("DroppableToggle"), GUIContent.none); //Toggle
|
||||
EditorGUI.LabelField(new Rect(rect.x + 10, rect.y + Height * 3, rect.width, EditorGUIUtility.singleLineHeight), new GUIContent("Droppable", "If enabled, this will spawn a copy of the attached object to the exact position and rotation of this item's Held object.")); //Title
|
||||
EditorGUI.BeginDisabledGroup(!element.FindPropertyRelative("DroppableToggle").boolValue);
|
||||
EditorGUI.PropertyField(new Rect(rect.x + 100, rect.y + Height * 3, rect.width - 100, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("DroppableObject"), GUIContent.none); //Object
|
||||
EditorGUI.EndDisabledGroup();
|
||||
};
|
||||
|
||||
WeaponsList.elementHeightCallback = (int index) =>
|
||||
{
|
||||
SerializedProperty element = WeaponsList.serializedProperty.GetArrayElementAtIndex(index);
|
||||
return EditorGUIUtility.singleLineHeight * 5.0f;
|
||||
};
|
||||
|
||||
WeaponsList.drawHeaderCallback = rect =>
|
||||
{
|
||||
EditorGUI.LabelField(rect, WeaponsDisplayName, EditorStyles.boldLabel);
|
||||
};
|
||||
|
||||
return WeaponsList;
|
||||
}
|
||||
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
FoldoutStyle = CustomEditorProperties.UpdateEditorStyles();
|
||||
EmeraldItems self = (EmeraldItems)target;
|
||||
serializedObject.Update();
|
||||
|
||||
CustomEditorProperties.BeginScriptHeaderNew("Items", ItemsEditorIcon, new GUIContent(), HideSettingsFoldout);
|
||||
|
||||
if (!HideSettingsFoldout.boolValue)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
WeaponSettings(self);
|
||||
EditorGUILayout.Space();
|
||||
ItemSettings(self);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
CustomEditorProperties.EndScriptHeader();
|
||||
}
|
||||
|
||||
void WeaponSettings(EmeraldItems self)
|
||||
{
|
||||
WeaponsFoldout.boolValue = EditorGUILayout.Foldout(WeaponsFoldout.boolValue, "Weapon Settings", true, FoldoutStyle);
|
||||
|
||||
if (WeaponsFoldout.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
|
||||
CustomEditorProperties.TextTitleWithDescription("Weapon Settings", "Allows AI to enable and disable weapon objects using Animation Events (Requires AI to have Equip and Unequip Animations).", true);
|
||||
|
||||
Type1EquippableWeaponsList.DoLayoutList();
|
||||
|
||||
if (EmeraldCombat.WeaponTypeAmount == EmeraldCombat.WeaponTypeAmounts.Two)
|
||||
{
|
||||
Type2EquippableWeaponsList.DoLayoutList();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
}
|
||||
}
|
||||
|
||||
void ItemSettings(EmeraldItems self)
|
||||
{
|
||||
ItemsFoldout.boolValue = EditorGUILayout.Foldout(ItemsFoldout.boolValue, "Item Settings", true, FoldoutStyle);
|
||||
|
||||
if (ItemsFoldout.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
|
||||
CustomEditorProperties.TextTitleWithDescription("Item Settings", "Objects that are attached to your AI that can be enabled or disable through Animation Events or programmatically " +
|
||||
"using the item ID. This can be useful for quests items, animation effects, animation specific items, etc. For more information regarding this, please see the Documentation.", true);
|
||||
|
||||
CustomEditorProperties.CustomHelpLabelField("Each Item below has an ID number. This ID is used to find that particular item and to either enable or disable it using Emerald AI's API.", true);
|
||||
ItemList.DoLayoutList();
|
||||
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cbd9ed11b291eb44f9b611fecce1c014
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,134 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Linq;
|
||||
using UnityEditorInternal;
|
||||
|
||||
namespace EmeraldAI.Utility
|
||||
{
|
||||
[CustomEditor(typeof(EmeraldOptimization))]
|
||||
[CanEditMultipleObjects]
|
||||
public class EmeraldOptimizationEditor : Editor
|
||||
{
|
||||
GUIStyle FoldoutStyle;
|
||||
Texture OptimizationEditorIcon;
|
||||
|
||||
#region SerializedProperties
|
||||
//Bool
|
||||
SerializedProperty HideSettingsFoldout, OptimizationFoldout;
|
||||
|
||||
//Int
|
||||
SerializedProperty DeactivateDelayProp;
|
||||
|
||||
//Object
|
||||
SerializedProperty AIRendererProp;
|
||||
|
||||
//Enum
|
||||
SerializedProperty OptimizeAIProp, UseDeactivateDelayProp, TotalLODsProp, MeshTypeProp;
|
||||
#endregion
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (OptimizationEditorIcon == null) OptimizationEditorIcon = Resources.Load("Editor Icons/EmeraldOptimization") as Texture;
|
||||
|
||||
//Bool
|
||||
HideSettingsFoldout = serializedObject.FindProperty("HideSettingsFoldout");
|
||||
OptimizationFoldout = serializedObject.FindProperty("OptimizationFoldout");
|
||||
|
||||
//Int
|
||||
DeactivateDelayProp = serializedObject.FindProperty("DeactivateDelay");
|
||||
|
||||
//Object
|
||||
AIRendererProp = serializedObject.FindProperty("AIRenderer");
|
||||
|
||||
//Enum
|
||||
OptimizeAIProp = serializedObject.FindProperty("OptimizeAI");
|
||||
UseDeactivateDelayProp = serializedObject.FindProperty("UseDeactivateDelay");
|
||||
TotalLODsProp = serializedObject.FindProperty("TotalLODsRef");
|
||||
MeshTypeProp = serializedObject.FindProperty("MeshType");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
FoldoutStyle = CustomEditorProperties.UpdateEditorStyles();
|
||||
EmeraldOptimization self = (EmeraldOptimization)target;
|
||||
serializedObject.Update();
|
||||
|
||||
CustomEditorProperties.BeginScriptHeaderNew("Optimization", OptimizationEditorIcon, new GUIContent(), HideSettingsFoldout);
|
||||
|
||||
MissingRendererMessage(self);
|
||||
|
||||
if (!HideSettingsFoldout.boolValue)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
OptimizationSettings(self);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
CustomEditorProperties.EndScriptHeader();
|
||||
}
|
||||
|
||||
void MissingRendererMessage(EmeraldOptimization self)
|
||||
{
|
||||
if (self.OptimizeAI == YesOrNo.Yes && self.MeshType == EmeraldOptimization.MeshTypes.SingleMesh && !self.AIRenderer)
|
||||
{
|
||||
CustomEditorProperties.DisplayWarningMessage("When using the Single Mesh Type, an AI needs to have an assigned Renderer. Please assign your AI's Skinned Mesh Renderer to the AI Renderer slot within the Optimization Settings foldout.");
|
||||
}
|
||||
}
|
||||
|
||||
void OptimizationSettings (EmeraldOptimization self)
|
||||
{
|
||||
OptimizationFoldout.boolValue = EditorGUILayout.Foldout(OptimizationFoldout.boolValue, "Optimization Settings", true, FoldoutStyle);
|
||||
|
||||
if (OptimizationFoldout.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
|
||||
CustomEditorProperties.TextTitleWithDescription("Optimization Settings", "The optimization component will optimize an AI by disabling certain scripts, functionality, and animations when an AI's model is culled or not visible.", true);
|
||||
|
||||
EditorGUILayout.PropertyField(OptimizeAIProp, new GUIContent("Optimize AI"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls whether or not this AI will be optimized when off screen or culled.", true);
|
||||
|
||||
if (self.OptimizeAI == YesOrNo.Yes)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.Space(10);
|
||||
EditorGUILayout.BeginVertical();
|
||||
|
||||
EditorGUILayout.PropertyField(UseDeactivateDelayProp, new GUIContent("Use Deactivate Delay"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls whether or not there is a delay when using the Disable when Off-Screen feature. If set to No, the AI will be disabled instantly.", true);
|
||||
|
||||
if (self.UseDeactivateDelay == YesOrNo.Yes)
|
||||
{
|
||||
CustomEditorProperties.CustomIntSlider(new Rect(), new GUIContent(), DeactivateDelayProp, "Deactivate Delay", 1, 30);
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the amount of seconds until the AI will be disabled when either culled or off-screen.", true);
|
||||
}
|
||||
|
||||
CustomEditorProperties.CustomPropertyField(MeshTypeProp, "Mesh Type", "Controls whether this AI uses a single Skinned Mesh Renderer or a LOD Group.", true);
|
||||
|
||||
if (self.MeshType == EmeraldOptimization.MeshTypes.LODGroup)
|
||||
{
|
||||
CustomEditorProperties.DisplayImportantMessage("Info - The LOD Group option requires that an AI has a LOD Group component attached with at least 1 LOD Level. Each level needs to have at least 1 mesh assigned. " +
|
||||
"The Optimization Component will automatically grab all needed information from the LOD Group during Start. Ensure your AI meets these requirements or the Optimization Component will be disabled.");
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
if (self.MeshType == EmeraldOptimization.MeshTypes.SingleMesh)
|
||||
{
|
||||
CustomEditorProperties.BeginIndent();
|
||||
EditorGUILayout.PropertyField(AIRendererProp, new GUIContent("AI Main Renderer"));
|
||||
CustomEditorProperties.CustomHelpLabelField("The AI's Main Renderer should be a single Skinned Mesh Renderer an AI uses. If an AI has multiple Skinned Mesh Renderers, an LOD Group should be used instead.", true);
|
||||
CustomEditorProperties.EndIndent();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5f9006483035e445ac1c52e1a814225
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,459 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
|
||||
namespace EmeraldAI.Utility
|
||||
{
|
||||
[CustomEditor(typeof(EmeraldUI))]
|
||||
[CanEditMultipleObjects]
|
||||
public class EmeraldUIEditor : Editor
|
||||
{
|
||||
GUIStyle FoldoutStyle;
|
||||
Texture UIEditorIcon;
|
||||
|
||||
#region SerializedProperties
|
||||
//Ints
|
||||
SerializedProperty MaxUIScaleSizeProp;
|
||||
|
||||
//Enums
|
||||
SerializedProperty CreateHealthBarsProp, UseCustomFontAINameProp, UseCustomFontAILevelProp, CustomizeHealthBarProp, DisplayAINameProp, DisplayAITitleProp, DisplayAILevelProp, UseAINameUIOutlineEffectProp, UseAILevelUIOutlineEffectProp;
|
||||
|
||||
//Bools
|
||||
SerializedProperty HideSettingsFoldout, UISettingsFoldoutProp, HealthBarsFoldoutProp, CombatTextFoldoutProp, NameTextFoldoutProp, LevelTextFoldoutProp;
|
||||
|
||||
//Layermask
|
||||
SerializedProperty UILayerMaskProp;
|
||||
|
||||
//Float
|
||||
SerializedProperty AINameLineSpacingProp;
|
||||
|
||||
//Colors
|
||||
SerializedProperty HealthBarColorProp, HealthBarColorDamageProp, HealthBarBackgroundColorProp, NameTextColorProp, LevelTextColorProp, AINameUIOutlineColorProp, AILevelUIOutlineColorProp, AINameFontProp, AILevelFontProp;
|
||||
|
||||
//Vectors
|
||||
SerializedProperty AINamePosProp, AILevelPosProp, AINameUIOutlineSizeProp, AILevelUIOutlineSizeProp, HealthBarPosProp, NameTextFontSizeProp, HealthBarScaleProp;
|
||||
|
||||
//Objects
|
||||
SerializedProperty HealthBarImageProp, HealthBarBackgroundImageProp;
|
||||
|
||||
//String
|
||||
SerializedProperty UITagProp, CameraTagProp, AINameProp, AITitleProp, AILevelProp;
|
||||
#endregion
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (UIEditorIcon == null) UIEditorIcon = Resources.Load("Editor Icons/EmeraldUI") as Texture;
|
||||
InitializeProperties();
|
||||
}
|
||||
|
||||
void InitializeProperties ()
|
||||
{
|
||||
//Int
|
||||
MaxUIScaleSizeProp = serializedObject.FindProperty("MaxUIScaleSize");
|
||||
|
||||
//Floats
|
||||
AINameLineSpacingProp = serializedObject.FindProperty("AINameLineSpacing");
|
||||
|
||||
//Enums
|
||||
UseAINameUIOutlineEffectProp = serializedObject.FindProperty("UseAINameUIOutlineEffect");
|
||||
UseAILevelUIOutlineEffectProp = serializedObject.FindProperty("UseAILevelUIOutlineEffect");
|
||||
CreateHealthBarsProp = serializedObject.FindProperty("AutoCreateHealthBars");
|
||||
CustomizeHealthBarProp = serializedObject.FindProperty("UseCustomHealthBar");
|
||||
DisplayAINameProp = serializedObject.FindProperty("DisplayAIName");
|
||||
DisplayAITitleProp = serializedObject.FindProperty("DisplayAITitle");
|
||||
DisplayAILevelProp = serializedObject.FindProperty("DisplayAILevel");
|
||||
UseCustomFontAINameProp = serializedObject.FindProperty("UseCustomFontAIName");
|
||||
UseCustomFontAILevelProp = serializedObject.FindProperty("UseCustomFontAILevel");
|
||||
|
||||
//Bools
|
||||
HideSettingsFoldout = serializedObject.FindProperty("HideSettingsFoldout");
|
||||
UISettingsFoldoutProp = serializedObject.FindProperty("UISettingsFoldout");
|
||||
HealthBarsFoldoutProp = serializedObject.FindProperty("HealthBarsFoldout");
|
||||
CombatTextFoldoutProp = serializedObject.FindProperty("CombatTextFoldout");
|
||||
NameTextFoldoutProp = serializedObject.FindProperty("NameTextFoldout");
|
||||
LevelTextFoldoutProp = serializedObject.FindProperty("LevelTextFoldout");
|
||||
|
||||
//Layermask
|
||||
UILayerMaskProp = serializedObject.FindProperty("UILayerMask");
|
||||
|
||||
//Vectors
|
||||
HealthBarPosProp = serializedObject.FindProperty("HealthBarPos");
|
||||
NameTextFontSizeProp = serializedObject.FindProperty("NameTextFontSize");
|
||||
HealthBarScaleProp = serializedObject.FindProperty("HealthBarScale");
|
||||
AINamePosProp = serializedObject.FindProperty("AINamePos");
|
||||
AINameUIOutlineSizeProp = serializedObject.FindProperty("AINameUIOutlineSize");
|
||||
AILevelPosProp = serializedObject.FindProperty("AILevelPos");
|
||||
AILevelUIOutlineSizeProp = serializedObject.FindProperty("AILevelUIOutlineSize");
|
||||
|
||||
//Color
|
||||
HealthBarColorProp = serializedObject.FindProperty("HealthBarColor");
|
||||
HealthBarColorDamageProp = serializedObject.FindProperty("HealthBarDamageColor");
|
||||
HealthBarBackgroundColorProp = serializedObject.FindProperty("HealthBarBackgroundColor");
|
||||
NameTextColorProp = serializedObject.FindProperty("NameTextColor");
|
||||
LevelTextColorProp = serializedObject.FindProperty("LevelTextColor");
|
||||
AINameUIOutlineColorProp = serializedObject.FindProperty("AINameUIOutlineColor");
|
||||
AILevelUIOutlineColorProp = serializedObject.FindProperty("AILevelUIOutlineColor");
|
||||
AINameFontProp = serializedObject.FindProperty("AINameFont");
|
||||
AILevelFontProp = serializedObject.FindProperty("AILevelFont");
|
||||
|
||||
//String
|
||||
UITagProp = serializedObject.FindProperty("UITag");
|
||||
CameraTagProp = serializedObject.FindProperty("CameraTag");
|
||||
AINameProp = serializedObject.FindProperty("AIName");
|
||||
AITitleProp = serializedObject.FindProperty("AITitle");
|
||||
AILevelProp = serializedObject.FindProperty("AILevel");
|
||||
|
||||
//Objects
|
||||
HealthBarImageProp = serializedObject.FindProperty("HealthBarImage");
|
||||
HealthBarBackgroundImageProp = serializedObject.FindProperty("HealthBarBackgroundImage");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
FoldoutStyle = CustomEditorProperties.UpdateEditorStyles();
|
||||
EmeraldUI self = (EmeraldUI)target;
|
||||
serializedObject.Update();
|
||||
|
||||
CustomEditorProperties.BeginScriptHeaderNew("UI", UIEditorIcon, new GUIContent(), HideSettingsFoldout);
|
||||
|
||||
if (!HideSettingsFoldout.boolValue)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
UISettings(self);
|
||||
EditorGUILayout.Space();
|
||||
NameTextSettings(self);
|
||||
EditorGUILayout.Space();
|
||||
LevelTextSettings(self);
|
||||
EditorGUILayout.Space();
|
||||
HealthbarSettings(self);
|
||||
EditorGUILayout.Space();
|
||||
CombatTextSettings(self);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
CustomEditorProperties.EndScriptHeader();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
void UISettings (EmeraldUI self)
|
||||
{
|
||||
UISettingsFoldoutProp.boolValue = CustomEditorProperties.Foldout(UISettingsFoldoutProp.boolValue, "UI Setup", true, FoldoutStyle);
|
||||
|
||||
if (UISettingsFoldoutProp.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
|
||||
CustomEditorProperties.TextTitleWithDescription("UI Setup", "Controls the use and setup of Emerald's built-in UI. In order for the UI to be visible, a player of the appropriate tag must enter an AI's trigger radius. " +
|
||||
"You can set an AI's UI Tag under the Detection and Tag tab.", true);
|
||||
|
||||
GUI.backgroundColor = new Color(1f, 1, 0.25f, 0.25f);
|
||||
EditorGUILayout.LabelField("In order for the UI system to work correctly, you will need to assign a Tag and Layer. This is typically your Player's Tag and Layer. " +
|
||||
"This is used to make the UI system more efficient by only running when the appropriate objects are detected. You will also need to apply your player's camera Tag so the UI can be properly positioned.", EditorStyles.helpBox);
|
||||
GUI.backgroundColor = Color.white;
|
||||
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.CustomTagField(new Rect(), new GUIContent(), CameraTagProp, "Camera Tag");
|
||||
CustomEditorProperties.CustomHelpLabelField("The Camera Tag is the Unity Tag that your player uses. The Camera is needed to properly position the UI.", true);
|
||||
|
||||
CustomEditorProperties.CustomTagField(new Rect(), new GUIContent(), UITagProp, "UI Tag");
|
||||
CustomEditorProperties.CustomHelpLabelField("The UI Tag is the Unity Tag that will trigger the AI's UI, when enabled.", true);
|
||||
|
||||
EditorGUILayout.PropertyField(UILayerMaskProp, new GUIContent("UI Layers"));
|
||||
CustomEditorProperties.CustomHelpLabelField("The UI Layers controls what layers this AI will detect to enable the their UI, if the object also has the appropriate UI Tag. This is typically used for players.", false);
|
||||
|
||||
if (UILayerMaskProp.intValue == 0 || UILayerMaskProp.intValue == 1)
|
||||
{
|
||||
GUI.backgroundColor = new Color(10f, 0.0f, 0.0f, 0.25f);
|
||||
EditorGUILayout.LabelField("The UI Layers cannot contain Nothing, Default, or Everything.", EditorStyles.helpBox);
|
||||
GUI.backgroundColor = Color.white;
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.PropertyField(MaxUIScaleSizeProp, new GUIContent("Max UI Scale"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the max size the UI will be scaled when the player is getting further away from an AI's UI.", true);
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
}
|
||||
}
|
||||
|
||||
void NameTextSettings (EmeraldUI self)
|
||||
{
|
||||
NameTextFoldoutProp.boolValue = CustomEditorProperties.Foldout(NameTextFoldoutProp.boolValue, "Name Text Settings", true, FoldoutStyle);
|
||||
|
||||
if (NameTextFoldoutProp.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
CustomEditorProperties.TextTitleWithDescription("Name Text Settings", "Settings for displaying and positioning this AI's Name using Emerald AI's built-in (Unity-based) UI System.", true);
|
||||
|
||||
EditorGUILayout.PropertyField(DisplayAINameProp, new GUIContent("Display AI Name"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Enables or disables the display of the AI's name. When enabled, the AI's name will be visible above its health bar.", true);
|
||||
|
||||
if (self.DisplayAIName == YesOrNo.Yes)
|
||||
{
|
||||
CustomEditorProperties.BeginIndent();
|
||||
|
||||
EditorGUILayout.PropertyField(AINameProp, new GUIContent("AI's Name"));
|
||||
CustomEditorProperties.CustomHelpLabelField("The name of the AI. This can be displayed with Emerald's built-in UI system or a custom one.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(UseCustomFontAINameProp, new GUIContent("Use Custom Name Font"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls whether or not the Name Text font can be customized.", false);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (self.UseCustomFontAIName == YesOrNo.Yes)
|
||||
{
|
||||
CustomEditorProperties.BeginIndent();
|
||||
EditorGUILayout.PropertyField(AINameFontProp, new GUIContent("Name Font"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the font of the AI's Name Text.", true);
|
||||
CustomEditorProperties.EndIndent();
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(AINamePosProp, new GUIContent("AI Name Position"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the position of the AI's name text.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(NameTextFontSizeProp, new GUIContent("AI Name Font Size"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the size of the AI's name text.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(NameTextColorProp, new GUIContent("AI Name Color"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the color of the AI's name text.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(UseAINameUIOutlineEffectProp, new GUIContent("Use Outline on Name Text"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls whether or not the AI's Name UI will use an Outline Effect.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (self.UseAINameUIOutlineEffect == YesOrNo.Yes)
|
||||
{
|
||||
CustomEditorProperties.BeginIndent();
|
||||
|
||||
EditorGUILayout.PropertyField(AINameUIOutlineColorProp, new GUIContent("Name Text Outline Color"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the color of the AI's Name Text Outline.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(AINameUIOutlineSizeProp, new GUIContent("Name Text Outline Size"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the size of the AI's Name Text Outline.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
CustomEditorProperties.EndIndent();
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(DisplayAITitleProp, new GUIContent("Display AI Title"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Enables or disables the display of the AI's title. When enabled, the AI's title will be visible above its health bar.", false);
|
||||
|
||||
if (self.DisplayAITitle == YesOrNo.Yes)
|
||||
{
|
||||
CustomEditorProperties.BeginIndent();
|
||||
|
||||
EditorGUILayout.PropertyField(AITitleProp, new GUIContent("AI's Title"));
|
||||
CustomEditorProperties.CustomHelpLabelField("The title of the AI. This can be displayed with Emerald's built-in UI system or a custom one.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(AINameLineSpacingProp, new GUIContent("Name Line Spacing"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the line spacing between the AI's Name and the AI's Title.", true);
|
||||
|
||||
CustomEditorProperties.EndIndent();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.EndIndent();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
}
|
||||
}
|
||||
|
||||
void LevelTextSettings(EmeraldUI self)
|
||||
{
|
||||
LevelTextFoldoutProp.boolValue = CustomEditorProperties.Foldout(LevelTextFoldoutProp.boolValue, "Level Text Settings", true, FoldoutStyle);
|
||||
|
||||
if (LevelTextFoldoutProp.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
CustomEditorProperties.TextTitleWithDescription("Name Text Settings", "Settings for displaying and positioning this AI's Level using Emerald AI's built-in (Unity-based) UI System.", true);
|
||||
|
||||
//TODO: Remove this limitation?
|
||||
/*
|
||||
if (self.AutoCreateHealthBars == YesOrNo.No)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.Space(10);
|
||||
EditorGUILayout.BeginVertical();
|
||||
GUI.backgroundColor = new Color(10f, 0.0f, 0.0f, 0.25f);
|
||||
EditorGUILayout.LabelField("You must have Auto Create Health Bars enabled to use this feature.", EditorStyles.helpBox);
|
||||
GUI.backgroundColor = Color.white;
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
*/
|
||||
EditorGUILayout.PropertyField(DisplayAILevelProp, new GUIContent("Display AI Level"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Enables or disables the display of the AI's level. When enabled, the AI's level will be visible to the left of its health bar.", true);
|
||||
|
||||
if (self.DisplayAILevel == YesOrNo.Yes)
|
||||
{
|
||||
CustomEditorProperties.BeginIndent();
|
||||
|
||||
CustomEditorProperties.CustomIntField(new Rect(), new GUIContent(), AILevelProp, "AI's Level");
|
||||
CustomEditorProperties.CustomHelpLabelField("The level of the AI. This can be displayed with Emerald's built-in UI system or a custom one.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(UseCustomFontAILevelProp, new GUIContent("Use Custom Level Font"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls whether or not the Level Text font can be customized.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (self.UseCustomFontAILevel == YesOrNo.Yes)
|
||||
{
|
||||
EditorGUILayout.PropertyField(AILevelFontProp, new GUIContent("Level Font"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the font of the AI's Level Text.", true);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(AILevelPosProp, new GUIContent("AI Level Position"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the position of the AI's level text.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(LevelTextColorProp, new GUIContent("Level Color"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the color of the AI's Level Text.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(UseAILevelUIOutlineEffectProp, new GUIContent("Use Outline on Level Text"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls whether or not the AI's Level UI will use an Outline Effect.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (self.UseAILevelUIOutlineEffect == YesOrNo.Yes)
|
||||
{
|
||||
CustomEditorProperties.BeginIndent();
|
||||
|
||||
EditorGUILayout.PropertyField(AILevelUIOutlineColorProp, new GUIContent("Level Text Outline Color"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the color of the AI's Level Text Outline.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(AILevelUIOutlineSizeProp, new GUIContent("Level Text Outline Size"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the size of the AI's Level Text Outline.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
CustomEditorProperties.EndIndent();
|
||||
}
|
||||
|
||||
CustomEditorProperties.EndIndent();
|
||||
}
|
||||
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
}
|
||||
}
|
||||
|
||||
void HealthbarSettings (EmeraldUI self)
|
||||
{
|
||||
HealthBarsFoldoutProp.boolValue = CustomEditorProperties.Foldout(HealthBarsFoldoutProp.boolValue, "Health Bar Settings", true, FoldoutStyle);
|
||||
|
||||
if (HealthBarsFoldoutProp.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
CustomEditorProperties.TextTitleWithDescription("Health Bar Settings", "Settings for displaying and positioning this AI's Health Bar using Emerald AI's built-in (Unity-based) UI System.", true);
|
||||
|
||||
EditorGUILayout.PropertyField(CreateHealthBarsProp, new GUIContent("Auto Create Health Bars"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Enables or disables the use of Emerald automatically creating health bars for your AI. Enabling this will open up additional settings.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (self.AutoCreateHealthBars == YesOrNo.Yes)
|
||||
{
|
||||
CustomEditorProperties.BeginIndent();
|
||||
|
||||
EditorGUILayout.PropertyField(HealthBarPosProp, new GUIContent("Health Bar Position"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the starting position of the AI's created health bar.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(HealthBarScaleProp, new GUIContent("Health Bar Scale"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the scale of the AI's created health bar.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(HealthBarColorProp, new GUIContent("Health Bar Color"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the color of the AI's health bar.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(HealthBarColorDamageProp, new GUIContent("Health Bar Damage Color"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the color of the AI's health bar when damaged.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(HealthBarBackgroundColorProp, new GUIContent("Background Color"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the background color of the AI's health bar.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(CustomizeHealthBarProp, new GUIContent("Use Custom Health Bar"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Allows you to use custom sprites for the AI's health bar.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (self.UseCustomHealthBar == YesOrNo.Yes)
|
||||
{
|
||||
EditorGUILayout.LabelField("Health Bar Sprites", EditorStyles.boldLabel);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
CustomEditorProperties.CustomObjectField(new Rect(), new GUIContent(), HealthBarImageProp, "Bar", typeof(Sprite), true);
|
||||
CustomEditorProperties.CustomHelpLabelField("Customizes the health bar sprite for the AI's health bar.", true);
|
||||
|
||||
CustomEditorProperties.CustomObjectField(new Rect(), new GUIContent(), HealthBarBackgroundImageProp, "Bar Background", typeof(Sprite), true);
|
||||
CustomEditorProperties.CustomHelpLabelField("Customizes the health bar's background sprite for the AI's health bar.", true);
|
||||
}
|
||||
|
||||
CustomEditorProperties.EndIndent();
|
||||
}
|
||||
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
}
|
||||
}
|
||||
|
||||
void CombatTextSettings (EmeraldUI self)
|
||||
{
|
||||
CombatTextFoldoutProp.boolValue = CustomEditorProperties.Foldout(CombatTextFoldoutProp.boolValue, "Combat Text Settings", true, FoldoutStyle);
|
||||
|
||||
if (CombatTextFoldoutProp.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
CustomEditorProperties.TextTitleWithDescription("Combat Text Settings", "A shortcut to Emerald AI's built-in (global) Combat Text System.", true);
|
||||
|
||||
CustomEditorProperties.CustomHelpLabelField("The Combat Text System can be adjusted through the Combat Text Manager. These settings are applied globally.", false);
|
||||
var ButtonStyle = new GUIStyle(GUI.skin.button);
|
||||
if (GUILayout.Button("Open Combat Text Manager", ButtonStyle))
|
||||
{
|
||||
EditorWindow CTM = EditorWindow.GetWindow(typeof(EmeraldCombatTextManager), true, "Combat Text Manager");
|
||||
CTM.minSize = new Vector2(600f, 725f);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSceneGUI()
|
||||
{
|
||||
EmeraldUI self = (EmeraldUI)target;
|
||||
DrawUIPositions(self);
|
||||
}
|
||||
|
||||
void DrawUIPositions(EmeraldUI self)
|
||||
{
|
||||
if (self == null) return;
|
||||
|
||||
if (self.DisplayAIName == YesOrNo.Yes && self.NameTextFoldout)
|
||||
{
|
||||
Handles.color = self.NameTextColor;
|
||||
Handles.DrawLine(new Vector3(self.transform.localPosition.x, self.transform.localPosition.y, self.transform.localPosition.z),
|
||||
new Vector3(self.AINamePos.x, self.AINamePos.y, self.AINamePos.z) + self.transform.localPosition);
|
||||
Handles.color = Color.white;
|
||||
}
|
||||
|
||||
if (self.AutoCreateHealthBars == YesOrNo.Yes && self.HealthBarsFoldout)
|
||||
{
|
||||
Handles.color = self.HealthBarColor;
|
||||
Handles.DrawLine(new Vector3(self.transform.localPosition.x + 0.25f, self.transform.localPosition.y, self.transform.localPosition.z),
|
||||
new Vector3(self.HealthBarPos.x + 0.25f, self.HealthBarPos.y, self.HealthBarPos.z) + self.transform.localPosition);
|
||||
Handles.color = Color.white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce109207489d6094cb34ad269047b4d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
|
||||
namespace EmeraldAI.Utility
|
||||
{
|
||||
[CustomEditor(typeof(EmeraldWeaponCollision))]
|
||||
[CanEditMultipleObjects]
|
||||
public class EmeraldWeaponCollisionEditor : Editor
|
||||
{
|
||||
GUIStyle FoldoutStyle;
|
||||
Texture WeaponCollisionEditorIcon;
|
||||
SerializedProperty CollisionBoxColor, HideSettingsFoldout, WeaponCollisionFoldout;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (WeaponCollisionEditorIcon == null) WeaponCollisionEditorIcon = Resources.Load("Editor Icons/EmeraldWeaponCollision") as Texture;
|
||||
EmeraldWeaponCollision self = (EmeraldWeaponCollision)target;
|
||||
self.WeaponCollider = self.GetComponent<BoxCollider>();
|
||||
CollisionBoxColor = serializedObject.FindProperty("CollisionBoxColor");
|
||||
HideSettingsFoldout = serializedObject.FindProperty("HideSettingsFoldout");
|
||||
WeaponCollisionFoldout = serializedObject.FindProperty("WeaponCollisionFoldout");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
FoldoutStyle = CustomEditorProperties.UpdateEditorStyles();
|
||||
serializedObject.Update();
|
||||
|
||||
CustomEditorProperties.BeginScriptHeaderNew("Weapon Collision", WeaponCollisionEditorIcon, new GUIContent(), HideSettingsFoldout);
|
||||
|
||||
if (!HideSettingsFoldout.boolValue)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
WeaponCollisionSettings();
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
CustomEditorProperties.EndScriptHeader();
|
||||
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
void WeaponCollisionSettings ()
|
||||
{
|
||||
WeaponCollisionFoldout.boolValue = EditorGUILayout.Foldout(WeaponCollisionFoldout.boolValue, "Weapon Collision Settings", true, FoldoutStyle);
|
||||
|
||||
if (WeaponCollisionFoldout.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
CustomEditorProperties.TextTitleWithDescription("Weapon Collision Settings", "Adjust the size of the Box Collider (using the Box Collider component) to position the collider to an AI's weapon. The Weapon Collision is intended for melee related attacks. In order for " +
|
||||
"the Weapon Collision component to work, it needs to be enabled through an Animation Event.", true);
|
||||
|
||||
EditorGUILayout.PropertyField(CollisionBoxColor, new GUIContent("Collision Box Color"));
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the color of the Collision Box.", true);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 62077336c47d2a744a0d815447c7b80d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,214 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using System.Linq;
|
||||
|
||||
namespace EmeraldAI.Utility
|
||||
{
|
||||
[System.Serializable]
|
||||
[CustomEditor(typeof(LocationBasedDamage))]
|
||||
[CanEditMultipleObjects]
|
||||
public class LocationBasedDamageEditor : Editor
|
||||
{
|
||||
GUIStyle FoldoutStyle;
|
||||
Texture LBDEditorIcon;
|
||||
ReorderableList ColliderList;
|
||||
string ColliderListState;
|
||||
SerializedProperty HideSettingsFoldout, LBDSettingsFoldout, LBDComponentsTag, SetCollidersLayerAndTag;
|
||||
List<string> layers = new List<string>();
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (LBDEditorIcon == null) LBDEditorIcon = Resources.Load("Editor Icons/EmeraldLBD") as Texture;
|
||||
HideSettingsFoldout = serializedObject.FindProperty("HideSettingsFoldout");
|
||||
LBDSettingsFoldout = serializedObject.FindProperty("LBDSettingsFoldout");
|
||||
LBDComponentsTag = serializedObject.FindProperty("LBDComponentsTag");
|
||||
SetCollidersLayerAndTag = serializedObject.FindProperty("SetCollidersLayerAndTag");
|
||||
InitializeList();
|
||||
InitializeLayers();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all the layers in the project so the user can choose one to apply to their LBD components.
|
||||
/// This layer will also be added to a static layermask and automatically added to every AI's obstruction detection layermask.
|
||||
/// This is needed to stop the LBD components from causing obstructions. Doing it globally allows all AI to receive the change automatically
|
||||
/// so users don't have to manually track or set layers for every AI in their project.
|
||||
/// </summary>
|
||||
void InitializeLayers()
|
||||
{
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
if (LayerMask.LayerToName(i) != "")
|
||||
layers.Add(LayerMask.LayerToName(i));
|
||||
else
|
||||
layers.Add("Empty");
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeList ()
|
||||
{
|
||||
//Label Style
|
||||
var LabelStyle = new GUIStyle();
|
||||
LabelStyle.fontStyle = FontStyle.Bold;
|
||||
LabelStyle.active.textColor = Color.white;
|
||||
LabelStyle.normal.textColor = Color.white;
|
||||
|
||||
ColliderList = new ReorderableList(serializedObject, serializedObject.FindProperty("ColliderList"), false, true, false, true);
|
||||
ColliderList.drawHeaderCallback = rect =>
|
||||
{
|
||||
EditorGUI.LabelField(rect, "Collider List", EditorStyles.boldLabel);
|
||||
};
|
||||
ColliderList.drawElementCallback =
|
||||
(Rect rect, int index, bool isActive, bool isFocused) =>
|
||||
{
|
||||
var element = ColliderList.serializedProperty.GetArrayElementAtIndex(index);
|
||||
ColliderList.elementHeight = EditorGUIUtility.singleLineHeight * 2.5f;
|
||||
|
||||
//Label
|
||||
if (element.FindPropertyRelative("ColliderObject").objectReferenceValue != null)
|
||||
{
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x + 120, rect.y, rect.width - 70, EditorGUIUtility.singleLineHeight),
|
||||
new GUIContent(element.FindPropertyRelative("ColliderObject").objectReferenceValue.name), LabelStyle);
|
||||
|
||||
//Select Button
|
||||
if (GUI.Button(new Rect(rect.x, rect.y, 110, EditorGUIUtility.singleLineHeight), "Select Collider"))
|
||||
{
|
||||
Selection.activeObject = element.FindPropertyRelative("ColliderObject").objectReferenceValue;
|
||||
}
|
||||
|
||||
//Multiplier
|
||||
element.FindPropertyRelative("DamageMultiplier").floatValue = EditorGUI.Slider(new Rect(rect.x, rect.y + EditorGUIUtility.singleLineHeight, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("ColliderObject").objectReferenceValue.name + " Multiplier", element.FindPropertyRelative("DamageMultiplier").floatValue, 0, 25);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.color = Color.red;
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x + 120, rect.y, rect.width - 120, EditorGUIUtility.singleLineHeight), new GUIContent("Null - Please Remove"), LabelStyle);
|
||||
GUILayout.FlexibleSpace();
|
||||
GUI.color = Color.white;
|
||||
|
||||
GUI.contentColor = Color.red;
|
||||
//Select Button
|
||||
if (GUI.Button(new Rect(rect.x, rect.y, 110, EditorGUIUtility.singleLineHeight), "Remove"))
|
||||
{
|
||||
LocationBasedDamage self = (LocationBasedDamage)target;
|
||||
self.ColliderList.RemoveAt(index);
|
||||
}
|
||||
GUI.contentColor = Color.white;
|
||||
|
||||
EditorGUI.BeginDisabledGroup(true);
|
||||
element.FindPropertyRelative("DamageMultiplier").floatValue = EditorGUI.Slider(new Rect(rect.x, rect.y + EditorGUIUtility.singleLineHeight, rect.width, EditorGUIUtility.singleLineHeight), "Null" + " Multiplier", element.FindPropertyRelative("DamageMultiplier").floatValue, 0, 25);
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
FoldoutStyle = CustomEditorProperties.UpdateEditorStyles();
|
||||
LocationBasedDamage self = (LocationBasedDamage)target;
|
||||
serializedObject.Update();
|
||||
|
||||
CustomEditorProperties.BeginScriptHeaderNew("Location Based Damage", LBDEditorIcon, new GUIContent(), HideSettingsFoldout);
|
||||
|
||||
if (!HideSettingsFoldout.boolValue)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
LBDSettings(self);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
CustomEditorProperties.EndScriptHeader();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
void LBDSettings (LocationBasedDamage self)
|
||||
{
|
||||
LBDSettingsFoldout.boolValue = EditorGUILayout.Foldout(LBDSettingsFoldout.boolValue, "Location Based Damage Settings", true, FoldoutStyle);
|
||||
|
||||
if (LBDSettingsFoldout.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
CustomEditorProperties.TextTitleWithDescription("Location Based Damage Settings", "The Location Based Damage component allows each collider to detect damage and apply a customizable damage multiplier based on the damage receieved. The hit effect that will play upon impact, " +
|
||||
"and at the position the hit is detected, is based off of the AI's Hit Effects List (Located under AI's Settings>Combat>Hit Effect)", false);
|
||||
|
||||
CustomEditorProperties.TutorialButton("For a tutorial on using the Location Based Damage component, please see the tutorial below. Note: The LBD uses different code to damage an AI.",
|
||||
"https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-optional/location-based-damage-component");
|
||||
EditorGUILayout.Space();
|
||||
|
||||
CustomEditorProperties.CustomPropertyField(SetCollidersLayerAndTag, "Set Colliders Layer and Tag", "Controls whether or not the Location Based Dmaage component will set the detected collider's tag and layer. Users can customize the tag and layer by enabling this setting.", true);
|
||||
|
||||
if (SetCollidersLayerAndTag.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginIndent();
|
||||
CustomEditorProperties.FactionListEnum(new Rect(), GUIContent.none, serializedObject.FindProperty("LBDComponentsLayer"), "Collider Layer", layers);
|
||||
CustomEditorProperties.CustomHelpLabelField("Sets the layer of the Location Based Damage colliders. This is layer will automatically be added to every AI's Obstruction Detection Layermask so the components will not obstruct their line of sight." +
|
||||
" It is very important that this layer is not your targets' layers and that it is not Default.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
CustomEditorProperties.CustomTagField(new Rect(), new GUIContent(), LBDComponentsTag, "Collider Tag");
|
||||
CustomEditorProperties.CustomHelpLabelField("Sets the tag of the Location Based Damage colliders. It is recommended that the tag is set to something other than Untagged.", true);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
CustomEditorProperties.FactionListEnum(new Rect(), GUIContent.none, serializedObject.FindProperty("DeadLBDComponentsLayer"), "Dead Collider Layer", layers);
|
||||
CustomEditorProperties.CustomHelpLabelField("Sets the layer of the Location Based Damage colliders when an AI dies.", true);
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.EndIndent();
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Space(-15);
|
||||
CustomEditorProperties.DisplayWarningMessage("It is recommended that this setting is kept on, unless you are handling the colliders' layer and tag manually. The layer should not be set to Default. Not properly managing this can result in detection issues");
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.LabelField("Gets all colliders within the AI and applies Location Based Damage Area components to them.", EditorStyles.helpBox);
|
||||
if (GUILayout.Button("Get Colliders"))
|
||||
{
|
||||
var m_Colliders = self.GetComponentsInChildren<Collider>();
|
||||
|
||||
foreach (Collider C in m_Colliders)
|
||||
{
|
||||
if (C != null && C.gameObject != self.gameObject)
|
||||
{
|
||||
if (!self.ColliderList.Exists(x => x.ColliderObject == C))
|
||||
{
|
||||
LocationBasedDamage.LocationBasedDamageClass lbdc = new LocationBasedDamage.LocationBasedDamageClass(C, 1);
|
||||
self.ColliderList.Add(lbdc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
if (self.ColliderList.Count == 0)
|
||||
{
|
||||
Debug.Log("There are no colliders within this AI. Please ensure that you have setup the AI with Unity's Ragdoll Wizard or a 3rd party ragdoll tool.");
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Clear Colliders") && EditorUtility.DisplayDialog("Clear Collider List?", "Are you sure you want to clear the AI's Collider List? This process cannot be undone.", "Clear", "Do Not Clear"))
|
||||
{
|
||||
self.ColliderList.Clear();
|
||||
serializedObject.Update();
|
||||
}
|
||||
|
||||
EditorGUILayout.HelpBox("You can remove an undesired collider by selecting the collider within the Collider List and pressing the - button on the bottom of the Collider List area.", MessageType.Info);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
ColliderList.DoLayoutList();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9826dfeb5581fd247993159fbf2d604d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,94 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace EmeraldAI.Utility
|
||||
{
|
||||
[CustomEditor(typeof(TargetPositionModifier))]
|
||||
[CanEditMultipleObjects]
|
||||
[System.Serializable]
|
||||
public class TargetPositionModifierEditor : Editor
|
||||
{
|
||||
GUIStyle FoldoutStyle;
|
||||
Texture TPMEditorIcon;
|
||||
SerializedProperty PositionModifierProp, TransformSourceProp, GizmoRadiusProp, GizmoColorProp, TPMSettingsFoldout, HideSettingsFoldout;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
InitializeProperties();
|
||||
}
|
||||
|
||||
void InitializeProperties()
|
||||
{
|
||||
if (TPMEditorIcon == null) TPMEditorIcon = Resources.Load("Editor Icons/EmeraldTPM") as Texture;
|
||||
PositionModifierProp = serializedObject.FindProperty("PositionModifier");
|
||||
TransformSourceProp = serializedObject.FindProperty("TransformSource");
|
||||
GizmoRadiusProp = serializedObject.FindProperty("GizmoRadius");
|
||||
GizmoColorProp = serializedObject.FindProperty("GizmoColor");
|
||||
TPMSettingsFoldout = serializedObject.FindProperty("TPMSettingsFoldout");
|
||||
HideSettingsFoldout = serializedObject.FindProperty("HideSettingsFoldout");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
FoldoutStyle = CustomEditorProperties.UpdateEditorStyles();
|
||||
serializedObject.Update();
|
||||
|
||||
if (TransformSourceProp.objectReferenceValue == null)
|
||||
{
|
||||
CustomEditorProperties.DisplaySetupWarning("A Transform Source is required when using the Transform Position Source. Please assign one in order to use the Target Position Modifier.");
|
||||
}
|
||||
|
||||
CustomEditorProperties.BeginScriptHeaderNew("Target Position Modifier", TPMEditorIcon, new GUIContent(), HideSettingsFoldout);
|
||||
|
||||
if (!HideSettingsFoldout.boolValue)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
TPMSettings();
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
CustomEditorProperties.EndScriptHeader();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
void TPMSettings ()
|
||||
{
|
||||
TPMSettingsFoldout.boolValue = EditorGUILayout.Foldout(TPMSettingsFoldout.boolValue, "Target Position Modifier Settings", true, FoldoutStyle);
|
||||
|
||||
if (TPMSettingsFoldout.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
CustomEditorProperties.TextTitleWithDescription("Target Position Modifier Settings", "This system modifies the target height of targets allowing AI agents to target the sphere gizmo shown.", false);
|
||||
|
||||
CustomEditorProperties.NoticeTextDescription("Enusre that the gizmo does not go into the ground or this could make a target undetectable to AI. If you can't see the gizmo, ensure that Unity' Gizmos are enabled.", false);
|
||||
|
||||
CustomEditorProperties.TutorialButton("For a detailed tutorial on using the Target Position Modifier, please see the tutorial below.", "https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-optional/target-position-modifier-component");
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (TransformSourceProp.objectReferenceValue == null)
|
||||
{
|
||||
GUI.backgroundColor = new Color(10f, 0.0f, 0.0f, 0.25f);
|
||||
EditorGUILayout.LabelField("This field cannot be left blank", EditorStyles.helpBox);
|
||||
GUI.backgroundColor = Color.white;
|
||||
}
|
||||
EditorGUILayout.PropertyField(TransformSourceProp, new GUIContent("Transform Source"));
|
||||
CustomEditorProperties.CustomHelpLabelField("The Transform Source should be the transform you want the Target Position Modifier based off of. It is recommended that this is a bone transform that is close the the center of your AI such as its chest or spine.", true);
|
||||
|
||||
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), PositionModifierProp, "Height Modifier", -5, 5);
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the height offset of the position modifier.", true);
|
||||
|
||||
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), GizmoRadiusProp, "Gizmo Radius", 0.05f, 2.5f);
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the radius of the sphere gizmo.", true);
|
||||
|
||||
CustomEditorProperties.CustomColorField(new Rect(), new GUIContent(), GizmoColorProp, "Gizmo Color");
|
||||
CustomEditorProperties.CustomHelpLabelField("Controls the color of the sphere gizmo.", true);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 13b7c111a75a9254596e0a4cc07d4b3a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,222 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[HelpURL("https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-optional/debugger-component")]
|
||||
public class EmeraldDebugger : MonoBehaviour
|
||||
{
|
||||
#region Debugger Variables
|
||||
public YesOrNo EnableDebuggingTools = YesOrNo.Yes;
|
||||
public YesOrNo DrawLineOfSightLines = YesOrNo.Yes;
|
||||
public YesOrNo DrawNavMeshPath = YesOrNo.Yes;
|
||||
public Color NavMeshPathColor = Color.blue;
|
||||
public YesOrNo DrawNavMeshDestination = YesOrNo.Yes;
|
||||
public Color NavMeshDestinationColor = Color.red;
|
||||
public YesOrNo DrawLookAtPoints = YesOrNo.Yes;
|
||||
public YesOrNo DrawUndetectedTargetsLine = YesOrNo.Yes;
|
||||
public YesOrNo DebugLogTargets = YesOrNo.Yes;
|
||||
public YesOrNo DebugLogObstructions = YesOrNo.Yes;
|
||||
|
||||
public YesOrNo DrawFootstepPositions = YesOrNo.Yes;
|
||||
public YesOrNo DebugLogFootsteps = YesOrNo.Yes;
|
||||
#endregion
|
||||
|
||||
#region Private Variables
|
||||
EmeraldSystem EmeraldComponent;
|
||||
EmeraldInverseKinematics IKComponent;
|
||||
Color DebugLineColor = Color.green;
|
||||
Vector3 TargetDirection;
|
||||
Transform DestinationObject;
|
||||
#endregion
|
||||
|
||||
#region Editor Variables
|
||||
public bool HideSettingsFoldout;
|
||||
public bool SettingsFoldout;
|
||||
#endregion
|
||||
|
||||
void Start()
|
||||
{
|
||||
InitializeDebugger();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the Debugger Component.
|
||||
/// </summary>
|
||||
void InitializeDebugger ()
|
||||
{
|
||||
EmeraldComponent = GetComponent<EmeraldSystem>();
|
||||
IKComponent = GetComponent<EmeraldInverseKinematics>();
|
||||
EmeraldComponent.DetectionComponent.OnEnemyTargetDetected += DebugDetectedEnemyTarget; //Subscribe to the OnEnemyTargetDetected delegate for DebugDetectedEnemyTarget
|
||||
EmeraldComponent.DetectionComponent.OnPlayerDetected += DebugDetectedPlayerTarget; //Subscribe to the OnEnemyTargetDetected delegate for DebugDetectedPlayerTarget
|
||||
}
|
||||
|
||||
public void DebuggerUpdate()
|
||||
{
|
||||
if (!enabled) return;
|
||||
|
||||
DebugObstructions();
|
||||
DrawTargetRaycastLines();
|
||||
DrawUndetectedTargetsLineInternal();
|
||||
DrawNavMeshPathInternal();
|
||||
DrawNavMeshDestinationInternal();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Debug logs a message to the Unity Console for testing purposes.
|
||||
/// </summary>
|
||||
public void DebugLogMessage(string Message)
|
||||
{
|
||||
Debug.Log(Message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Debug logs an AI's current obstruction to the Untiy Console.
|
||||
/// </summary>
|
||||
void DebugObstructions()
|
||||
{
|
||||
if (EnableDebuggingTools == YesOrNo.No || DebugLogObstructions == YesOrNo.No || !EmeraldComponent.CombatTarget && !EmeraldComponent.LookAtTarget) return;
|
||||
|
||||
Transform CurrentObstruction = EmeraldComponent.DetectionComponent.CurrentObstruction;
|
||||
|
||||
if (!EmeraldComponent.CombatComponent.DeathDelayActive && EmeraldComponent.CombatTarget && EmeraldComponent.CombatTarget.localScale != Vector3.one * 0.003f && CurrentObstruction)
|
||||
{
|
||||
Debug.Log("<b>" + "<color=green>" + gameObject.name + " - Current Obstruction: " + "</color>" + "<color=red>" + CurrentObstruction.name + "</color>" + "</b>");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Debug logs detected enemy target's information to the Untiy Console.
|
||||
/// </summary>
|
||||
void DebugDetectedEnemyTarget()
|
||||
{
|
||||
if (EnableDebuggingTools == YesOrNo.No || DebugLogTargets == YesOrNo.No) return;
|
||||
|
||||
if (!EmeraldComponent.CombatComponent.DeathDelayActive)
|
||||
{
|
||||
if (EmeraldComponent.CombatTarget != null)
|
||||
{
|
||||
Debug.Log("<b>" + "<color=green>" + gameObject.name + " - Current Combat Target: " + "</color>" + "<color=red>" + EmeraldComponent.CombatTarget.gameObject.name + "</color>" + "</b>" + " |" +
|
||||
"<b>" + "<color=green>" + " Relation Type: " + "</color>" + "<color=red>" + EmeraldComponent.DetectionComponent.GetTargetFactionRelation(EmeraldComponent.CombatTarget) + "</color>" + "</b>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Debug logs detected player target's information to the Untiy Console.
|
||||
/// </summary>
|
||||
void DebugDetectedPlayerTarget()
|
||||
{
|
||||
if (EnableDebuggingTools == YesOrNo.No || DebugLogTargets == YesOrNo.No) return;
|
||||
|
||||
if (!EmeraldComponent.CombatComponent.DeathDelayActive)
|
||||
{
|
||||
if (EmeraldComponent.LookAtTarget != null)
|
||||
{
|
||||
Debug.Log("<b>" + "<color=green>" + gameObject.name + " - Current Look At Target: " + "</color>" + "<color=red>" + EmeraldComponent.LookAtTarget.gameObject.name + "</color>" + "</b>" + " |" +
|
||||
"<b>" + "<color=green>" + " Relation Type: " + "</color>" + "<color=green>" + EmeraldComponent.DetectionComponent.GetTargetFactionRelation(EmeraldComponent.LookAtTarget) + "</color>" + "</b>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the raycast lines between targets and look at targets.
|
||||
/// </summary>
|
||||
void DrawTargetRaycastLines()
|
||||
{
|
||||
if (EnableDebuggingTools == YesOrNo.No || DrawLineOfSightLines == YesOrNo.No || EmeraldComponent.CurrentTargetInfo.TargetSource == null) return;
|
||||
|
||||
Transform HeadTransform = EmeraldComponent.DetectionComponent.HeadTransform;
|
||||
|
||||
if (EmeraldComponent.CombatTarget != null)
|
||||
{
|
||||
TargetDirection = EmeraldComponent.CurrentTargetInfo.CurrentICombat.DamagePosition() - HeadTransform.position;
|
||||
Debug.DrawRay(new Vector3(HeadTransform.position.x, HeadTransform.position.y, HeadTransform.position.z), TargetDirection, DebugLineColor);
|
||||
}
|
||||
else if (EmeraldComponent.LookAtTarget != null)
|
||||
{
|
||||
Vector3 LookAtTargetDir = EmeraldComponent.CurrentTargetInfo.CurrentICombat.DamagePosition() - HeadTransform.position;
|
||||
Debug.DrawRay(new Vector3(EmeraldComponent.DetectionComponent.HeadTransform.position.x, HeadTransform.position.y, HeadTransform.position.z), LookAtTargetDir, DebugLineColor);
|
||||
}
|
||||
|
||||
if (EmeraldComponent.DetectionComponent.TargetObstructed || EmeraldComponent.AnimationComponent.IsTurning)
|
||||
{
|
||||
DebugLineColor = Color.red;
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugLineColor = Color.green;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws lines towards each undetected targets while the AI is still in its Alert state.
|
||||
/// </summary>
|
||||
void DrawUndetectedTargetsLineInternal()
|
||||
{
|
||||
if (EnableDebuggingTools == YesOrNo.No || DrawUndetectedTargetsLine == YesOrNo.No) return;
|
||||
|
||||
if (EmeraldComponent.DetectionComponent.CurrentDetectionState == EmeraldDetection.DetectionStates.Alert && EmeraldComponent.CombatTarget == null && !EmeraldComponent.AnimationComponent.IsDead)
|
||||
{
|
||||
foreach (Collider C in EmeraldComponent.DetectionComponent.LineOfSightTargets.ToArray())
|
||||
{
|
||||
Vector3 direction = C.bounds.center - EmeraldComponent.DetectionComponent.HeadTransform.position;
|
||||
Debug.DrawRay(EmeraldComponent.DetectionComponent.HeadTransform.position, direction, new Color(1, 0.549f, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the AI's current path with a line.
|
||||
/// </summary>
|
||||
void DrawNavMeshPathInternal ()
|
||||
{
|
||||
if (EnableDebuggingTools == YesOrNo.No || DrawNavMeshPath == YesOrNo.No) return;
|
||||
|
||||
for (int i = 0; i < EmeraldComponent.m_NavMeshAgent.path.corners.Length; i++)
|
||||
{
|
||||
if (i > 0) Debug.DrawLine(EmeraldComponent.m_NavMeshAgent.path.corners[i - 1] + Vector3.up * 0.5f, EmeraldComponent.m_NavMeshAgent.path.corners[i] + Vector3.up * 0.5f, NavMeshPathColor);
|
||||
else Debug.DrawLine(EmeraldComponent.m_NavMeshAgent.path.corners[0] + Vector3.up * 0.5f, EmeraldComponent.m_NavMeshAgent.path.corners[i] + Vector3.up * 0.5f, NavMeshPathColor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the AI's current destination.
|
||||
/// </summary>
|
||||
void DrawNavMeshDestinationInternal ()
|
||||
{
|
||||
if (EnableDebuggingTools == YesOrNo.No || DrawNavMeshDestination == YesOrNo.No) return;
|
||||
|
||||
DrawCircle(EmeraldComponent.m_NavMeshAgent.destination, 0.25f, NavMeshDestinationColor);
|
||||
Debug.DrawLine(EmeraldComponent.m_NavMeshAgent.destination + Vector3.up * 0.5f, EmeraldComponent.m_NavMeshAgent.destination, NavMeshDestinationColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the AI's current look at point when using the IK Component.
|
||||
/// </summary>
|
||||
void DrawLookAtPointsInternal()
|
||||
{
|
||||
if (DrawLookAtPoints == YesOrNo.No || !IKComponent || !IKComponent.m_AimSource || EmeraldComponent.AnimationComponent.IsDead) return;
|
||||
|
||||
Gizmos.color = new Color(1, 0, 0, 0.35f);
|
||||
Gizmos.DrawSphere(IKComponent.m_AimSource.position, 0.12f);
|
||||
Gizmos.color = Color.white;
|
||||
}
|
||||
|
||||
void DrawCircle(Vector3 center, float radius, Color color)
|
||||
{
|
||||
Vector3 prevPos = center + new Vector3(radius, 0, 0);
|
||||
for (int i = 0; i < 30; i++)
|
||||
{
|
||||
float angle = (float)(i + 1) / 30.0f * Mathf.PI * 2.0f;
|
||||
Vector3 newPos = center + new Vector3(Mathf.Cos(angle) * radius, 0, Mathf.Sin(angle) * radius);
|
||||
Debug.DrawLine(prevPos, newPos, color);
|
||||
prevPos = newPos;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
DrawLookAtPointsInternal();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ad554582c3102645807bc20652b7fbf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,75 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows UnityEvents to work through Emerald's various usable callbacks.
|
||||
/// </summary>
|
||||
[HelpURL("https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-optional/events-component")]
|
||||
public class EmeraldEvents : MonoBehaviour
|
||||
{
|
||||
#region Events Variables
|
||||
public UnityEvent OnEnabledEvent;
|
||||
public UnityEvent OnStartEvent;
|
||||
public UnityEvent OnEnemyTargetDetectedEvent;
|
||||
public UnityEvent OnStartCombatEvent;
|
||||
public UnityEvent OnEndCombatEvent;
|
||||
public UnityEvent OnAttackStartEvent;
|
||||
public UnityEvent OnAttackEndEvent;
|
||||
public UnityEvent OnTakeDamageEvent;
|
||||
public UnityEvent OnTakeCritDamageEvent;
|
||||
public UnityEvent OnDoDamageEvent;
|
||||
public UnityEvent OnDoCritDamageEvent;
|
||||
public UnityEvent OnKilledTargetEvent;
|
||||
public UnityEvent OnDeathEvent;
|
||||
public UnityEvent OnReachedDestinationEvent;
|
||||
public UnityEvent OnReachedWaypointEvent;
|
||||
public UnityEvent OnGeneratedWaypointEvent;
|
||||
public UnityEvent OnPlayerDetectedEvent;
|
||||
public UnityEvent OnFleeEvent;
|
||||
EmeraldSystem EmeraldComponent;
|
||||
#endregion
|
||||
|
||||
#region Editor Variables
|
||||
public bool HideSettingsFoldout;
|
||||
public bool GeneralEventsFoldout;
|
||||
public bool CombatEventsFoldout;
|
||||
#endregion
|
||||
|
||||
void Start()
|
||||
{
|
||||
OnStartEvent.Invoke(); //Invoke the OnStartEvent
|
||||
InitializeEvents();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the Events Component.
|
||||
/// </summary>
|
||||
void InitializeEvents ()
|
||||
{
|
||||
EmeraldComponent = GetComponent<EmeraldSystem>();
|
||||
EmeraldComponent.MovementComponent.OnReachedDestination += OnReachedDestinationEvent.Invoke; //Subscribe the OnReachedDestinationEvent to the OnReachedDestination delegate.
|
||||
EmeraldComponent.MovementComponent.OnReachedWaypoint += OnReachedWaypointEvent.Invoke; //Subscribe the OnReachedWaypointEvent to the OnReachedWaypoint delegate.
|
||||
EmeraldComponent.MovementComponent.OnGeneratedWaypoint += OnGeneratedWaypointEvent.Invoke; //Subscribe the OnGeneratedWaypointEvent to the OnGeneratedWaypoint delegate.
|
||||
EmeraldComponent.DetectionComponent.OnEnemyTargetDetected += OnEnemyTargetDetectedEvent.Invoke; //Subscribe the OnEnemyTargetDetectedEvent to the OnEnemyTargetDetected delegate.
|
||||
EmeraldComponent.DetectionComponent.OnPlayerDetected += OnPlayerDetectedEvent.Invoke; //Subscribe the OnPlayerDetectedEvent to the OnPlayerDetected delegate.
|
||||
EmeraldComponent.HealthComponent.OnDeath += OnDeathEvent.Invoke; //Subscribe the OnDeathEvent to the OnDeath delegate.
|
||||
EmeraldComponent.HealthComponent.OnTakeDamage += OnTakeDamageEvent.Invoke; //Subscribe the OnTakeDamageEvent to the OnTakeDamage delegate.
|
||||
EmeraldComponent.HealthComponent.OnTakeCritDamage += OnTakeCritDamageEvent.Invoke; //Subscribe the OnTakeCritDamageEvent to the OnTakeCritDamage delegate.
|
||||
EmeraldComponent.CombatComponent.OnKilledTarget += OnKilledTargetEvent.Invoke; //Subscribe the OnKilledTargetEvent to the OnKilledTarget delegate.
|
||||
EmeraldComponent.AnimationComponent.OnStartAttackAnimation += OnAttackStartEvent.Invoke; //Subscribe the OnAttackStartEvent to the OnAttackStartEvent delegate.
|
||||
EmeraldComponent.AnimationComponent.OnEndAttackAnimation += OnAttackEndEvent.Invoke; //Subscribe the OnAttackEndEvent to the OnAttackEndEvent delegate.
|
||||
EmeraldComponent.CombatComponent.OnDoDamage += OnDoDamageEvent.Invoke; //Subscribe the OnDoDamageEvent to the OnDoDamage delegate.
|
||||
EmeraldComponent.CombatComponent.OnDoCritDamage += OnDoCritDamageEvent.Invoke; //Subscribe the OnDoCritDamageEvent to the OnDoCritDamage delegate.
|
||||
EmeraldComponent.CombatComponent.OnStartCombat += OnStartCombatEvent.Invoke; //Subscribe the OnStartCombatEvent to the OnStartCombat delegate.
|
||||
EmeraldComponent.CombatComponent.OnEndCombat += OnEndCombatEvent.Invoke; //Subscribe the OnEndCombatEvent to the OnEndCombat delegate.
|
||||
EmeraldComponent.BehaviorsComponent.OnFlee += OnFleeEvent.Invoke; //Subscribe the OnFleeEvent to the OnFlee delegate.
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
OnEnabledEvent.Invoke(); //Invoke the OnEnabledEvent.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 41a9deb83781e644a8e353dfbc492941
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,236 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
using EmeraldAI;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[HelpURL("https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-optional/footsteps-component")]
|
||||
public class EmeraldFootsteps : MonoBehaviour
|
||||
{
|
||||
#region Variables
|
||||
public LayerMask DetectableLayers;
|
||||
public LayerMask IgnoreLayers;
|
||||
public List<Transform> FeetTransforms = new List<Transform>();
|
||||
public List<FootstepSurfaceObject> FootstepSurfaces = new List<FootstepSurfaceObject>();
|
||||
public bool HideSettingsFoldout;
|
||||
public bool FootstepsFoldout;
|
||||
public bool SurfaceFoldout;
|
||||
EmeraldSystem EmeraldComponent;
|
||||
LayerMask InternalIgnoreLayers;
|
||||
Transform CurrentFoot;
|
||||
Transform LastFoot;
|
||||
Transform LastFoot2;
|
||||
float TimeStamp;
|
||||
#endregion
|
||||
|
||||
void Start()
|
||||
{
|
||||
EmeraldComponent = GetComponent<EmeraldSystem>();
|
||||
Invoke(nameof(SetupIgnoreLayers), 0.1f);
|
||||
if (FeetTransforms.Count == 0) Debug.LogError("The '" + gameObject.name + "' does not have any Feet Transforms. Please assign some in order to use the Footsteps Component.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatically add an AI's own layer and its LBD's Collider Layer to the IgnoreLayers layermask.
|
||||
/// </summary>
|
||||
void SetupIgnoreLayers ()
|
||||
{
|
||||
InternalIgnoreLayers = IgnoreLayers;
|
||||
|
||||
LayerMask LBDLayers = 0;
|
||||
|
||||
//Add the AI's LBD Collider Layer, if the AI is using a LBDComponent.
|
||||
if (EmeraldComponent.LBDComponent != null)
|
||||
{
|
||||
LBDLayers |= (1 << EmeraldComponent.LBDComponent.LBDComponentsLayer);
|
||||
}
|
||||
|
||||
//Add the AI's own layer to the
|
||||
InternalIgnoreLayers |= (1 << EmeraldComponent.gameObject.layer);
|
||||
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
if (LBDLayers == (LBDLayers | (1 << i)))
|
||||
{
|
||||
InternalIgnoreLayers |= (1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
//Update the IgnoreLayers with the addition of the automatically included layers.
|
||||
IgnoreLayers = InternalIgnoreLayers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A universal function for creating a footstep.
|
||||
/// </summary>
|
||||
public void Footstep()
|
||||
{
|
||||
CreateFootstep();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The original function used for walk footstep sounds. This is used to allow AI who previously used this event
|
||||
/// to work without having to redo all footstep sounds. Keep in mind, this function will eventually be deprecated in future versions.
|
||||
/// It's best to use the Footstep function, which works universally for both walk and run (as well as any other animations you want footsteps for).
|
||||
/// </summary>
|
||||
public void WalkFootstepSound()
|
||||
{
|
||||
CreateFootstep();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The original function used for walk footstep sounds. This is used to allow AI who previously used this event
|
||||
/// to work without having to redo all footstep sounds. Keep in mind, this function will eventually be deprecated in future versions.
|
||||
/// It's best to use the Footstep function, which works universally for both walk and run (as well as any other animations you want footsteps for).
|
||||
/// </summary>
|
||||
public void RunFootstepSound()
|
||||
{
|
||||
CreateFootstep();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a footstep by calculating the lowest footstep at the time a Footstep event is called through an Animation Event.
|
||||
/// </summary>
|
||||
void CreateFootstep()
|
||||
{
|
||||
//Return if any of these conditions are met.
|
||||
if (EmeraldComponent.AnimationComponent.IsIdling || Time.time < (TimeStamp + 0.25f) || EmeraldComponent.AnimationComponent.BusyBetweenStates || FeetTransforms.Count == 0) return;
|
||||
|
||||
TimeStamp = Time.time; //Time stamp the last footstep and ensure at least 1/4 a second passes before creating a new one (as Animation Events can sometimes fire simultaneously if they are between states).
|
||||
|
||||
//Get the lowest footstep at the time a Footstep event is called through an Animation Event.
|
||||
CalculateLowestFoot();
|
||||
|
||||
if (CurrentFoot != null)
|
||||
{
|
||||
//Fire a raycast down from the lowest foot transform (ignoring any layers from the IgnoreLayers variable).
|
||||
RaycastHit hit;
|
||||
if (Physics.Raycast(CurrentFoot.position, Vector3.up * -0.25f, out hit, 1f, ~IgnoreLayers))
|
||||
{
|
||||
if (hit.collider != null)
|
||||
{
|
||||
//Get the tag of detected raycast collider.
|
||||
var StepData = FootstepSurfaces.Find(step => step.SurfaceType == FootstepSurfaceObject.SurfaceTypes.Tag && step.SurfaceTag == hit.collider.tag);
|
||||
|
||||
//If the StepData is null, check for a terrain. If a terrain is found, get the most dominant texture for the footstep's position.
|
||||
//Use this texture to determine the Surface Data that should be used for this footstep.
|
||||
if (StepData == null)
|
||||
{
|
||||
Terrain CurrentTerrain = hit.collider.GetComponent<Terrain>();
|
||||
if (CurrentTerrain != null)
|
||||
{
|
||||
Texture CurrentTexture = GetTerrainTexture(transform.position, CurrentTerrain);
|
||||
StepData = FootstepSurfaces.Find(step => step.SurfaceType == FootstepSurfaceObject.SurfaceTypes.Texture && step.SurfaceTextures.Contains(CurrentTexture));
|
||||
}
|
||||
}
|
||||
|
||||
//Debug Settings Only
|
||||
if (EmeraldComponent.DebuggerComponent != null && EmeraldComponent.DebuggerComponent.DrawFootstepPositions == YesOrNo.Yes)
|
||||
{
|
||||
Debug.DrawLine(CurrentFoot.position, hit.point, Color.yellow, 6);
|
||||
DrawCircle(hit.point + Vector3.up * 0.05f, 0.25f, Color.yellow);
|
||||
}
|
||||
|
||||
//Play a footstep sound and effect using the current StepData, given they aren't null.
|
||||
if (StepData != null)
|
||||
{
|
||||
//Debug Settings Only
|
||||
if (EmeraldComponent.DebuggerComponent != null && EmeraldComponent.DebuggerComponent.DebugLogFootsteps == YesOrNo.Yes)
|
||||
{
|
||||
Debug.Log("The <b><color=green>" + gameObject.name + "</color></b> footstep collided with <b><color=green>" + hit.collider.name + "</color></b> and used the <b><color=green>" + StepData.name + "</color></b> Footstep Surface Object.");
|
||||
}
|
||||
|
||||
//Step Effects
|
||||
if (StepData.StepEffects.Count > 0)
|
||||
{
|
||||
GameObject StepEffect = StepData.StepEffects[Random.Range(0, StepData.StepEffects.Count)];
|
||||
if (StepEffect != null) EmeraldAI.Utility.EmeraldObjectPool.SpawnEffect(StepEffect, new Vector3(CurrentFoot.position.x, hit.point.y + 0.01f, CurrentFoot.position.z), Quaternion.FromToRotation(Vector3.up, hit.normal), StepData.StepEffectTimeout);
|
||||
}
|
||||
|
||||
//Footprints
|
||||
if (StepData.Footprints.Count > 0)
|
||||
{
|
||||
GameObject Footprint = StepData.Footprints[Random.Range(0, StepData.Footprints.Count)];
|
||||
if (Footprint != null) EmeraldAI.Utility.EmeraldObjectPool.SpawnEffect(Footprint, new Vector3(CurrentFoot.position.x, hit.point.y + 0.01f, CurrentFoot.position.z), Quaternion.FromToRotation(transform.up, hit.normal) * transform.rotation, StepData.FootprintTimeout);
|
||||
}
|
||||
|
||||
//Step Sounds
|
||||
if (StepData.StepSounds.Count > 0)
|
||||
{
|
||||
AudioClip StepSound = StepData.StepSounds[Random.Range(0, StepData.StepSounds.Count)];
|
||||
if (StepSound != null) EmeraldComponent.SoundComponent.PlayAudioClip(StepSound, StepData.StepVolume);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Texture GetTerrainTexture(Vector3 Position, Terrain terrain)
|
||||
{
|
||||
int surfaceIndex = 0;
|
||||
surfaceIndex = GetDominateTexture(Position, terrain, terrain.terrainData);
|
||||
return terrain.terrainData.terrainLayers[surfaceIndex].diffuseTexture;
|
||||
}
|
||||
|
||||
float[] GetTextureBlend(Vector3 Pos, Terrain terrain, TerrainData terrainData)
|
||||
{
|
||||
int posX = (int)(((Pos.x - terrain.transform.position.x) / terrainData.size.x) * terrainData.alphamapWidth);
|
||||
int posZ = (int)(((Pos.z - terrain.transform.position.z) / terrainData.size.z) * terrainData.alphamapHeight);
|
||||
float[,,] SplatmapData = terrainData.GetAlphamaps(posX, posZ, 1, 1);
|
||||
float[] blend = new float[SplatmapData.GetUpperBound(2) + 1];
|
||||
|
||||
for (int i = 0; i < blend.Length; i++)
|
||||
{
|
||||
blend[i] = SplatmapData[0, 0, i];
|
||||
}
|
||||
|
||||
return blend;
|
||||
}
|
||||
|
||||
int GetDominateTexture(Vector3 Pos, Terrain terrain, TerrainData terrainData)
|
||||
{
|
||||
float[] textureMix = GetTextureBlend(Pos, terrain, terrainData);
|
||||
int greatestIndex = 0;
|
||||
float maxTextureMix = 0;
|
||||
|
||||
for (int i = 0; i < textureMix.Length; i++)
|
||||
{
|
||||
if (textureMix[i] > maxTextureMix)
|
||||
{
|
||||
greatestIndex = i;
|
||||
maxTextureMix = textureMix[i];
|
||||
}
|
||||
}
|
||||
|
||||
return greatestIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Order the FeetTransforms from the lowest to the highest, assigning the lowest footstep as the CurrentFoot.
|
||||
/// </summary>
|
||||
void CalculateLowestFoot()
|
||||
{
|
||||
LastFoot = CurrentFoot;
|
||||
CurrentFoot = FeetTransforms.OrderBy(p => p.position.y).First();
|
||||
if (LastFoot == CurrentFoot) CurrentFoot = FeetTransforms[FeetTransforms.Count-1];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to draw a circle of where each footstep collided.
|
||||
/// </summary>
|
||||
void DrawCircle(Vector3 center, float radius, Color color)
|
||||
{
|
||||
Vector3 prevPos = center + new Vector3(radius, 0, 0);
|
||||
for (int i = 0; i < 30; i++)
|
||||
{
|
||||
float angle = (float)(i + 1) / 30.0f * Mathf.PI * 2.0f;
|
||||
Vector3 newPos = center + new Vector3(Mathf.Cos(angle) * radius, 0, Mathf.Sin(angle) * radius);
|
||||
Debug.DrawLine(prevPos, newPos, color, 6);
|
||||
prevPos = newPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2ed4a775d9ab2ce40b00c0c4f9665db7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,368 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Animations.Rigging;
|
||||
using System.Linq;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[HelpURL("https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-optional/inverse-kinematics-component")]
|
||||
public class EmeraldInverseKinematics : MonoBehaviour
|
||||
{
|
||||
#region IK Variables
|
||||
public int WanderingLookAtLimit = 75;
|
||||
public float WanderingLookSpeed = 4f;
|
||||
public int WanderingLookDistance = 12;
|
||||
public float WanderingLookHeightOffset = 0;
|
||||
public int CombatLookAtLimit = 75;
|
||||
public float CombatLookSpeed = 6f;
|
||||
public int CombatLookDistance = 12;
|
||||
public float CombatLookHeightOffset = 0;
|
||||
public List<Rig> UpperBodyRigsList = new List<Rig>();
|
||||
public Transform m_AimSource;
|
||||
#endregion
|
||||
|
||||
#region Private Variables
|
||||
List<MultiAimConstraint> m_MultiAimConstraints = new List<MultiAimConstraint>();
|
||||
Coroutine FadeRigCoroutine;
|
||||
Transform m_AimSourceParent;
|
||||
EmeraldSystem EmeraldComponent;
|
||||
Vector3 m_AimGoal;
|
||||
Vector3 CurrentTargetPosition;
|
||||
RigBuilder m_RigBuilder;
|
||||
MultiAimConstraint ChildMultiAimConstraints;
|
||||
float AutoFadeInTimer;
|
||||
bool FadeInProgress;
|
||||
float CurrentTargetAngle;
|
||||
#endregion
|
||||
|
||||
#region Editor Variables
|
||||
public bool HideSettingsFoldout;
|
||||
public bool GeneralIKSettingsFoldout;
|
||||
public bool RigSettingsFoldout;
|
||||
#endregion
|
||||
|
||||
void Start()
|
||||
{
|
||||
InitializeIK();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the IK system.
|
||||
/// </summary>
|
||||
private void InitializeIK()
|
||||
{
|
||||
EmeraldComponent = GetComponent<EmeraldSystem>();
|
||||
m_RigBuilder = GetComponent<RigBuilder>(); //Get the AI's RigBuilder.
|
||||
|
||||
if (m_RigBuilder == null)
|
||||
{
|
||||
this.enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
EmeraldComponent.HealthComponent.OnDeath += DisableInverseKinematics; //Subscribe to Death delegate to disable Inverse Kinematics on death.
|
||||
|
||||
//Create an aim source
|
||||
m_AimSourceParent = new GameObject("Aim Source Parent").transform;
|
||||
m_AimSourceParent.position = EmeraldComponent.DetectionComponent.HeadTransform.position;
|
||||
m_AimSourceParent.SetParent(transform);
|
||||
|
||||
//Set up the m_AimSource's position, which is based off an AI's HeadTransform position
|
||||
m_AimSource = new GameObject("Aim Source").transform;
|
||||
m_AimSource.position = EmeraldComponent.DetectionComponent.HeadTransform.position;
|
||||
m_AimSource.localPosition = m_AimSource.localPosition + transform.forward * 2;
|
||||
m_AimSource.SetParent(m_AimSourceParent);
|
||||
m_AimSource.localEulerAngles = Vector3.zero;
|
||||
m_AimGoal = m_AimSourceParent.position + (transform.forward * 3);
|
||||
|
||||
//Initialize the list of MultiAimConstraints (located within each Rig) with the m_AimSource.
|
||||
for (int i = 0; i < UpperBodyRigsList.Count; i++)
|
||||
{
|
||||
var ChildMultiAimConstraints = UpperBodyRigsList[i].GetComponentsInChildren<MultiAimConstraint>();
|
||||
|
||||
if (ChildMultiAimConstraints.Length > 0)
|
||||
{
|
||||
for (int j = 0; j < ChildMultiAimConstraints.Length; j++)
|
||||
{
|
||||
m_MultiAimConstraints.Add(ChildMultiAimConstraints[j]);
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < m_MultiAimConstraints.Count; j++)
|
||||
{
|
||||
var m_SourceObjects = m_MultiAimConstraints[j].data.sourceObjects;
|
||||
m_MultiAimConstraints[j].data.maintainOffset = false;
|
||||
m_SourceObjects.SetTransform(0, m_AimSource);
|
||||
|
||||
//Add a sourceObject if one doesn't exist
|
||||
if (m_MultiAimConstraints[j].data.sourceObjects.Count == 0)
|
||||
m_SourceObjects.Add(new WeightedTransform(m_AimSource, 1f));
|
||||
|
||||
m_MultiAimConstraints[j].data.sourceObjects = m_SourceObjects;
|
||||
}
|
||||
}
|
||||
|
||||
m_RigBuilder.Build(); //Rebuild the Rig Builder so the changes can be applied.
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
UpdateIK();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the IK system by tracking the current Aim Goal and lerping the Aim Source position to it over time.
|
||||
/// </summary>
|
||||
void UpdateIK ()
|
||||
{
|
||||
if (transform.localScale == Vector3.one * 0.003f) return;
|
||||
|
||||
if (FadeInProgress && EmeraldComponent.AnimationComponent.IsIdling)
|
||||
{
|
||||
AutoFadeInTimer += Time.deltaTime;
|
||||
|
||||
if (AutoFadeInTimer > 0.5f)
|
||||
{
|
||||
for (int i = 0; i < UpperBodyRigsList.Count; i++)
|
||||
{
|
||||
UpperBodyRigsList[i].weight = Mathf.Lerp(UpperBodyRigsList[i].weight, 1, Time.deltaTime * 5);
|
||||
|
||||
if (UpperBodyRigsList[UpperBodyRigsList.Count - 1].weight >= 1f)
|
||||
{
|
||||
if (FadeRigCoroutine != null) StopCoroutine(FadeRigCoroutine);
|
||||
AutoFadeInTimer = 0;
|
||||
FadeInProgress = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CurrentTargetAngle = EmeraldComponent.CombatComponent.TargetAngle;
|
||||
int LookAtLimit = EmeraldComponent.CombatComponent.CombatState ? CombatLookAtLimit : WanderingLookAtLimit;
|
||||
float LookSpeed = EmeraldComponent.CombatComponent.CombatState ? CombatLookSpeed : WanderingLookSpeed;
|
||||
int LookDistance = EmeraldComponent.CombatComponent.CombatState ? CombatLookDistance : WanderingLookDistance;
|
||||
float LookHeightOffset = EmeraldComponent.CombatComponent.CombatState ? CombatLookHeightOffset : WanderingLookHeightOffset;
|
||||
|
||||
if (EmeraldComponent.CurrentTargetInfo.TargetSource != null)
|
||||
{
|
||||
CurrentTargetPosition = EmeraldComponent.CurrentTargetInfo.CurrentICombat.DamagePosition();
|
||||
float Distance = Vector3.Distance(m_AimSourceParent.position, CurrentTargetPosition);
|
||||
|
||||
//Target is within look at and distance range.
|
||||
if (CurrentTargetAngle <= LookAtLimit && Distance < LookDistance && !EmeraldComponent.AnimationComponent.IsStunned && !EmeraldComponent.DetectionComponent.TargetObstructed && !EmeraldComponent.AnimationComponent.IsTurning && !EmeraldComponent.AnimationComponent.IsDodging)
|
||||
{
|
||||
if (Distance > 0.75f)
|
||||
{
|
||||
float CurrentLookDistance = Vector3.Distance(new Vector3(m_AimSource.position.x, m_AimSource.position.y, CurrentTargetPosition.z), CurrentTargetPosition); //Get the distance between the current look position and the look goal (exluding the z axis)
|
||||
EmeraldComponent.BehaviorsComponent.IsAiming = (CurrentLookDistance > 0.5f);
|
||||
m_AimGoal = new Vector3(CurrentTargetPosition.x, Mathf.Lerp(m_AimGoal.y, CurrentTargetPosition.y, Time.deltaTime * 3f), CurrentTargetPosition.z);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_AimGoal = new Vector3(CurrentTargetPosition.x, m_AimGoal.y, CurrentTargetPosition.z);
|
||||
}
|
||||
}
|
||||
//Target is not within look at or distance range.
|
||||
else
|
||||
{
|
||||
if (EmeraldComponent.CombatComponent.CombatState)
|
||||
{
|
||||
EmeraldComponent.BehaviorsComponent.IsAiming = false;
|
||||
if (!EmeraldComponent.AnimationComponent.IsTurningLeft && !EmeraldComponent.AnimationComponent.IsTurningRight)
|
||||
{
|
||||
Vector3 LookPos = m_AimSourceParent.position + (transform.forward * EmeraldComponent.CombatComponent.DistanceFromTarget) + transform.up * LookHeightOffset;
|
||||
m_AimGoal = new Vector3(LookPos.x, Mathf.Lerp(m_AimGoal.y, LookPos.y, Time.deltaTime), LookPos.z);
|
||||
}
|
||||
else if (EmeraldComponent.AnimationComponent.IsTurningLeft)
|
||||
{
|
||||
Vector3 LookPos = m_AimSourceParent.position + (transform.forward * EmeraldComponent.CombatComponent.DistanceFromTarget) + m_AimSourceParent.right * 5 + transform.up * LookHeightOffset;
|
||||
m_AimGoal = new Vector3(LookPos.x, Mathf.Lerp(m_AimGoal.y, LookPos.y, Time.deltaTime), LookPos.z);
|
||||
}
|
||||
else if (EmeraldComponent.AnimationComponent.IsTurningRight)
|
||||
{
|
||||
Vector3 LookPos = m_AimSourceParent.position + (transform.forward * EmeraldComponent.CombatComponent.DistanceFromTarget) - m_AimSourceParent.right * 5 + transform.up * LookHeightOffset;
|
||||
m_AimGoal = new Vector3(LookPos.x, Mathf.Lerp(m_AimGoal.y, LookPos.y, Time.deltaTime), LookPos.z);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector3 LookPos = m_AimSourceParent.position + (transform.forward * 3) + transform.up * LookHeightOffset;
|
||||
m_AimGoal = new Vector3(LookPos.x, Mathf.Lerp(m_AimGoal.y, LookPos.y, Time.deltaTime), LookPos.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!EmeraldComponent.CombatComponent.DeathDelayActive || EmeraldComponent.CombatTarget == null && !EmeraldComponent.CombatComponent.DeathDelayActive || EmeraldComponent.DetectionComponent.TargetObstructed)
|
||||
{
|
||||
//Sets the aim goal to be in front of the AI (the default looking position).
|
||||
m_AimGoal = m_AimSourceParent.position + (transform.forward * 3) + transform.up * LookHeightOffset;
|
||||
EmeraldComponent.BehaviorsComponent.IsAiming = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!EmeraldComponent.AnimationComponent.IsTurning && !EmeraldComponent.AnimationComponent.IsDodging) m_AimSource.position = Vector3.Slerp(m_AimSource.position, m_AimGoal, Time.deltaTime * LookSpeed);
|
||||
else m_AimSource.position = Vector3.Slerp(m_AimSource.position, m_AimGoal, Time.deltaTime * 1);
|
||||
}
|
||||
|
||||
|
||||
void Debugging()
|
||||
{
|
||||
for (int i = 0; i < UpperBodyRigsList.Count; i++)
|
||||
{
|
||||
var ChildMultiAimConstraints = UpperBodyRigsList[i].GetComponentsInChildren<MultiAimConstraint>();
|
||||
|
||||
if (ChildMultiAimConstraints.Length > 0)
|
||||
{
|
||||
for (int j = 0; j < ChildMultiAimConstraints.Length; j++)
|
||||
{
|
||||
Vector3 AimDir = (m_AimSource.position - ChildMultiAimConstraints[j].data.constrainedObject.position);
|
||||
//Debug.DrawLine(ChildMultiAimConstraints[j].data.constrainedObject.position, AimDir);
|
||||
Debug.DrawRay(ChildMultiAimConstraints[j].data.constrainedObject.position, AimDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pass the rig name to fade out the rig's weight (String Parameter == Rig Name) and (Float Parameter == Fade Speed).
|
||||
/// </summary>
|
||||
public void FadeOutIK (AnimationEvent animationEvent)
|
||||
{
|
||||
if (!this.enabled) return;
|
||||
string[] RigNames = animationEvent.stringParameter.Split(',');
|
||||
if (RigNames.Length == 0)
|
||||
{
|
||||
//Find the rig using the passed name
|
||||
Rig m_Rig = UpperBodyRigsList.Find(x => x.gameObject.name == animationEvent.stringParameter);
|
||||
if (FadeRigCoroutine != null) StopCoroutine(FadeRigCoroutine);
|
||||
if (m_Rig != null) FadeRigCoroutine = StartCoroutine(FadeOutRigInternal(new Rig[] {m_Rig}, animationEvent.floatParameter));
|
||||
else Debug.LogError("The Rig " + animationEvent.stringParameter + " could not be found on the " + gameObject.name + " AI. Please check to ensure everything was properly named.");
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Rig> m_Rigs = new List<Rig>();
|
||||
|
||||
for (int i = 0; i < RigNames.Length; i++)
|
||||
{
|
||||
//Remove the first character from the rig name if it's a space
|
||||
if (RigNames[i][0] == ' ')
|
||||
RigNames[i] = RigNames[i].Substring(1);
|
||||
|
||||
//Find the rig using the passed name and add it to the list of rigs
|
||||
Rig m_Rig = UpperBodyRigsList.Find(x => x.gameObject.name == RigNames[i]);
|
||||
if (m_Rig != null) m_Rigs.Add(m_Rig);
|
||||
else Debug.LogError("The Rig " + RigNames[i] + " could not be found on the " + gameObject.name + " AI. Please check to ensure everything was properly named.");
|
||||
}
|
||||
|
||||
//Start the coroutine to fade out each rig
|
||||
if (FadeRigCoroutine != null) StopCoroutine(FadeRigCoroutine);
|
||||
FadeRigCoroutine = StartCoroutine(FadeOutRigInternal(m_Rigs.ToArray(), animationEvent.floatParameter));
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator FadeOutRigInternal(Rig[] rigs, float Speed)
|
||||
{
|
||||
if (EmeraldComponent.CombatComponent.CombatState) FadeInProgress = true;
|
||||
|
||||
for (int i = 0; i < rigs.Length; i++)
|
||||
{
|
||||
float t = 0;
|
||||
float StartingWeight = rigs[i].weight;
|
||||
|
||||
while (t < 1)
|
||||
{
|
||||
t += Time.deltaTime * Speed;
|
||||
EmeraldComponent.BehaviorsComponent.IsAiming = true;
|
||||
rigs[i].weight = Mathf.Lerp(StartingWeight, 0, t);
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pass the rig name to fade in the rig's weight (String Parameter == Rig Name) and (Float Parameter == Fade Speed).
|
||||
/// </summary>
|
||||
public void FadeInIK(AnimationEvent animationEvent)
|
||||
{
|
||||
if (!this.enabled) return;
|
||||
string[] RigNames = animationEvent.stringParameter.Split(',');
|
||||
if (RigNames.Length == 0)
|
||||
{
|
||||
//Find the rig using the passed name
|
||||
Rig m_Rig = UpperBodyRigsList.Find(x => x.gameObject.name == animationEvent.stringParameter);
|
||||
if (FadeRigCoroutine != null) StopCoroutine(FadeRigCoroutine);
|
||||
if (m_Rig != null) FadeRigCoroutine = StartCoroutine(FadeInRigInternal(new Rig[] {m_Rig}, animationEvent.floatParameter));
|
||||
else Debug.LogError("The Rig " + animationEvent.stringParameter + " could not be found on the " + gameObject.name + " AI. Please check to ensure everything was properly named.");
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Rig> m_Rigs = new List<Rig>();
|
||||
|
||||
for (int i = 0; i < RigNames.Length; i++)
|
||||
{
|
||||
//Remove the first character from the rig name if it's a space
|
||||
if (RigNames[i][0] == ' ')
|
||||
RigNames[i] = RigNames[i].Substring(1);
|
||||
|
||||
//Find the rig using the passed name and add it to the list of rigs
|
||||
Rig m_Rig = UpperBodyRigsList.Find(x => x.gameObject.name == RigNames[i]);
|
||||
if (m_Rig != null) m_Rigs.Add(m_Rig);
|
||||
else Debug.LogError("The Rig " + RigNames[i] + " could not be found on the " + gameObject.name + " AI. Please check to ensure everything was properly named.");
|
||||
}
|
||||
|
||||
//Start the coroutine to fade in each rig
|
||||
if (FadeRigCoroutine != null) StopCoroutine(FadeRigCoroutine);
|
||||
FadeRigCoroutine = StartCoroutine(FadeInRigInternal(m_Rigs.ToArray(), animationEvent.floatParameter));
|
||||
}
|
||||
|
||||
FadeInProgress = false;
|
||||
}
|
||||
|
||||
IEnumerator FadeInRigInternal(Rig[] rigs, float Speed)
|
||||
{
|
||||
for (int i = 0; i < rigs.Length; i++)
|
||||
{
|
||||
float t = 0;
|
||||
float StartingWeight = rigs[i].weight;
|
||||
|
||||
while (t < 1)
|
||||
{
|
||||
t += Time.deltaTime * Speed;
|
||||
EmeraldComponent.BehaviorsComponent.IsAiming = true;
|
||||
rigs[i].weight = Mathf.Lerp(StartingWeight, 1, t);
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
FadeRigCoroutine = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables the Inverse Kinematics and RigBuilder.
|
||||
/// </summary>
|
||||
public void EnableInverseKinematics()
|
||||
{
|
||||
for (int i = 0; i < UpperBodyRigsList.Count; i++)
|
||||
{
|
||||
UpperBodyRigsList[i].weight = 1;
|
||||
}
|
||||
|
||||
m_RigBuilder.enabled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables the Inverse Kinematics and RigBuilder.
|
||||
/// </summary>
|
||||
public void DisableInverseKinematics()
|
||||
{
|
||||
for (int i = 0; i < UpperBodyRigsList.Count; i++)
|
||||
{
|
||||
UpperBodyRigsList[i].weight = 0;
|
||||
}
|
||||
|
||||
m_RigBuilder.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 398e9e5be7a119e46aa1da605e4145d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,369 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using EmeraldAI.Utility;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[HelpURL("https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-optional/items-component")]
|
||||
public class EmeraldItems : MonoBehaviour
|
||||
{
|
||||
#region Items Variables
|
||||
public YesOrNo UseDroppableWeapon = YesOrNo.No;
|
||||
|
||||
[SerializeField]
|
||||
public List<EquippableWeapons> Type1EquippableWeapons = new List<EquippableWeapons>();
|
||||
[SerializeField]
|
||||
public List<EquippableWeapons> Type2EquippableWeapons = new List<EquippableWeapons>();
|
||||
[System.Serializable]
|
||||
public class EquippableWeapons
|
||||
{
|
||||
public bool HeldToggle;
|
||||
public GameObject HeldObject;
|
||||
public bool HolsteredToggle;
|
||||
public GameObject HolsteredObject;
|
||||
public bool DroppableToggle;
|
||||
public GameObject DroppableObject;
|
||||
}
|
||||
|
||||
public delegate void OnEquipWeaponHandler(string WeaponType);
|
||||
public event OnEquipWeaponHandler OnEquipWeapon;
|
||||
public delegate void OnUnequipWeaponHandler(string WeaponType);
|
||||
public event OnUnequipWeaponHandler OnUnequipWeapon;
|
||||
|
||||
[System.Serializable]
|
||||
public class ItemClass
|
||||
{
|
||||
public int ItemID = 1;
|
||||
public GameObject ItemObject;
|
||||
}
|
||||
[SerializeField]
|
||||
public List<ItemClass> ItemList = new List<ItemClass>();
|
||||
#endregion
|
||||
|
||||
#region Editor Variables
|
||||
public bool HideSettingsFoldout;
|
||||
public bool WeaponsFoldout;
|
||||
public bool ItemsFoldout;
|
||||
#endregion
|
||||
|
||||
void Start()
|
||||
{
|
||||
InitializeDroppableWeapon();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Intializes the AI's droppable weapon to be used when the AI dies.
|
||||
/// </summary>
|
||||
public void InitializeDroppableWeapon()
|
||||
{
|
||||
GetComponent<EmeraldHealth>().OnDeath += CreateDroppableWeapon; //Subscribe to the OnDeath event for when dropping an AI's weapon on death, given that it's enabled.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates an AI's Droppable Weapon Object on death.
|
||||
/// </summary>
|
||||
public void CreateDroppableWeapon()
|
||||
{
|
||||
EmeraldSystem EmeraldComponent = GetComponent<EmeraldSystem>();
|
||||
|
||||
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1)
|
||||
{
|
||||
for (int i = 0; i < Type1EquippableWeapons.Count; i++)
|
||||
{
|
||||
if (Type1EquippableWeapons[i].DroppableObject != null && Type1EquippableWeapons[i].DroppableToggle)
|
||||
{
|
||||
if (Type1EquippableWeapons[i].HeldObject != null)
|
||||
{
|
||||
//Instantiate a copy of the DroppableObject and position it to HeldObject
|
||||
var DroppableMeleeWeapon = EmeraldObjectPool.Spawn(Type1EquippableWeapons[i].DroppableObject, Type1EquippableWeapons[i].HeldObject.transform.position, Type1EquippableWeapons[i].HeldObject.transform.rotation);
|
||||
DroppableMeleeWeapon.transform.localScale = Type1EquippableWeapons[i].HeldObject.transform.lossyScale;
|
||||
DroppableMeleeWeapon.transform.SetParent(Type1EquippableWeapons[i].HeldObject.transform.parent);
|
||||
DroppableMeleeWeapon.transform.localPosition = Type1EquippableWeapons[i].HeldObject.transform.localPosition;
|
||||
DroppableMeleeWeapon.gameObject.name = Type1EquippableWeapons[i].HeldObject.gameObject.name + " (Droppable Copy)";
|
||||
DroppableMeleeWeapon.transform.SetParent(EmeraldSystem.ObjectPool.transform);
|
||||
|
||||
//Check for a collider on the WeaponObject, if there isn't one, add one.
|
||||
if (DroppableMeleeWeapon.GetComponent<Collider>() == null)
|
||||
DroppableMeleeWeapon.AddComponent<BoxCollider>();
|
||||
|
||||
if (DroppableMeleeWeapon.GetComponent<Rigidbody>() == null)
|
||||
DroppableMeleeWeapon.AddComponent<Rigidbody>();
|
||||
|
||||
//Apply the AI's current velocity to the weapon object.
|
||||
Rigidbody WeaponRigidbody = DroppableMeleeWeapon.GetComponent<Rigidbody>();
|
||||
WeaponRigidbody.interpolation = RigidbodyInterpolation.Interpolate;
|
||||
|
||||
//Used to provide the force to the AI's weapon in the opposite direction of the last target to hit the AI.
|
||||
Transform LastAttacker = EmeraldComponent.CombatComponent.LastAttacker;
|
||||
|
||||
if (EmeraldComponent.CombatTarget != null)
|
||||
{
|
||||
if (LastAttacker != null && LastAttacker != EmeraldComponent.CombatTarget)
|
||||
WeaponRigidbody.AddForce((LastAttacker.position - transform.position).normalized * EmeraldComponent.CombatComponent.ReceivedRagdollForceAmount * 0.01f, ForceMode.Impulse);
|
||||
else
|
||||
WeaponRigidbody.AddForce((EmeraldComponent.CombatTarget.position - transform.position).normalized * EmeraldComponent.CombatComponent.ReceivedRagdollForceAmount * 0.01f, ForceMode.Impulse);
|
||||
}
|
||||
|
||||
//Disable the held object
|
||||
Type1EquippableWeapons[i].HeldObject.SetActive(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("The AI '" + gameObject.name + "' does not have a held object for the '" + Type1EquippableWeapons[i].DroppableObject.name + "' Droppable Object (Type 1) with the Items Component, please assign one.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < Type2EquippableWeapons.Count; i++)
|
||||
{
|
||||
if (Type2EquippableWeapons[i].DroppableObject != null && Type2EquippableWeapons[i].DroppableToggle)
|
||||
{
|
||||
if (Type2EquippableWeapons[i].HeldObject != null)
|
||||
{
|
||||
//Instantiate a copy of the DroppableObject and position it to HeldObject
|
||||
var DroppableMeleeWeapon = EmeraldObjectPool.Spawn(Type2EquippableWeapons[i].DroppableObject, Type2EquippableWeapons[i].HeldObject.transform.position, Type2EquippableWeapons[i].HeldObject.transform.rotation);
|
||||
DroppableMeleeWeapon.transform.localScale = Type2EquippableWeapons[i].HeldObject.transform.lossyScale;
|
||||
DroppableMeleeWeapon.transform.SetParent(Type2EquippableWeapons[i].HeldObject.transform.parent);
|
||||
DroppableMeleeWeapon.transform.localPosition = Type2EquippableWeapons[i].HeldObject.transform.localPosition;
|
||||
DroppableMeleeWeapon.gameObject.name = Type2EquippableWeapons[i].HeldObject.gameObject.name + " (Droppable Copy)";
|
||||
DroppableMeleeWeapon.transform.SetParent(EmeraldSystem.ObjectPool.transform);
|
||||
|
||||
//Check for a collider on the WeaponObject, if there isn't one, add one.
|
||||
if (DroppableMeleeWeapon.GetComponent<Collider>() == null)
|
||||
DroppableMeleeWeapon.AddComponent<BoxCollider>();
|
||||
|
||||
if (DroppableMeleeWeapon.GetComponent<Rigidbody>() == null)
|
||||
DroppableMeleeWeapon.AddComponent<Rigidbody>();
|
||||
|
||||
//Apply the AI's current velocity to the weapon object.
|
||||
Rigidbody WeaponRigidbody = DroppableMeleeWeapon.GetComponent<Rigidbody>();
|
||||
WeaponRigidbody.interpolation = RigidbodyInterpolation.Interpolate;
|
||||
|
||||
//Used to provide the force to the AI's weapon in the opposite direction of the last target to hit the AI.
|
||||
Transform LastAttacker = EmeraldComponent.CombatComponent.LastAttacker;
|
||||
|
||||
if (EmeraldComponent.CombatTarget != null)
|
||||
{
|
||||
if (LastAttacker != null && LastAttacker != EmeraldComponent.CombatTarget)
|
||||
WeaponRigidbody.AddForce((LastAttacker.position - transform.position).normalized * -EmeraldComponent.CombatComponent.ReceivedRagdollForceAmount, ForceMode.Impulse);
|
||||
else
|
||||
WeaponRigidbody.AddForce((EmeraldComponent.CombatTarget.position - transform.position).normalized * -EmeraldComponent.CombatComponent.ReceivedRagdollForceAmount, ForceMode.Impulse);
|
||||
}
|
||||
|
||||
//Disable the held object
|
||||
Type2EquippableWeapons[i].HeldObject.SetActive(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("The AI '" + gameObject.name + "' does not have a held object for the '" + Type2EquippableWeapons[i].DroppableObject.name + "' Droppable Object (Type 2) with the Items Component, please assign one.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables the AI's weapon object and plays the AI's equip sound effect, if one is applied.
|
||||
/// </summary>
|
||||
public void EquipWeapon(string WeaponTypeToEnable)
|
||||
{
|
||||
EmeraldSystem EmeraldComponent = GetComponent<EmeraldSystem>();
|
||||
|
||||
if (WeaponTypeToEnable == "Weapon Type 1")
|
||||
{
|
||||
for (int i = 0; i < Type1EquippableWeapons.Count; i++)
|
||||
{
|
||||
if (!Type1EquippableWeapons[i].HolsteredToggle) return;
|
||||
|
||||
if (Type1EquippableWeapons[i].HeldObject != null) Type1EquippableWeapons[i].HeldObject.SetActive(true); //Enable the held weapon
|
||||
if (Type1EquippableWeapons[i].HolsteredObject != null) Type1EquippableWeapons[i].HolsteredObject.SetActive(false); //Disable the holstered weapon
|
||||
}
|
||||
|
||||
OnEquipWeapon?.Invoke(WeaponTypeToEnable); //Invoke the OnEquipWeapon event.
|
||||
}
|
||||
else if (WeaponTypeToEnable == "Weapon Type 2")
|
||||
{
|
||||
for (int i = 0; i < Type2EquippableWeapons.Count; i++)
|
||||
{
|
||||
if (!Type2EquippableWeapons[i].HolsteredToggle) return;
|
||||
|
||||
if (Type2EquippableWeapons[i].HeldObject != null) Type2EquippableWeapons[i].HeldObject.SetActive(true); //Enable the held weapon
|
||||
if (Type2EquippableWeapons[i].HolsteredObject != null) Type2EquippableWeapons[i].HolsteredObject.SetActive(false); //Disable the holstered weapon
|
||||
}
|
||||
|
||||
OnEquipWeapon?.Invoke(WeaponTypeToEnable); //Invoke the OnEquipWeapon event.
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("This string withing the EquipWeapon Animation Event is blank or incorrect. Ensure that it's either Weapon Type 1 or Weapon Type 2.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disables the AI's weapon object and plays the AI's unequip sound effect, if one is applied.
|
||||
/// </summary>
|
||||
public void UnequipWeapon(string WeaponTypeToDisable)
|
||||
{
|
||||
EmeraldSystem EmeraldComponent = GetComponent<EmeraldSystem>();
|
||||
|
||||
if (WeaponTypeToDisable == "Weapon Type 1")
|
||||
{
|
||||
for (int i = 0; i < Type1EquippableWeapons.Count; i++)
|
||||
{
|
||||
if (!Type1EquippableWeapons[i].HolsteredToggle) return;
|
||||
|
||||
if (Type1EquippableWeapons[i].HeldObject != null) Type1EquippableWeapons[i].HeldObject.SetActive(false); //Disable the held weapon
|
||||
if (Type1EquippableWeapons[i].HolsteredObject != null) Type1EquippableWeapons[i].HolsteredObject.SetActive(true); //Enable the holstered weapon
|
||||
}
|
||||
|
||||
OnUnequipWeapon?.Invoke(WeaponTypeToDisable); //Invoke the OnUnequipWeapon event.
|
||||
}
|
||||
else if (WeaponTypeToDisable == "Weapon Type 2")
|
||||
{
|
||||
for (int i = 0; i < Type2EquippableWeapons.Count; i++)
|
||||
{
|
||||
if (!Type2EquippableWeapons[i].HolsteredToggle) return;
|
||||
|
||||
if (Type2EquippableWeapons[i].HeldObject != null) Type2EquippableWeapons[i].HeldObject.SetActive(false); //Disable the held weapon
|
||||
if (Type2EquippableWeapons[i].HolsteredObject != null) Type2EquippableWeapons[i].HolsteredObject.SetActive(true); //Enable the holstered weapon
|
||||
}
|
||||
|
||||
OnUnequipWeapon?.Invoke(WeaponTypeToDisable); //Invoke the OnUnequipWeapon event.
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("This string withing the UnequipWeapon Animation Event is blank or incorrect. Ensure that it's either Type 2 or Type 1.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables an item from your AI's Item list using the Item ID.
|
||||
/// </summary>
|
||||
public void EnableItem(int ItemID)
|
||||
{
|
||||
//Look through each item in the ItemList for the appropriate ID.
|
||||
//Once found, enable the item of the same index as the found ID.
|
||||
for (int i = 0; i < ItemList.Count; i++)
|
||||
{
|
||||
if (ItemList[i].ItemID == ItemID)
|
||||
{
|
||||
if (ItemList[i].ItemObject != null)
|
||||
{
|
||||
ItemList[i].ItemObject.SetActive(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disables an item from your AI's Item list using the Item ID.
|
||||
/// </summary>
|
||||
public void DisableItem(int ItemID)
|
||||
{
|
||||
//Look through each item in the ItemList for the appropriate ID.
|
||||
//Once found, enable the item of the same index as the found ID.
|
||||
for (int i = 0; i < ItemList.Count; i++)
|
||||
{
|
||||
if (ItemList[i].ItemID == ItemID)
|
||||
{
|
||||
if (ItemList[i].ItemObject != null)
|
||||
{
|
||||
ItemList[i].ItemObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disables all items from your AI's Item list.
|
||||
/// </summary>
|
||||
public void DisableAllItems()
|
||||
{
|
||||
//Disable all of an AI's items
|
||||
for (int i = 0; i < ItemList.Count; i++)
|
||||
{
|
||||
if (ItemList[i].ItemObject != null)
|
||||
{
|
||||
ItemList[i].ItemObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset any equipped items to their defaults.
|
||||
/// </summary>
|
||||
public void ResetSettings()
|
||||
{
|
||||
EmeraldSystem EmeraldComponent = GetComponent<EmeraldSystem>();
|
||||
|
||||
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1)
|
||||
{
|
||||
for (int i = 0; i < Type1EquippableWeapons.Count; i++)
|
||||
{
|
||||
if (Type1EquippableWeapons[i].HeldObject != null) Type1EquippableWeapons[i].HeldObject.SetActive(false); //Disable the held weapon
|
||||
if (Type1EquippableWeapons[i].HolsteredObject != null) Type1EquippableWeapons[i].HolsteredObject.SetActive(true); //Enable the holstered weapon
|
||||
}
|
||||
}
|
||||
else if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2)
|
||||
{
|
||||
for (int i = 0; i < Type2EquippableWeapons.Count; i++)
|
||||
{
|
||||
if (Type2EquippableWeapons[i].HeldObject != null) Type2EquippableWeapons[i].HeldObject.SetActive(false); //Disable the held weapon
|
||||
if (Type2EquippableWeapons[i].HolsteredObject != null) Type2EquippableWeapons[i].HolsteredObject.SetActive(true); //Enable the holstered weapon
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void EnableWeaponCollider(string Name)
|
||||
{
|
||||
EmeraldSystem EmeraldComponent = GetComponent<EmeraldSystem>();
|
||||
|
||||
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1)
|
||||
{
|
||||
for (int i = 0; i < Type1EquippableWeapons.Count; i++)
|
||||
{
|
||||
if (Type1EquippableWeapons[i].HeldObject.GetComponent<EmeraldWeaponCollision>() != null)
|
||||
{
|
||||
Type1EquippableWeapons[i].HeldObject.GetComponent<EmeraldWeaponCollision>().EnableWeaponCollider(Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2)
|
||||
{
|
||||
for (int i = 0; i < Type2EquippableWeapons.Count; i++)
|
||||
{
|
||||
if (Type2EquippableWeapons[i].HeldObject.GetComponent<EmeraldWeaponCollision>() != null)
|
||||
{
|
||||
Type2EquippableWeapons[i].HeldObject.GetComponent<EmeraldWeaponCollision>().EnableWeaponCollider(Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DisableWeaponCollider(string Name)
|
||||
{
|
||||
EmeraldSystem EmeraldComponent = GetComponent<EmeraldSystem>();
|
||||
|
||||
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1)
|
||||
{
|
||||
for (int i = 0; i < Type1EquippableWeapons.Count; i++)
|
||||
{
|
||||
if (Type1EquippableWeapons[i].HeldObject.GetComponent<EmeraldWeaponCollision>() != null)
|
||||
{
|
||||
Type1EquippableWeapons[i].HeldObject.GetComponent<EmeraldWeaponCollision>().DisableWeaponCollider(Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2)
|
||||
{
|
||||
for (int i = 0; i < Type2EquippableWeapons.Count; i++)
|
||||
{
|
||||
if (Type2EquippableWeapons[i].HeldObject.GetComponent<EmeraldWeaponCollision>() != null)
|
||||
{
|
||||
Type2EquippableWeapons[i].HeldObject.GetComponent<EmeraldWeaponCollision>().DisableWeaponCollider(Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 47eaa6fcdf0a9a64a9cbfd56d640ab33
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,204 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using EmeraldAI.Utility;
|
||||
using UnityEditor;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[HelpURL("https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-optional/optimization-component")]
|
||||
public class EmeraldOptimization : MonoBehaviour
|
||||
{
|
||||
public bool HideSettingsFoldout;
|
||||
public bool OptimizationFoldout;
|
||||
public enum OptimizedStates { Active = 0, Inactive = 1 };
|
||||
public OptimizedStates OptimizedState = OptimizedStates.Inactive;
|
||||
public enum TotalLODsEnum { One = 1, Two = 2, Three = 3, Four = 4 };
|
||||
public TotalLODsEnum TotalLODsRef = TotalLODsEnum.Three;
|
||||
public YesOrNo OptimizeAI = YesOrNo.Yes;
|
||||
public YesOrNo UseDeactivateDelay = YesOrNo.No;
|
||||
public enum MeshTypes { SingleMesh, LODGroup};
|
||||
public MeshTypes MeshType = MeshTypes.SingleMesh;
|
||||
public Renderer AIRenderer;
|
||||
public Renderer Renderer1;
|
||||
public Renderer Renderer2;
|
||||
public Renderer Renderer3;
|
||||
public Renderer Renderer4;
|
||||
public VisibilityCheck m_VisibilityCheck;
|
||||
public int DeactivateDelay = 5;
|
||||
public bool Initialized;
|
||||
|
||||
EmeraldSystem EmeraldComponent;
|
||||
|
||||
|
||||
void Start()
|
||||
{
|
||||
InitializeOptimizationSettings();
|
||||
StartCoroutine(Initialize());
|
||||
}
|
||||
|
||||
IEnumerator Initialize ()
|
||||
{
|
||||
while (Time.time < 0.5f)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
Initialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the optimization settings.
|
||||
/// </summary>
|
||||
public void InitializeOptimizationSettings()
|
||||
{
|
||||
if (OptimizeAI == YesOrNo.Yes)
|
||||
{
|
||||
EmeraldComponent = GetComponent<EmeraldSystem>();
|
||||
|
||||
if (OptimizeAI == YesOrNo.Yes && MeshType == MeshTypes.SingleMesh)
|
||||
{
|
||||
if (AIRenderer != null && UseDeactivateDelay == YesOrNo.No)
|
||||
{
|
||||
DeactivateDelay = 0;
|
||||
m_VisibilityCheck = AIRenderer.gameObject.AddComponent<VisibilityCheck>();
|
||||
m_VisibilityCheck.EmeraldComponent = EmeraldComponent;
|
||||
m_VisibilityCheck.EmeraldOptimization = this;
|
||||
}
|
||||
else if (AIRenderer != null && UseDeactivateDelay == YesOrNo.Yes)
|
||||
{
|
||||
m_VisibilityCheck = AIRenderer.gameObject.AddComponent<VisibilityCheck>();
|
||||
m_VisibilityCheck.EmeraldComponent = EmeraldComponent;
|
||||
m_VisibilityCheck.EmeraldOptimization = this;
|
||||
}
|
||||
else if (MeshType == MeshTypes.SingleMesh && AIRenderer == null)
|
||||
{
|
||||
OptimizeAI = YesOrNo.No;
|
||||
}
|
||||
}
|
||||
|
||||
if (MeshType == MeshTypes.LODGroup)
|
||||
{
|
||||
GetLODs();
|
||||
|
||||
if (TotalLODsRef == TotalLODsEnum.One)
|
||||
{
|
||||
if (Renderer1 == null)
|
||||
{
|
||||
OptimizeAI = YesOrNo.No;
|
||||
MeshType = MeshTypes.SingleMesh;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_VisibilityCheck = Renderer1.gameObject.AddComponent<VisibilityCheck>();
|
||||
m_VisibilityCheck.EmeraldComponent = EmeraldComponent;
|
||||
m_VisibilityCheck.EmeraldOptimization = this;
|
||||
}
|
||||
}
|
||||
else if (TotalLODsRef == TotalLODsEnum.Two)
|
||||
{
|
||||
if (Renderer1 == null || Renderer2 == null)
|
||||
{
|
||||
OptimizeAI = YesOrNo.No;
|
||||
MeshType = MeshTypes.SingleMesh;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_VisibilityCheck = Renderer2.gameObject.AddComponent<VisibilityCheck>();
|
||||
m_VisibilityCheck.EmeraldComponent = EmeraldComponent;
|
||||
m_VisibilityCheck.EmeraldOptimization = this;
|
||||
}
|
||||
}
|
||||
else if (TotalLODsRef == TotalLODsEnum.Three)
|
||||
{
|
||||
if (Renderer1 == null || Renderer2 == null || Renderer3 == null)
|
||||
{
|
||||
OptimizeAI = YesOrNo.No;
|
||||
MeshType = MeshTypes.SingleMesh;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_VisibilityCheck = Renderer3.gameObject.AddComponent<VisibilityCheck>();
|
||||
m_VisibilityCheck.EmeraldComponent = EmeraldComponent;
|
||||
m_VisibilityCheck.EmeraldOptimization = this;
|
||||
}
|
||||
}
|
||||
else if (TotalLODsRef == TotalLODsEnum.Four)
|
||||
{
|
||||
if (Renderer1 == null || Renderer2 == null ||
|
||||
Renderer3 == null || Renderer4 == null)
|
||||
{
|
||||
OptimizeAI = YesOrNo.No;
|
||||
MeshType = MeshTypes.SingleMesh;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_VisibilityCheck = Renderer4.gameObject.AddComponent<VisibilityCheck>();
|
||||
m_VisibilityCheck.EmeraldComponent = EmeraldComponent;
|
||||
m_VisibilityCheck.EmeraldOptimization = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (OptimizeAI == YesOrNo.No)
|
||||
{
|
||||
OptimizedState = OptimizedStates.Inactive;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all groups within an AI's LODGroup component.
|
||||
/// </summary>
|
||||
void GetLODs ()
|
||||
{
|
||||
LODGroup _LODGroup = GetComponentInChildren<LODGroup>();
|
||||
|
||||
if (_LODGroup == null)
|
||||
{
|
||||
Debug.LogError("No LOD Group could be found. Please ensure that your AI has an LOD group that has at least 1 levels. The LODGroup Feature has been disabled.");
|
||||
MeshType = MeshTypes.SingleMesh;
|
||||
}
|
||||
else if (_LODGroup != null)
|
||||
{
|
||||
LOD[] AllLODs = _LODGroup.GetLODs();
|
||||
|
||||
if (_LODGroup.lodCount <= 4)
|
||||
{
|
||||
TotalLODsRef = (TotalLODsEnum)(_LODGroup.lodCount);
|
||||
}
|
||||
|
||||
if (_LODGroup.lodCount >= 1)
|
||||
{
|
||||
for (int i = 0; i < _LODGroup.lodCount; i++)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
Renderer1 = AllLODs[i].renderers[0];
|
||||
}
|
||||
if (i == 1)
|
||||
{
|
||||
Renderer2 = AllLODs[i].renderers[0];
|
||||
}
|
||||
if (i == 2)
|
||||
{
|
||||
Renderer3 = AllLODs[i].renderers[0];
|
||||
}
|
||||
if (i == 3)
|
||||
{
|
||||
Renderer4 = AllLODs[i].renderers[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Update ()
|
||||
{
|
||||
//Check all of an AI's LOD renderers, when using the Optimization feature.
|
||||
if (OptimizeAI == YesOrNo.Yes && MeshType == MeshTypes.LODGroup && Initialized)
|
||||
{
|
||||
m_VisibilityCheck.CheckAIRenderers();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b91fbc8656cfaac4ca227b5b515230d5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,255 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using EmeraldAI.Utility;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[HelpURL("https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-optional/ui-component")]
|
||||
public class EmeraldUI : MonoBehaviour
|
||||
{
|
||||
#region Variables
|
||||
public string AIName = "AI Name";
|
||||
public string AITitle = "AI Title";
|
||||
public int AILevel = 1;
|
||||
public bool HideSettingsFoldout;
|
||||
public bool UISettingsFoldout;
|
||||
public bool HealthBarsFoldout;
|
||||
public bool CombatTextFoldout;
|
||||
public bool NameTextFoldout;
|
||||
public bool LevelTextFoldout;
|
||||
public YesOrNo UseCustomHealthBar = YesOrNo.No;
|
||||
public YesOrNo DisplayAIName = YesOrNo.No;
|
||||
public YesOrNo DisplayAITitle = YesOrNo.No;
|
||||
public YesOrNo DisplayAILevel = YesOrNo.No;
|
||||
public YesOrNo UseAINameUIOutlineEffect = YesOrNo.Yes;
|
||||
public YesOrNo UseAILevelUIOutlineEffect = YesOrNo.Yes;
|
||||
public YesOrNo UseCustomFontAIName = YesOrNo.No;
|
||||
public YesOrNo UseCustomFontAILevel = YesOrNo.No;
|
||||
public YesOrNo AutoCreateHealthBars = YesOrNo.No;
|
||||
public Canvas HealthBarCanvasRef;
|
||||
public GameObject HealthBar;
|
||||
public GameObject HealthBarCanvas;
|
||||
public string CameraTag = "MainCamera";
|
||||
public EmeraldHealthBar m_HealthBarComponent;
|
||||
public string UITag = "Player";
|
||||
public LayerMask UILayerMask = 0;
|
||||
public int MaxUIScaleSize = 16;
|
||||
public Text AINameUI;
|
||||
public Font AINameFont;
|
||||
public float AINameLineSpacing = 0.75f;
|
||||
public Vector2 AINameUIOutlineSize = new Vector2(0.35f, -0.35f);
|
||||
public Color AINameUIOutlineColor = Color.black;
|
||||
public Text AILevelUI;
|
||||
public Font AILevelFont;
|
||||
public Vector2 AILevelUIOutlineSize = new Vector2(0.35f, -0.35f);
|
||||
public Color AILevelUIOutlineColor = Color.black;
|
||||
public Sprite HealthBarImage;
|
||||
public Sprite HealthBarBackgroundImage;
|
||||
public Vector3 HealthBarPos = new Vector3(0, 1.75f, 0);
|
||||
public Color HealthBarColor = new Color32(197, 41, 41, 255);
|
||||
public Color HealthBarDamageColor = new Color32(248, 217, 4, 255);
|
||||
public Color HealthBarBackgroundColor = new Color32(36, 36, 36, 255);
|
||||
public Color NameTextColor = new Color32(255, 255, 255, 255);
|
||||
public Color LevelTextColor = new Color32(255, 255, 255, 255);
|
||||
public Vector3 HealthBarScale = new Vector3(0.75f, 1, 1);
|
||||
public int NameTextFontSize = 7;
|
||||
public GameObject WaypointParent;
|
||||
public string WaypointOrigin;
|
||||
public Vector3 AINamePos = new Vector3(0, 3, 0);
|
||||
public Vector3 AILevelPos = new Vector3(1.5f, 0, 0);
|
||||
EmeraldSystem EmeraldComponent;
|
||||
#endregion
|
||||
|
||||
void Start()
|
||||
{
|
||||
InitializeUI(); //Initialize the EmeraldUI script.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the UI settings.
|
||||
/// </summary>
|
||||
void InitializeUI ()
|
||||
{
|
||||
EmeraldComponent = GetComponent<EmeraldSystem>();
|
||||
EmeraldComponent.DetectionComponent.OnDetectionUpdate += UpdateAIUI; //Subscribe to the OnDetectionUpdate event for updating the UI's state.
|
||||
|
||||
if (AutoCreateHealthBars == YesOrNo.Yes && HealthBarCanvas == null || DisplayAIName == YesOrNo.Yes && HealthBarCanvas == null)
|
||||
{
|
||||
HealthBarCanvas = Resources.Load("AI Health Bar Canvas") as GameObject;
|
||||
}
|
||||
|
||||
if (AutoCreateHealthBars == YesOrNo.Yes && HealthBarCanvas != null || DisplayAIName == YesOrNo.Yes && HealthBarCanvas != null)
|
||||
{
|
||||
HealthBar = Instantiate(HealthBarCanvas, Vector3.zero, Quaternion.identity) as GameObject;
|
||||
GameObject HealthBarParent = new GameObject();
|
||||
HealthBarParent.name = "HealthBarParent";
|
||||
HealthBarParent.transform.SetParent(this.transform);
|
||||
HealthBarParent.transform.localPosition = new Vector3(0, 0, 0);
|
||||
|
||||
HealthBar.transform.SetParent(HealthBarParent.transform);
|
||||
HealthBar.transform.localPosition = HealthBarPos;
|
||||
HealthBar.AddComponent<EmeraldHealthBar>();
|
||||
EmeraldHealthBar HealthBarScript = HealthBar.GetComponent<EmeraldHealthBar>();
|
||||
m_HealthBarComponent = HealthBarScript;
|
||||
HealthBar.name = "AI Health Bar Canvas";
|
||||
|
||||
GameObject HealthBarChild = HealthBar.transform.Find("AI Health Bar Background").gameObject;
|
||||
HealthBarChild.transform.localScale = HealthBarScale;
|
||||
|
||||
Image HealthBarRef = HealthBarChild.transform.Find("AI Health Bar").GetComponent<Image>();
|
||||
HealthBarRef.color = HealthBarColor;
|
||||
|
||||
Image HealthBarDamageRef = HealthBarChild.transform.Find("AI Health Bar (Damage)").GetComponent<Image>();
|
||||
HealthBarDamageRef.color = HealthBarDamageColor;
|
||||
|
||||
Image HealthBarBackgroundImageRef = HealthBarChild.GetComponent<Image>();
|
||||
HealthBarBackgroundImageRef.color = HealthBarBackgroundColor;
|
||||
|
||||
HealthBarCanvasRef = HealthBar.GetComponent<Canvas>();
|
||||
|
||||
if (AutoCreateHealthBars == YesOrNo.No)
|
||||
{
|
||||
HealthBarChild.GetComponent<Image>().enabled = false;
|
||||
HealthBarRef.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
if (UseCustomHealthBar == YesOrNo.Yes && HealthBarBackgroundImage != null && HealthBarImage != null)
|
||||
{
|
||||
HealthBarBackgroundImageRef.sprite = HealthBarBackgroundImage;
|
||||
HealthBarRef.sprite = HealthBarImage;
|
||||
}
|
||||
|
||||
//Displays and colors our AI's name text, if enabled.
|
||||
if (DisplayAIName == YesOrNo.Yes)
|
||||
{
|
||||
AINameUI = HealthBar.transform.Find("AI Name Text").gameObject.GetComponent<Text>();
|
||||
|
||||
if (UseAINameUIOutlineEffect == YesOrNo.Yes)
|
||||
{
|
||||
Outline AINameOutline = AINameUI.GetComponent<Outline>();
|
||||
AINameOutline.effectDistance = AINameUIOutlineSize;
|
||||
AINameOutline.effectColor = AINameUIOutlineColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
AINameUI.GetComponent<Outline>().enabled = false;
|
||||
}
|
||||
|
||||
if (DisplayAITitle == YesOrNo.Yes)
|
||||
{
|
||||
AIName = AIName + "\\n" + AITitle;
|
||||
AIName = AIName.Replace("\\n", "\n");
|
||||
AINamePos.y += 0.25f;
|
||||
|
||||
if (UseAINameUIOutlineEffect == YesOrNo.Yes)
|
||||
AINameUI.lineSpacing = AINameLineSpacing;
|
||||
}
|
||||
|
||||
AINameUI.transform.localPosition = new Vector3(AINamePos.x, AINamePos.y - HealthBarPos.y, AINamePos.z);
|
||||
AINameUI.text = AIName;
|
||||
AINameUI.fontSize = NameTextFontSize;
|
||||
AINameUI.color = NameTextColor;
|
||||
|
||||
if (UseCustomFontAIName == YesOrNo.Yes)
|
||||
AINameUI.font = AINameFont;
|
||||
}
|
||||
|
||||
//Displays and colors our AI's level text, if enabled.
|
||||
if (DisplayAILevel == YesOrNo.Yes)
|
||||
{
|
||||
AILevelUI = HealthBar.transform.Find("AI Level Text").gameObject.GetComponent<Text>();
|
||||
AILevelUI.text = " " + AILevel.ToString();
|
||||
AILevelUI.color = LevelTextColor;
|
||||
AILevelUI.transform.localPosition = new Vector3(AILevelPos.x, AILevelPos.y, AILevelPos.z);
|
||||
|
||||
if (UseCustomFontAILevel == YesOrNo.Yes)
|
||||
AILevelUI.font = AILevelFont;
|
||||
|
||||
if (UseAINameUIOutlineEffect == YesOrNo.Yes)
|
||||
{
|
||||
Outline AINameOutline = AINameUI.GetComponent<Outline>();
|
||||
AINameOutline.effectDistance = AINameUIOutlineSize;
|
||||
AINameOutline.effectColor = AINameUIOutlineColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
AILevelUI.GetComponent<Outline>().enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
HealthBarCanvasRef.enabled = false;
|
||||
if (AutoCreateHealthBars == YesOrNo.No)
|
||||
{
|
||||
HealthBarBackgroundImageRef.gameObject.SetActive(false);
|
||||
}
|
||||
if (AINameUI != null && DisplayAIName == YesOrNo.Yes)
|
||||
{
|
||||
AINameUI.gameObject.SetActive(false);
|
||||
}
|
||||
if (AILevelUI != null && DisplayAILevel == YesOrNo.Yes)
|
||||
{
|
||||
AILevelUI.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the AI's UI state, if it's enabled.
|
||||
/// </summary>
|
||||
void UpdateAIUI()
|
||||
{
|
||||
if (AutoCreateHealthBars == YesOrNo.Yes || DisplayAIName == YesOrNo.Yes)
|
||||
{
|
||||
Collider[] CurrentlyDetectedTargets = Physics.OverlapSphere(transform.position, EmeraldComponent.DetectionComponent.DetectionRadius, UILayerMask);
|
||||
if (CurrentlyDetectedTargets.Length > 0)
|
||||
{
|
||||
List<Collider> TargetList = new List<Collider>();
|
||||
for (int i = 0; i < CurrentlyDetectedTargets.Length; i++)
|
||||
{
|
||||
if (CurrentlyDetectedTargets[i].CompareTag(EmeraldComponent.DetectionComponent.PlayerTag))
|
||||
{
|
||||
TargetList.Add(CurrentlyDetectedTargets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (TargetList.Count > 0)
|
||||
{
|
||||
SetUI(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetUI(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetUI(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUI(bool Enabled)
|
||||
{
|
||||
if (EmeraldComponent.AnimationComponent.IsDead || HealthBarCanvas == null) return;
|
||||
|
||||
m_HealthBarComponent.CalculateUI();
|
||||
HealthBarCanvasRef.enabled = Enabled;
|
||||
if (AutoCreateHealthBars == YesOrNo.Yes)
|
||||
{
|
||||
HealthBar.SetActive(Enabled);
|
||||
|
||||
if (DisplayAILevel == YesOrNo.Yes)
|
||||
{
|
||||
AILevelUI.gameObject.SetActive(Enabled);
|
||||
}
|
||||
}
|
||||
|
||||
if (DisplayAIName == YesOrNo.Yes)
|
||||
{
|
||||
AINameUI.gameObject.SetActive(Enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f964d14d2a406284fad2ef0b03a2e4d5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,118 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[RequireComponent(typeof(BoxCollider))]
|
||||
[HelpURL("https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-optional/weapon-collisions-component")]
|
||||
public class EmeraldWeaponCollision : MonoBehaviour
|
||||
{
|
||||
public bool HideSettingsFoldout;
|
||||
public bool WeaponCollisionFoldout;
|
||||
public BoxCollider WeaponCollider;
|
||||
public Color CollisionBoxColor = new Color(1, 0.85f, 0, 0.25f);
|
||||
|
||||
public List<Transform> HitTargets = new List<Transform>();
|
||||
|
||||
public bool OnCollision;
|
||||
EmeraldSystem EmeraldComponent;
|
||||
Rigidbody m_Rigidbody;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
EmeraldComponent = GetComponentInParent<EmeraldSystem>();
|
||||
EmeraldComponent.CombatComponent.WeaponColliders.Add(this);
|
||||
EmeraldComponent.AnimationComponent.OnGetHit += DisableWeaponCollider; //Subscribe to the OnGetHit event for canceling weapon colliders during hits.
|
||||
EmeraldComponent.AnimationComponent.OnRecoil += DisableWeaponCollider; //Subscribe to the OnRecoil event for canceling weapon colliders during hits.
|
||||
WeaponCollider = GetComponent<BoxCollider>();
|
||||
WeaponCollider.enabled = false;
|
||||
WeaponCollider.isTrigger = true;
|
||||
if (m_Rigidbody == null) m_Rigidbody = gameObject.AddComponent<Rigidbody>();
|
||||
m_Rigidbody.isKinematic = true;
|
||||
}
|
||||
|
||||
public void EnableWeaponCollider(string Name)
|
||||
{
|
||||
if (gameObject.name == Name)
|
||||
{
|
||||
if (gameObject.GetComponent<Collider>() == null)
|
||||
return;
|
||||
|
||||
WeaponCollider.enabled = true;
|
||||
EmeraldComponent.CombatComponent.CurrentWeaponCollision = this;
|
||||
}
|
||||
}
|
||||
|
||||
public void DisableWeaponCollider(string Name)
|
||||
{
|
||||
if (gameObject.name == Name)
|
||||
{
|
||||
if (gameObject.GetComponent<Collider>() == null)
|
||||
return;
|
||||
|
||||
WeaponCollider.enabled = false;
|
||||
EmeraldComponent.CombatComponent.CurrentWeaponCollision = null;
|
||||
HitTargets.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
void DisableWeaponCollider ()
|
||||
{
|
||||
if (WeaponCollider.enabled)
|
||||
{
|
||||
WeaponCollider.enabled = false;
|
||||
HitTargets.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider collision)
|
||||
{
|
||||
if (collision.gameObject != EmeraldComponent.gameObject)
|
||||
{
|
||||
if (collision.gameObject.GetComponent<LocationBasedDamageArea>() != null || collision.gameObject.GetComponent<IDamageable>() != null)
|
||||
{
|
||||
if (EmeraldComponent.LBDComponent != null && !EmeraldComponent.LBDComponent.ColliderList.Exists(x => x.ColliderObject == collision))
|
||||
{
|
||||
DamageTarget(collision.gameObject);
|
||||
}
|
||||
else if (EmeraldComponent.LBDComponent == null)
|
||||
{
|
||||
DamageTarget(collision.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Damages the target that collided with the weapon, given that it has a IDamageable.
|
||||
/// </summary>
|
||||
void DamageTarget(GameObject Target)
|
||||
{
|
||||
var m_MeleeAbility = (MeleeAbility)EmeraldComponent.CombatComponent.CurrentEmeraldAIAbility;
|
||||
if (m_MeleeAbility != null)
|
||||
{
|
||||
Transform TargetRoot = m_MeleeAbility.GetTargetRoot(Target);
|
||||
|
||||
if (TargetRoot != null && !HitTargets.Contains(TargetRoot))
|
||||
{
|
||||
m_MeleeAbility.MeleeDamage(EmeraldComponent.gameObject, Target, TargetRoot);
|
||||
HitTargets.Add(TargetRoot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
if (WeaponCollider == null)
|
||||
return;
|
||||
|
||||
if (WeaponCollider.enabled)
|
||||
{
|
||||
Gizmos.color = CollisionBoxColor;
|
||||
Gizmos.matrix = Matrix4x4.TRS(transform.TransformPoint(WeaponCollider.center), transform.rotation, transform.lossyScale);
|
||||
Gizmos.DrawCube(Vector3.zero, WeaponCollider.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4684f27df27199d40a99e1fd0dcec69b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,153 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
public class LocationBasedDamage : MonoBehaviour
|
||||
{
|
||||
#region Variables
|
||||
public List<Collider> IgnoreLineOfSight = new List<Collider>();
|
||||
public int LBDComponentsLayer;
|
||||
public int DeadLBDComponentsLayer;
|
||||
public bool SetCollidersLayerAndTag = true;
|
||||
public string LBDComponentsTag = "Untagged";
|
||||
EmeraldSystem EmeraldComponent;
|
||||
public bool LBDSettingsFoldout = true;
|
||||
public bool HideSettingsFoldout;
|
||||
[SerializeField]
|
||||
public List<LocationBasedDamageClass> ColliderList = new List<LocationBasedDamageClass>();
|
||||
[System.Serializable]
|
||||
public class LocationBasedDamageClass
|
||||
{
|
||||
public Collider ColliderObject;
|
||||
public float DamageMultiplier = 1;
|
||||
public Vector3 BonePosition;
|
||||
public Quaternion BoneRotation;
|
||||
|
||||
public LocationBasedDamageClass(Collider m_ColliderObject, int m_DamageMultiplier)
|
||||
{
|
||||
ColliderObject = m_ColliderObject;
|
||||
DamageMultiplier = m_DamageMultiplier;
|
||||
}
|
||||
|
||||
public static bool Contains(List<LocationBasedDamageClass> m_LocationBasedDamageList, LocationBasedDamageClass m_LocationBasedDamageClass)
|
||||
{
|
||||
foreach (LocationBasedDamageClass lbdc in m_LocationBasedDamageList)
|
||||
{
|
||||
return (lbdc.ColliderObject == m_LocationBasedDamageClass.ColliderObject);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void Start()
|
||||
{
|
||||
InitializeLocationBasedDamage();
|
||||
}
|
||||
|
||||
public void InitializeLocationBasedDamage()
|
||||
{
|
||||
EmeraldComponent = GetComponent<EmeraldSystem>();
|
||||
EmeraldComponent.LBDComponent = this;
|
||||
EmeraldComponent.AIBoxCollider.size = new Vector3(0.015f, 0.015f, 0.015f);
|
||||
EmeraldComponent.AIBoxCollider.center = Vector3.zero;
|
||||
EmeraldComponent.AIBoxCollider.center = Vector3.up * transform.localScale.y;
|
||||
EmeraldComponent.AIBoxCollider.isTrigger = true;
|
||||
if (SetCollidersLayerAndTag) EmeraldDetection.LBDLayers |= (1 << LBDComponentsLayer);
|
||||
EmeraldComponent.HealthComponent.OnDeath += InitializeDeathLayer;
|
||||
|
||||
for (int i = 0; i < ColliderList.Count; i++)
|
||||
{
|
||||
if (ColliderList[i].ColliderObject.GetComponent<Rigidbody>() != null)
|
||||
{
|
||||
Rigidbody ColliderRigidbody = ColliderList[i].ColliderObject.GetComponent<Rigidbody>();
|
||||
ColliderRigidbody.useGravity = true;
|
||||
ColliderRigidbody.isKinematic = true;
|
||||
|
||||
//Cache the position and rotation of each collider. These are used later when reusing an already killed AI.
|
||||
ColliderList[i].BonePosition = ColliderRigidbody.position;
|
||||
ColliderList[i].BoneRotation = ColliderRigidbody.rotation;
|
||||
|
||||
LocationBasedDamageArea DamageComponent = ColliderList[i].ColliderObject.gameObject.AddComponent<LocationBasedDamageArea>();
|
||||
DamageComponent.EmeraldComponent = EmeraldComponent;
|
||||
DamageComponent.DamageMultiplier = ColliderList[i].DamageMultiplier;
|
||||
|
||||
//Integrated support for Invector
|
||||
#if INVECTOR_MELEE || INVECTOR_SHOOTER
|
||||
ColliderList[i].ColliderObject.gameObject.AddComponent<Invector.vCharacterController.vDamageReceiver>();
|
||||
#endif
|
||||
|
||||
EmeraldComponent.DetectionComponent.IgnoredColliders.Add(ColliderList[i].ColliderObject);
|
||||
}
|
||||
|
||||
if (SetCollidersLayerAndTag)
|
||||
{
|
||||
ColliderList[i].ColliderObject.gameObject.layer = LBDComponentsLayer;
|
||||
ColliderList[i].ColliderObject.gameObject.tag = LBDComponentsTag;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < IgnoreLineOfSight.Count; i++)
|
||||
{
|
||||
IgnoreLineOfSight[i].gameObject.layer = LayerMask.NameToLayer("Ignore Raycast");
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeDeathLayer ()
|
||||
{
|
||||
if (!SetCollidersLayerAndTag) return;
|
||||
|
||||
for (int i = 0; i < ColliderList.Count; i++)
|
||||
{
|
||||
if (ColliderList[i].ColliderObject.GetComponent<Rigidbody>() != null)
|
||||
{
|
||||
ColliderList[i].ColliderObject.gameObject.layer = DeadLBDComponentsLayer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the LDB Components (called when an AI is reset).
|
||||
/// </summary>
|
||||
public void ResetLBDComponents ()
|
||||
{
|
||||
for (int i = 0; i < ColliderList.Count; i++)
|
||||
{
|
||||
if (ColliderList[i].ColliderObject.GetComponent<Rigidbody>() != null)
|
||||
{
|
||||
StartCoroutine(Reset(ColliderList[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the rigidbody and joint components. The helps prevent the ragdoll from becoming unstable after being reused in a different location.
|
||||
/// </summary>
|
||||
IEnumerator Reset (LocationBasedDamageClass LBDC)
|
||||
{
|
||||
Rigidbody ColliderRigidbody = LBDC.ColliderObject.GetComponent<Rigidbody>();
|
||||
ColliderRigidbody.useGravity = true;
|
||||
ColliderRigidbody.isKinematic = true;
|
||||
if (SetCollidersLayerAndTag) LBDC.ColliderObject.gameObject.layer = LBDComponentsLayer;
|
||||
|
||||
yield return new WaitForSeconds(0.05f);
|
||||
ColliderRigidbody.position = LBDC.BonePosition;
|
||||
yield return new WaitForSeconds(0.05f);
|
||||
ColliderRigidbody.rotation = LBDC.BoneRotation;
|
||||
|
||||
yield return new WaitForSeconds(0.05f);
|
||||
Joint ColliderJoint = LBDC.ColliderObject.GetComponent<Joint>();
|
||||
ColliderJoint.autoConfigureConnectedAnchor = false;
|
||||
yield return new WaitForSeconds(0.05f);
|
||||
ColliderJoint.autoConfigureConnectedAnchor = true;
|
||||
|
||||
yield return new WaitForSeconds(0.05f);
|
||||
LBDC.ColliderObject.gameObject.SetActive(false);
|
||||
yield return new WaitForSeconds(0.05f);
|
||||
LBDC.ColliderObject.gameObject.SetActive(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ea90b83659abaa43ac9e5fa97b9597b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 662ca265f09a49c439ab6343cdec8baa
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,140 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EmeraldAI.SoundDetection
|
||||
{
|
||||
[RequireComponent(typeof(AudioSource))]
|
||||
public class AttractModifier : MonoBehaviour
|
||||
{
|
||||
#region Variables
|
||||
public FactionClass PlayerFaction;
|
||||
public int Radius = 10;
|
||||
public float MinVelocity = 3.5f;
|
||||
public float SoundCooldownSeconds = 1f;
|
||||
public float ReactionCooldownSeconds = 1f;
|
||||
public LayerMask TriggerLayers = ~0; //By default, use all layers for triggering an AttractModifier
|
||||
public LayerMask EmeraldAILayer;
|
||||
public TriggerTypes TriggerType = TriggerTypes.OnCollision;
|
||||
public ReactionObject AttractReaction;
|
||||
public bool EnemyRelationsOnly = true;
|
||||
public List<AudioClip> TriggerSounds = new List<AudioClip>();
|
||||
AudioSource m_AudioSource;
|
||||
bool ReactionTriggered;
|
||||
bool SoundTriggered;
|
||||
#endregion
|
||||
|
||||
#region Editor Variables
|
||||
public bool HideSettingsFoldout;
|
||||
public bool AttractModifierFoldout;
|
||||
#endregion
|
||||
|
||||
void Start()
|
||||
{
|
||||
m_AudioSource = GetComponent<AudioSource>();
|
||||
|
||||
if (TriggerType == TriggerTypes.OnStart)
|
||||
{
|
||||
GetTargets();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the specified reaction during a trigger collision.
|
||||
/// </summary>
|
||||
private void OnTriggerEnter(Collider collision)
|
||||
{
|
||||
if (TriggerType == TriggerTypes.OnTrigger)
|
||||
{
|
||||
GetTargets(((1 << collision.gameObject.layer) & TriggerLayers) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the specified reaction during a collision that meets or exceeds the MinVelocity.
|
||||
/// </summary>
|
||||
private void OnCollisionEnter(Collision collision)
|
||||
{
|
||||
if (TriggerType == TriggerTypes.OnCollision && collision.relativeVelocity.magnitude >= MinVelocity)
|
||||
{
|
||||
GetTargets(((1 << collision.gameObject.layer) & TriggerLayers) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the specified reaction when called (Requries the OnCustomCall TriggerType).
|
||||
/// </summary>
|
||||
public void ActivateAttraction ()
|
||||
{
|
||||
if (TriggerType == TriggerTypes.OnCustomCall)
|
||||
{
|
||||
GetTargets();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find all Emerald AI targets within the specified radius and invoke the AttractReaction.
|
||||
/// </summary>
|
||||
void GetTargets (bool HasTriggerLayer = true)
|
||||
{
|
||||
PlayTriggerSound();
|
||||
|
||||
if (ReactionTriggered || Time.time < 0.5f || !HasTriggerLayer)
|
||||
return;
|
||||
|
||||
Collider[] m_DetectedTargets = Physics.OverlapSphere(transform.position, Radius, EmeraldAILayer);
|
||||
|
||||
if (m_DetectedTargets.Length == 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < m_DetectedTargets.Length; i++)
|
||||
{
|
||||
if (m_DetectedTargets[i].GetComponent<EmeraldSoundDetector>() != null)
|
||||
{
|
||||
EmeraldSystem EmeraldComponent = m_DetectedTargets[i].GetComponent<EmeraldSystem>(); //Cache each EmeraldSystem
|
||||
|
||||
//Don't allow AI with follower targets to use Attract Modifiers.
|
||||
if (EmeraldComponent.TargetToFollow != null) continue;
|
||||
|
||||
//Only allow AI with an Enemy relation to receive Attract Modifiers.
|
||||
if (EnemyRelationsOnly && EmeraldComponent.DetectionComponent.FactionRelationsList.Exists(x => x.FactionIndex == PlayerFaction.FactionIndex && x.RelationType != 0)) continue;
|
||||
|
||||
if (AttractReaction != null)
|
||||
{
|
||||
EmeraldComponent.SoundDetectorComponent.DetectedAttractModifier = gameObject; //Assign the detected Emerald AI agent as the DetectedAttractModifier
|
||||
EmeraldComponent.SoundDetectorComponent.InvokeReactionList(AttractReaction, true); //Invoke the ReactionList.
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("There's no Reaction Object on the " + gameObject.name + "'s AttractReaction slot. Please add one in order for Attract Modifier to work correctly.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ReactionTriggered = true;
|
||||
Invoke("ReactionCooldown", ReactionCooldownSeconds);
|
||||
}
|
||||
|
||||
void PlayTriggerSound ()
|
||||
{
|
||||
if (SoundTriggered || Time.time < 0.5f)
|
||||
return;
|
||||
|
||||
if (TriggerSounds.Count > 0)
|
||||
m_AudioSource.PlayOneShot(TriggerSounds[Random.Range(0, TriggerSounds.Count)]);
|
||||
|
||||
SoundTriggered = true;
|
||||
Invoke("SoundCooldown", SoundCooldownSeconds);
|
||||
}
|
||||
|
||||
void SoundCooldown()
|
||||
{
|
||||
SoundTriggered = false;
|
||||
}
|
||||
|
||||
void ReactionCooldown ()
|
||||
{
|
||||
ReactionTriggered = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ca4b66f947a39e4893c1ff725d34ad0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 31a4af1aed1312c4f8b6a89c62ef840e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,164 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using EmeraldAI.Utility;
|
||||
|
||||
namespace EmeraldAI.SoundDetection.Utility
|
||||
{
|
||||
[System.Serializable]
|
||||
[CustomEditor(typeof(AttractModifier))]
|
||||
public class AttractModifierEditor : Editor
|
||||
{
|
||||
GUIStyle FoldoutStyle;
|
||||
Texture AttractModifierEditorIcon;
|
||||
SerializedProperty PlayerFactionProp, RadiusProp, MinVelocityProp, ReactionCooldownSecondsProp, SoundCooldownSecondsProp, EmeraldAILayerProp, TriggerTypeProp, AttractReactionProp, TriggerLayersProp, EnemyRelationsOnlyProp, HideSettingsFoldout, AttractModifierFoldout;
|
||||
ReorderableList TriggerSoundsList;
|
||||
EmeraldFactionData FactionData;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (AttractModifierEditorIcon == null) AttractModifierEditorIcon = Resources.Load("AttractModifier") as Texture;
|
||||
RadiusProp = serializedObject.FindProperty("Radius");
|
||||
PlayerFactionProp = serializedObject.FindProperty("PlayerFaction.FactionIndex");
|
||||
MinVelocityProp = serializedObject.FindProperty("MinVelocity");
|
||||
ReactionCooldownSecondsProp = serializedObject.FindProperty("ReactionCooldownSeconds");
|
||||
SoundCooldownSecondsProp = serializedObject.FindProperty("SoundCooldownSeconds");
|
||||
EmeraldAILayerProp = serializedObject.FindProperty("EmeraldAILayer");
|
||||
TriggerTypeProp = serializedObject.FindProperty("TriggerType");
|
||||
AttractReactionProp = serializedObject.FindProperty("AttractReaction");
|
||||
TriggerLayersProp = serializedObject.FindProperty("TriggerLayers");
|
||||
EnemyRelationsOnlyProp = serializedObject.FindProperty("EnemyRelationsOnly");
|
||||
HideSettingsFoldout = serializedObject.FindProperty("HideSettingsFoldout");
|
||||
AttractModifierFoldout = serializedObject.FindProperty("AttractModifierFoldout");
|
||||
FactionData = Resources.Load("Faction Data") as EmeraldFactionData;
|
||||
|
||||
//Trigger Sounds
|
||||
TriggerSoundsList = new ReorderableList(serializedObject, serializedObject.FindProperty("TriggerSounds"), true, true, true, true);
|
||||
TriggerSoundsList.drawHeaderCallback = rect =>
|
||||
{
|
||||
EditorGUI.LabelField(rect, "Trigger Sounds List", EditorStyles.boldLabel);
|
||||
};
|
||||
TriggerSoundsList.drawElementCallback =
|
||||
(Rect rect, int index, bool isActive, bool isFocused) =>
|
||||
{
|
||||
var element = TriggerSoundsList.serializedProperty.GetArrayElementAtIndex(index);
|
||||
EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), element, GUIContent.none);
|
||||
};
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
AttractModifier self = (AttractModifier)target;
|
||||
FoldoutStyle = CustomEditorProperties.UpdateEditorStyles();
|
||||
serializedObject.Update();
|
||||
|
||||
CustomEditorProperties.BeginScriptHeaderNew("Attract Modifier", AttractModifierEditorIcon, new GUIContent(), HideSettingsFoldout);
|
||||
|
||||
if (!HideSettingsFoldout.boolValue)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
AttractModifierSettings();
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
CustomEditorProperties.EndScriptHeader();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
void AttractModifierSettings()
|
||||
{
|
||||
AttractModifierFoldout.boolValue = EditorGUILayout.Foldout(AttractModifierFoldout.boolValue, "Attract Modifier Settings", true, FoldoutStyle);
|
||||
|
||||
if (AttractModifierFoldout.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
CustomEditorProperties.TextTitleWithDescription("Attract Modifier Settings", "This system will attract all AI that are within range and invoke the 'Attract Reaction'. The object the Attract Modifier is attached to " +
|
||||
"will be the source of attraction. This system is intended to extend the functionality of the Sound Detection component by allowing certain objects, collisions, and custom calls to attract nearby AI.", true);
|
||||
|
||||
CustomEditorProperties.TutorialButton("For a tutorial on using the Attract Modifier, please see the tutorial below.", "https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-optional/sound-detector-component/using-an-attract-modifier");
|
||||
|
||||
CustomEditorProperties.CustomPropertyField(EmeraldAILayerProp, "Emerald AI Layer", "The Emerald AI layers used by your AI (Only objects with this layer, and that are Emerald AI agents with a Sound Detection component, will be detected).", true);
|
||||
CustomEditorProperties.CustomPropertyField(AttractReactionProp, "Attract Reaction", "The Reaction Object that will be called when this modifier is invoked/triggered " +
|
||||
"(Reaction Objects can be created by right clicking in the project tab and going to Create>Emerald AI>Create>Reaction Object).", true);
|
||||
CustomEditorProperties.CustomPropertyField(EnemyRelationsOnlyProp, "Enemy Relations Only", "Controls whether or not this Attract Modifier will only be received by " +
|
||||
"AI with a Player Relation of Enemy. If set to false, all AI within range will receive this Attract Modifier if it's triggered.", false);
|
||||
|
||||
if (EnemyRelationsOnlyProp.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginIndent();
|
||||
PlayerFactionProp.intValue = EditorGUILayout.Popup("Player Faction", PlayerFactionProp.intValue, FactionData.FactionNameList.ToArray());
|
||||
EditorGUILayout.LabelField("The faction your player uses.", EditorStyles.helpBox);
|
||||
CustomEditorProperties.EndIndent();
|
||||
}
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
CustomEditorProperties.CustomPropertyField(RadiusProp, "Radius", "Controls the range of affect for this Attract Modifier. AI within this range will receive the Reaction Object when this Attract Modifier is triggered.", true);
|
||||
CustomEditorProperties.CustomPropertyField(ReactionCooldownSecondsProp, "Reaction Cooldown Seconds", "The amount of time (in seconds) until the Attract Reaction can be invoked again.", true);
|
||||
CustomEditorProperties.CustomPropertyField(SoundCooldownSecondsProp, "Sound Cooldown Seconds", "The amount of time (in seconds) until the trigger sound can be played again.", true);
|
||||
|
||||
if ((TriggerTypes)TriggerTypeProp.intValue == TriggerTypes.OnCollision)
|
||||
{
|
||||
CustomEditorProperties.CustomPropertyField(MinVelocityProp, "Min Velocity", "The minimum velocity required to invoke the attached Attract Reaction (usable only with Collision Trigger Type).", true);
|
||||
}
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
CustomEditorProperties.CustomPropertyField(TriggerTypeProp, "Trigger Type", "Controls the how the Attract Modifier will be invoked.", false);
|
||||
|
||||
if (TriggerTypeProp.intValue == (int)TriggerTypes.OnStart)
|
||||
{
|
||||
EditorGUILayout.LabelField("OnStart - Invokes the Reaction Object on Start and uses this gameobject as the attraction source.", EditorStyles.helpBox);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
else if (TriggerTypeProp.intValue == (int)TriggerTypes.OnTrigger)
|
||||
{
|
||||
EditorGUILayout.LabelField("OnTrigger - Invokes the Reaction Object when a trigger collision happens with this object. This gameobject as the attraction source.", EditorStyles.helpBox);
|
||||
TriggerLayerMaskDrawer();
|
||||
}
|
||||
else if (TriggerTypeProp.intValue == (int)TriggerTypes.OnCollision)
|
||||
{
|
||||
EditorGUILayout.LabelField("OnCollision - Invokes the Reaction Object when a non-trigger collision happens with this object. This gameobject as the attraction source.", EditorStyles.helpBox);
|
||||
TriggerLayerMaskDrawer();
|
||||
}
|
||||
else if (TriggerTypeProp.intValue == (int)TriggerTypes.OnCustomCall)
|
||||
{
|
||||
EditorGUILayout.LabelField("OnCustomCall - Invokes the Reaction Object when the ActivateAttraction function, located within the AttractModifier script, is called. This gameobject as the attraction source.", EditorStyles.helpBox);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
GUILayout.Space(5);
|
||||
|
||||
EditorGUILayout.LabelField("A random sound from the Trigger Sounds list will be played when the Trigger Type condition is met.", EditorStyles.helpBox);
|
||||
TriggerSoundsList.DoLayoutList();
|
||||
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
}
|
||||
}
|
||||
|
||||
void OnSceneGUI()
|
||||
{
|
||||
AttractModifier self = (AttractModifier)target;
|
||||
Handles.color = new Color(1f, 0f, 0, 1f);
|
||||
Handles.DrawWireDisc(self.transform.position, self.transform.up, (float)self.Radius, 3);
|
||||
}
|
||||
|
||||
void TriggerLayerMaskDrawer ()
|
||||
{
|
||||
CustomEditorProperties.BeginIndent();
|
||||
CustomEditorProperties.CustomPropertyField(TriggerLayersProp, "Trigger Layers", "Controls which collision layers are allowed to trigger this Attract Modifier.", true);
|
||||
|
||||
if (TriggerLayersProp.intValue == 0)
|
||||
{
|
||||
GUI.backgroundColor = new Color(10f, 0.0f, 0.0f, 0.25f);
|
||||
EditorGUILayout.LabelField("The Trigger Layers LayerMask cannot be set to Nothing", EditorStyles.helpBox);
|
||||
GUI.backgroundColor = Color.white;
|
||||
}
|
||||
|
||||
CustomEditorProperties.EndIndent();
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c6ced538672e8c845aa706ace73da55a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using EmeraldAI.Utility;
|
||||
|
||||
namespace EmeraldAI.SoundDetection.Utility
|
||||
{
|
||||
[System.Serializable]
|
||||
[CustomEditor(typeof(EmeraldSoundDetector))]
|
||||
public class EmeraldSoundDetectorEditor : Editor
|
||||
{
|
||||
GUIStyle FoldoutStyle;
|
||||
Texture SoundDetectionEditorIcon;
|
||||
|
||||
SerializedProperty CheckIncrementProp;
|
||||
SerializedProperty MinVelocityThresholdProp;
|
||||
SerializedProperty AttentionRateProp;
|
||||
SerializedProperty AttentionFalloffProp;
|
||||
SerializedProperty AttractModifierCooldownProp;
|
||||
SerializedProperty DelayUnawareSecondsProp;
|
||||
|
||||
SerializedProperty UnawareReactionProp;
|
||||
SerializedProperty SuspiciousReactionProp;
|
||||
SerializedProperty AwareReactionProp;
|
||||
|
||||
SerializedProperty UnawareThreatLevelProp;
|
||||
SerializedProperty SuspiciousThreatLevelProp;
|
||||
SerializedProperty AwareThreatLevelProp;
|
||||
|
||||
SerializedProperty UnawareEventProp;
|
||||
SerializedProperty SuspiciousEventProp;
|
||||
SerializedProperty AwareEventProp;
|
||||
|
||||
SerializedProperty HideSettingsFoldoutProp, SoundDetectorFoldoutProp, UnawareFoldoutProp, SuspiciousFoldoutProp, AwareFoldoutProp;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (SoundDetectionEditorIcon == null) SoundDetectionEditorIcon = Resources.Load("Editor Icons/EmeraldSoundDetector") as Texture;
|
||||
|
||||
CheckIncrementProp = serializedObject.FindProperty("CheckIncrement");
|
||||
MinVelocityThresholdProp = serializedObject.FindProperty("MinVelocityThreshold");
|
||||
AttentionRateProp = serializedObject.FindProperty("AttentionRate");
|
||||
AttentionFalloffProp = serializedObject.FindProperty("AttentionFalloff");
|
||||
DelayUnawareSecondsProp = serializedObject.FindProperty("DelayUnawareSeconds");
|
||||
AttractModifierCooldownProp = serializedObject.FindProperty("AttractModifierCooldown");
|
||||
|
||||
UnawareThreatLevelProp = serializedObject.FindProperty("UnawareThreatLevel");
|
||||
SuspiciousThreatLevelProp = serializedObject.FindProperty("SuspiciousThreatLevel");
|
||||
AwareThreatLevelProp = serializedObject.FindProperty("AwareThreatLevel");
|
||||
|
||||
UnawareEventProp = serializedObject.FindProperty("UnawareEvent");
|
||||
SuspiciousEventProp = serializedObject.FindProperty("SuspiciousEvent");
|
||||
AwareEventProp = serializedObject.FindProperty("AwareEvent");
|
||||
|
||||
UnawareReactionProp = serializedObject.FindProperty("UnawareReaction");
|
||||
SuspiciousReactionProp = serializedObject.FindProperty("SuspiciousReaction");
|
||||
AwareReactionProp = serializedObject.FindProperty("AwareReaction");
|
||||
|
||||
HideSettingsFoldoutProp = serializedObject.FindProperty("HideSettingsFoldout");
|
||||
SoundDetectorFoldoutProp = serializedObject.FindProperty("SoundDetectorFoldout");
|
||||
UnawareFoldoutProp = serializedObject.FindProperty("UnawareFoldout");
|
||||
SuspiciousFoldoutProp = serializedObject.FindProperty("SuspiciousFoldout");
|
||||
AwareFoldoutProp = serializedObject.FindProperty("AwareFoldout");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
FoldoutStyle = CustomEditorProperties.UpdateEditorStyles();
|
||||
EmeraldSoundDetector self = (EmeraldSoundDetector)target;
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
CustomEditorProperties.BeginScriptHeaderNew("Sound Detector", SoundDetectionEditorIcon, new GUIContent(), HideSettingsFoldoutProp);
|
||||
|
||||
if (!HideSettingsFoldoutProp.boolValue)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
SoundDetectorSettings(self);
|
||||
EditorGUILayout.Space();
|
||||
UnawareSettings();
|
||||
EditorGUILayout.Space();
|
||||
SuspiciousSettings();
|
||||
EditorGUILayout.Space();
|
||||
AwareSettings();
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
CustomEditorProperties.EndScriptHeader();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
void SoundDetectorSettings (EmeraldSoundDetector self)
|
||||
{
|
||||
SoundDetectorFoldoutProp.boolValue = EditorGUILayout.Foldout(SoundDetectorFoldoutProp.boolValue, "Sound Detector Settings", true, FoldoutStyle);
|
||||
|
||||
if (SoundDetectorFoldoutProp.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
CustomEditorProperties.TextTitleWithDescription("Sound Detector Settings", "The Sound Detector component gives AI the ability to hear player targets and other sounds made by external sources. When these events happen, " +
|
||||
"it will trigger Reaction Objects that will determine what the AI does. These Reaction Objects can be customized by the user.", false);
|
||||
EditorGUILayout.HelpBox("AI will only listen for player targets. The tags and layers used for this are based on this AI's Emerald AI settings from its Detection Settings.", MessageType.Info); //TODO: Replace with CustomEditorProperties equivalent
|
||||
GUILayout.Space(10);
|
||||
|
||||
DisplayThreatLevel(self);
|
||||
|
||||
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), CheckIncrementProp, "Check Increment", 0.0f, 1f);
|
||||
CustomHelpLabelField("Controls how often sound detecting calculations are made.", true);
|
||||
|
||||
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), MinVelocityThresholdProp, "Min Velocity Threshold", 0.05f, 10f);
|
||||
CustomHelpLabelField("Controls the minimum detected velocity 'sound'. Any amount lower than this will be handled by the Attention Falloff and will not be detected.", true);
|
||||
|
||||
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), AttentionRateProp, "Attention Rate", 0.0025f, 1.0f);
|
||||
CustomHelpLabelField("Controls how quickly an AI's Current Threat Amount will increase, given that any detected targets' velocity is at or above the Min Velocity Threshold.", true);
|
||||
|
||||
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), AttentionFalloffProp, "Attention Fall off", 0.0025f, 1.0f);
|
||||
CustomHelpLabelField("Controls how quickly an AI's Current Threat Amount will decrease, given that all detected targets' velocity is below the Min Velocity Threshold.", true);
|
||||
|
||||
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), AttractModifierCooldownProp, "Attract Modifier Cooldown", 1f, 25f);
|
||||
CustomHelpLabelField("Controls how many seconds need to pass before the AI can detect Attract Modifier again, after already detecting one.", true);
|
||||
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
}
|
||||
}
|
||||
|
||||
void UnawareSettings ()
|
||||
{
|
||||
UnawareFoldoutProp.boolValue = EditorGUILayout.Foldout(UnawareFoldoutProp.boolValue, "Unaware Settings", true, FoldoutStyle);
|
||||
|
||||
if (UnawareFoldoutProp.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
CustomEditorProperties.TextTitleWithDescription("Unaware Settings", "An Unaware Reaction will only be triggered after an AI has become Suspicious or Aware. This can happen after a target has been " +
|
||||
"lost or is too quite to be detected. This should be used for resetting an AI back to its original settings, given they've been modified with a Reaction Object.", false);
|
||||
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), UnawareThreatLevelProp, "Unaware Threat Level", 0.0f, 1f);
|
||||
CustomHelpLabelField("Controls the Threat Amount that's needed for an AI to reach the Unaware Threat Level.", false);
|
||||
GUILayout.Space(15);
|
||||
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), DelayUnawareSecondsProp, "Delay Unaware Seconds", 0f, 25f);
|
||||
CustomHelpLabelField("Controls how many seconds need to pass before the Unaware level is invoked, given the Unware Threat Level has been met.", false);
|
||||
GUILayout.Space(15);
|
||||
EditorGUILayout.PropertyField(UnawareReactionProp, new GUIContent("Unaware Reaction"));
|
||||
CustomHelpLabelField("The Reaction Object that will be used for this Reaction. Reaction Objects can be shared between multiple AI so it does not have to be recreated.", false);
|
||||
GUILayout.Space(15);
|
||||
CustomHelpLabelField("Unaware Events - Controls the custom events that happen when the AI becomes unaware.", false);
|
||||
EditorGUILayout.PropertyField(UnawareEventProp, new GUIContent("Unaware Event"));
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
}
|
||||
}
|
||||
|
||||
void SuspiciousSettings()
|
||||
{
|
||||
SuspiciousFoldoutProp.boolValue = EditorGUILayout.Foldout(SuspiciousFoldoutProp.boolValue, "Suspicious Settings", true, FoldoutStyle);
|
||||
|
||||
if (SuspiciousFoldoutProp.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
CustomEditorProperties.TextTitleWithDescription("Suspicious Settings", "A Suspicious Reaction will only be triggered once and after an AI has reached a Suspicious Threat Level. " +
|
||||
"It will not trigger again until after the AI has engaged with a target or if it has reached the Unaware Threat Level.", false);
|
||||
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), SuspiciousThreatLevelProp, "Suspicious Threat Level", 0.0f, 1f);
|
||||
CustomHelpLabelField("Controls the Threat Amount that's needed for an AI to reach the Suspicious Threat Level.", false);
|
||||
GUILayout.Space(15);
|
||||
EditorGUILayout.PropertyField(SuspiciousReactionProp, new GUIContent("Suspicious Reaction"));
|
||||
CustomHelpLabelField("The Reaction Object that will be used for this Reaction. Reaction Objects can be shared between multiple AI so it does not have to be recreated.", false);
|
||||
GUILayout.Space(15);
|
||||
CustomHelpLabelField("Suspicious Events - Controls the custom events that happen when an AI reaches a Suspicious Threat Level.", false);
|
||||
EditorGUILayout.PropertyField(SuspiciousEventProp, new GUIContent("Suspicious Event"));
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
}
|
||||
}
|
||||
|
||||
void AwareSettings()
|
||||
{
|
||||
AwareFoldoutProp.boolValue = EditorGUILayout.Foldout(AwareFoldoutProp.boolValue, "Aware Settings", true, FoldoutStyle);
|
||||
|
||||
if (AwareFoldoutProp.boolValue)
|
||||
{
|
||||
CustomEditorProperties.BeginFoldoutWindowBox();
|
||||
CustomEditorProperties.TextTitleWithDescription("Aware Settings", "An Aware Reaction will only be triggered once and after an AI has reached an Aware Threat Level. " +
|
||||
"It will not trigger again until after the AI has engaged with a target or if it has reached the Unaware Threat Level.", false);
|
||||
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), AwareThreatLevelProp, "Aware Threat Level", 0.0f, 1f);
|
||||
CustomHelpLabelField("Controls the Threat Amount that's needed for an AI to reach the Aware Threat Level.", false);
|
||||
GUILayout.Space(15);
|
||||
EditorGUILayout.PropertyField(AwareReactionProp, new GUIContent("Aware Reaction"));
|
||||
CustomHelpLabelField("The Reaction Object that will be used for this Reaction. Reaction Objects can be shared between multiple AI so it does not have to be recreated.", false);
|
||||
GUILayout.Space(15);
|
||||
CustomHelpLabelField("Aware Events - Controls the custom events that happen when an AI reaches a Aware Threat Level.", false);
|
||||
EditorGUILayout.PropertyField(AwareEventProp, new GUIContent("Aware Event"));
|
||||
CustomEditorProperties.EndFoldoutWindowBox();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayThreatLevel (EmeraldSoundDetector self)
|
||||
{
|
||||
EditorGUILayout.BeginVertical("Box"); //Begin Title Box
|
||||
|
||||
DisplayTitle("Info"); //Title
|
||||
|
||||
CustomHelpLabelField("Current Threat Level: " + self.CurrentThreatLevel.ToString(), false);
|
||||
|
||||
Rect r = EditorGUILayout.BeginVertical();
|
||||
r.height = 25;
|
||||
EditorGUI.ProgressBar(r, self.CurrentThreatAmount, "Current Threat Amount: " + (Mathf.Round(self.CurrentThreatAmount * 100f) / 100f).ToString());
|
||||
EditorGUILayout.EndVertical();
|
||||
GUILayout.Space(35);
|
||||
EditorGUILayout.EndVertical(); //End Title Box
|
||||
GUILayout.Space(15);
|
||||
}
|
||||
|
||||
void CustomHelpLabelField(string TextInfo, bool UseSpace)
|
||||
{
|
||||
GUI.backgroundColor = new Color(1f, 1f, 1f, 1f);
|
||||
EditorGUILayout.LabelField(TextInfo, EditorStyles.helpBox);
|
||||
GUI.backgroundColor = Color.white;
|
||||
if (UseSpace)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayTitle(string Title)
|
||||
{
|
||||
GUI.backgroundColor = new Color(0.2f, 0.2f, 0.2f, 0.25f);
|
||||
EditorGUILayout.BeginVertical("Box");
|
||||
EditorGUILayout.LabelField(Title, EditorStyles.boldLabel);
|
||||
GUI.backgroundColor = Color.white;
|
||||
EditorGUILayout.EndVertical();
|
||||
GUI.backgroundColor = Color.white;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8f0d1f2920cd7b44a3b4b94bae45087
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,43 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
|
||||
namespace EmeraldAI.Utility
|
||||
{
|
||||
public class LayerMaskDrawer : Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a LayerMask to a field value
|
||||
/// </summary>
|
||||
public static int LayerMaskToField(LayerMask mask)
|
||||
{
|
||||
int field = 0;
|
||||
var layers = InternalEditorUtility.layers;
|
||||
for (int c = 0; c < layers.Length; c++)
|
||||
{
|
||||
if ((mask & (1 << LayerMask.NameToLayer(layers[c]))) != 0)
|
||||
{
|
||||
field |= 1 << c;
|
||||
}
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the field value to a LayerMask
|
||||
/// </summary>
|
||||
public static LayerMask FieldToLayerMask(int field)
|
||||
{
|
||||
LayerMask mask = 0;
|
||||
var layers = InternalEditorUtility.layers;
|
||||
for (int c = 0; c < layers.Length; c++)
|
||||
{
|
||||
if ((field & (1 << c)) != 0)
|
||||
{
|
||||
mask |= 1 << LayerMask.NameToLayer(layers[c]);
|
||||
}
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85bb59ef219dbda498774afbad7e1361
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,357 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace EmeraldAI.SoundDetection.Utility
|
||||
{
|
||||
[System.Serializable]
|
||||
[CustomEditor(typeof(ReactionObject))]
|
||||
public class ReactionObjectEditor : Editor
|
||||
{
|
||||
ReorderableList ReactionList;
|
||||
string DebugLogMessageInfo = "Debug Logs a message to the Unity Console (useful for testing mechanics and values).";
|
||||
string PlaySoundInfo = "Plays a sound at the position of this AI (useful for audible queues).";
|
||||
string PlayEmoteAnimationInfo = "Plays an emote animation using the Emote Animation ID set witin the Emerald AI Editor (useful for visual queues).";
|
||||
string LookAtLoudestTargetPositionInfo = "Looks in the direction of the loudest noise.";
|
||||
string ReturnToStartingPositionInfo = "Returns the AI back to its starting position.";
|
||||
string ExpandDetectionDistanceInfo = "Expand the AI's Detection Distance, in addition to its current detection distance (useful for detecting a target that may have recently attacked a nearby target).";
|
||||
string SetMovementStateInfo = "Changes the AI's movement type to either run or walk.";
|
||||
string ResetDetectionDistanceInfo = "Resets the AI's Detection Distance to its default/starting value.";
|
||||
string ResetLookAtPositionInfo = "Resets the AI's Look At Position to its default/starting position.";
|
||||
string AttractModifierInfo = "(Only Usable with an Attract Modifier) Called when the condition on Attract Modifier is invoked, which is set on the a gameobject with AttractModifier component.";
|
||||
string DelayInfo = "Delays the reaction below this reaction by the set amount of seconds.";
|
||||
string ResetAllToDefaultInfo = "Resets all modified values back to their default values (Look At Position, Detection Distance, Movement State, and Combat State).";
|
||||
string EnterCombatStateInfo = "Puts the AI into its combat state and allows it to use its combat state animations. If an AI uses equip animations, its Equip Weapon animation will be played before transitioning to its combat animations.";
|
||||
string ExitCombatStateInfo = "Returns the AI to its default non-combat state using non-combat animations, given there are no visible targets. If an AI uses equip animations, its Unequip Weapon animation will be played before transitioning to its non-combat animations.";
|
||||
string FleeFromLoudestTargetInfo = "Sets the AI's flee target as the loudest detected target. This reaction is only for AI with a Coward Beahvior Type (If no loudest target is present, this reaction will be ignored).";
|
||||
string MoveToLoudestTargetInfo = "Moves the AI directly to the loudest detected target. (If no loudest target is present, this reaction will be ignored)";
|
||||
string MoveAroundCurrentPositionInfo = "Allows the AI to generate new waypoints from the AI's current position based on the user set waypoint amount and radius.";
|
||||
string MoveAroundLoudestTargetInfo = "Allows the AI to generate new waypoints from the AI's loudest detected target based on the user set waypoint amount and radius. (If no loudest target is present, this reaction will be ignored)";
|
||||
string NoneInfo = "A None reaction is the default reaction. Nothing will happen when this reaction is triggered.";
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
UpdateReactionList();
|
||||
}
|
||||
|
||||
void UpdateReactionList()
|
||||
{
|
||||
//Reaction List
|
||||
ReactionList = new ReorderableList(serializedObject, serializedObject.FindProperty("ReactionList"), true, true, true, true);
|
||||
ReactionList.drawElementCallback =
|
||||
(Rect rect, int index, bool isActive, bool isFocused) =>
|
||||
{
|
||||
CustomCallback(ReactionList, rect, index, isActive, isFocused);
|
||||
};
|
||||
|
||||
ReactionList.drawHeaderCallback = rect =>
|
||||
{
|
||||
EditorGUI.LabelField(rect, "Reaction List", EditorStyles.boldLabel);
|
||||
};
|
||||
|
||||
|
||||
//Modify the heights of each element to create a cleaner reorderable list. This allows each element to exapnd its height based on how many options the element setting has.
|
||||
ReactionList.elementHeightCallback = (int index) =>
|
||||
{
|
||||
SerializedProperty element = ReactionList.serializedProperty.GetArrayElementAtIndex(index);
|
||||
float height = 1;
|
||||
|
||||
if ((Reaction.ElementLineHeights)element.FindPropertyRelative("ElementLineHeight").intValue == Reaction.ElementLineHeights.One)
|
||||
height -= 1.35f;
|
||||
else if ((Reaction.ElementLineHeights)element.FindPropertyRelative("ElementLineHeight").intValue == Reaction.ElementLineHeights.Two)
|
||||
height = 1;
|
||||
else if ((Reaction.ElementLineHeights)element.FindPropertyRelative("ElementLineHeight").intValue == Reaction.ElementLineHeights.Three)
|
||||
height += 1.35f;
|
||||
else if ((Reaction.ElementLineHeights)element.FindPropertyRelative("ElementLineHeight").intValue == Reaction.ElementLineHeights.Four)
|
||||
height += 2.7f;
|
||||
else if ((Reaction.ElementLineHeights)element.FindPropertyRelative("ElementLineHeight").intValue == Reaction.ElementLineHeights.Five)
|
||||
height += 4.05f;
|
||||
else if ((Reaction.ElementLineHeights)element.FindPropertyRelative("ElementLineHeight").intValue == Reaction.ElementLineHeights.Six)
|
||||
height += 5.4f;
|
||||
|
||||
return EditorGUIUtility.singleLineHeight * (2.35f + height);
|
||||
};
|
||||
|
||||
//Set a newly created element to defalt values.
|
||||
ReactionList.onAddCallback = ReactionList =>
|
||||
{
|
||||
var m_List = serializedObject.FindProperty("ReactionList");
|
||||
m_List.arraySize++;
|
||||
|
||||
SerializedProperty element = ReactionList.serializedProperty.GetArrayElementAtIndex(m_List.arraySize-1);
|
||||
element.FindPropertyRelative("ReactionType").intValue = (int)ReactionTypes.None;
|
||||
element.FindPropertyRelative("IntValue1").intValue = 5;
|
||||
element.FindPropertyRelative("IntValue2").intValue = 2;
|
||||
element.FindPropertyRelative("StringValue").stringValue = "New Message";
|
||||
element.FindPropertyRelative("FloatValue").floatValue = 1f;
|
||||
element.FindPropertyRelative("BoolValue").boolValue = true;
|
||||
element.FindPropertyRelative("SoundRef").objectReferenceValue = null;
|
||||
};
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
GUILayout.Space(10);
|
||||
ReactionObject self = (ReactionObject)target;
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUILayout.BeginVertical("Box"); //Begin Title Box
|
||||
GUI.backgroundColor = new Color(0.2f, 0.2f, 0.2f, 0.25f);
|
||||
DisplayTitle("Reaction Object");
|
||||
|
||||
CustomHelpLabelField("A list of reactions that will be execuded, in order from top to bottom, when this reaction is invoked. If an AI sees a target, this reaction will be canceled and it will rely on its Behavior Type.", false);
|
||||
EditorGUILayout.HelpBox("You can hover over each Reaction Type and its value to get a detailed tooltip of its usage/functionality.", MessageType.Info);
|
||||
GUILayout.Space(5);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
ReactionList.DoLayoutList();
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
//Update the ReactionList on change as it changes size dynamically depending on the option.
|
||||
UpdateReactionList();
|
||||
}
|
||||
|
||||
GUILayout.Space(15);
|
||||
EditorGUILayout.EndVertical(); //End Title Box
|
||||
GUILayout.Space(15);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
Undo.RecordObject(self, "Undo");
|
||||
|
||||
if (GUI.changed)
|
||||
{
|
||||
EditorUtility.SetDirty(target);
|
||||
EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
void CustomHelpLabelField(string TextInfo, bool UseSpace)
|
||||
{
|
||||
GUI.backgroundColor = new Color(0.1f, 0.1f, 0.1f, 0.19f);
|
||||
EditorGUILayout.LabelField(TextInfo, EditorStyles.helpBox);
|
||||
GUI.backgroundColor = Color.white;
|
||||
if (UseSpace)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
}
|
||||
|
||||
void CustomPopup(Rect position, GUIContent label, SerializedProperty property, string nameOfLabel, string[] names)
|
||||
{
|
||||
label = EditorGUI.BeginProperty(position, label, property);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
string[] enumNamesList = names;
|
||||
var newValue = EditorGUI.Popup(position, property.intValue, enumNamesList);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
property.intValue = newValue;
|
||||
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
void DisplayTitle(string Title)
|
||||
{
|
||||
EditorGUILayout.BeginVertical("Box");
|
||||
EditorGUILayout.LabelField(Title, EditorStyles.boldLabel);
|
||||
GUI.backgroundColor = Color.white;
|
||||
EditorGUILayout.EndVertical();
|
||||
GUI.backgroundColor = Color.white;
|
||||
}
|
||||
|
||||
void CustomCallback(ReorderableList list, Rect rect, int index, bool isActive, bool isFocused)
|
||||
{
|
||||
var element = list.serializedProperty.GetArrayElementAtIndex(index);
|
||||
|
||||
EditorGUI.PropertyField(new Rect(rect.x, rect.y + 11f, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("ReactionType"), new GUIContent("Reaction Type", ""));
|
||||
|
||||
//One line elements
|
||||
if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.ResetDetectionDistance)
|
||||
{
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 11, rect.width, EditorGUIUtility.singleLineHeight),
|
||||
new GUIContent(" ", ResetDetectionDistanceInfo));
|
||||
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.One;
|
||||
}
|
||||
else if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.ResetLookAtPosition)
|
||||
{
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 11, rect.width, EditorGUIUtility.singleLineHeight),
|
||||
new GUIContent(" ", ResetLookAtPositionInfo));
|
||||
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.One;
|
||||
}
|
||||
else if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.ReturnToStartingPosition)
|
||||
{
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 11, rect.width, EditorGUIUtility.singleLineHeight),
|
||||
new GUIContent(" ", ReturnToStartingPositionInfo));
|
||||
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.One;
|
||||
}
|
||||
else if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.None)
|
||||
{
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 11, rect.width, EditorGUIUtility.singleLineHeight),
|
||||
new GUIContent(" ", NoneInfo));
|
||||
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.One;
|
||||
}
|
||||
else if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.ResetAllToDefault)
|
||||
{
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 11, rect.width, EditorGUIUtility.singleLineHeight),
|
||||
new GUIContent(" ", ResetAllToDefaultInfo));
|
||||
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.One;
|
||||
}
|
||||
else if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.EnterCombatState)
|
||||
{
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 11, rect.width, EditorGUIUtility.singleLineHeight),
|
||||
new GUIContent(" ", EnterCombatStateInfo));
|
||||
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.One;
|
||||
}
|
||||
else if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.ExitCombatState)
|
||||
{
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 11, rect.width, EditorGUIUtility.singleLineHeight),
|
||||
new GUIContent(" ", ExitCombatStateInfo));
|
||||
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.One;
|
||||
}
|
||||
else if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.FleeFromLoudestTarget)
|
||||
{
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 11, rect.width, EditorGUIUtility.singleLineHeight),
|
||||
new GUIContent(" ", FleeFromLoudestTargetInfo));
|
||||
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.One;
|
||||
}
|
||||
|
||||
//Two line elements
|
||||
else if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.DebugLogMessage)
|
||||
{
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 11, rect.width, EditorGUIUtility.singleLineHeight),
|
||||
new GUIContent(" ", DebugLogMessageInfo));
|
||||
EditorGUI.PropertyField(new Rect(rect.x, rect.y + 34, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("StringValue"), new GUIContent("Debug Message", "The message that will be displayed in the Unity Console."));
|
||||
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.Two;
|
||||
}
|
||||
else if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.PlayEmoteAnimation)
|
||||
{
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 11, rect.width, EditorGUIUtility.singleLineHeight),
|
||||
new GUIContent(" ", PlayEmoteAnimationInfo));
|
||||
EditorGUI.PropertyField(new Rect(rect.x, rect.y + 34, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("IntValue1"), new GUIContent("Emote Animation ID", "The Emote Animation ID is the same " +
|
||||
"Emote Animation ID set witin the Emerald AI Editor of Animation Settings tab."));
|
||||
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.Two;
|
||||
}
|
||||
else if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.LookAtLoudestTarget)
|
||||
{
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 11, rect.width, EditorGUIUtility.singleLineHeight),
|
||||
new GUIContent(" ", LookAtLoudestTargetPositionInfo));
|
||||
EditorGUI.PropertyField(new Rect(rect.x, rect.y + 34, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("IntValue1"), new GUIContent("Seconds", "The amount of time (in seconds) this AI will look at the loudest target."));
|
||||
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.Two;
|
||||
}
|
||||
else if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.Delay)
|
||||
{
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 11, rect.width, EditorGUIUtility.singleLineHeight),
|
||||
new GUIContent(" ", DelayInfo));
|
||||
EditorGUI.Slider(new Rect(rect.x, rect.y + 34, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("FloatValue"), 0.25f, 10f, new GUIContent("Delay Seconds", "The delay (in seconds) before the reaction below this one is called."));
|
||||
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.Two;
|
||||
}
|
||||
else if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.ExpandDetectionDistance)
|
||||
{
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 11, rect.width, EditorGUIUtility.singleLineHeight),
|
||||
new GUIContent(" ", ExpandDetectionDistanceInfo));
|
||||
EditorGUI.PropertyField(new Rect(rect.x, rect.y + 34, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("IntValue1"), new GUIContent("Distance", "The distance in which an AI's Detection Radius will be expanded " +
|
||||
"(in addition to its current detection distance.)."));
|
||||
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.Two;
|
||||
}
|
||||
else if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.SetMovementState)
|
||||
{
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 11, rect.width, EditorGUIUtility.singleLineHeight),
|
||||
new GUIContent(" ", SetMovementStateInfo));
|
||||
EditorGUI.PropertyField(new Rect(rect.x, rect.y + 34, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("MovementState"), new GUIContent("Movement State", "The movement state that this AI will use (Walk or Run). This can be " +
|
||||
"reset back to its default value by using the Reset All To Default reaction or by setting it manually with this same reaction."));
|
||||
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.Two;
|
||||
}
|
||||
else if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.AttractModifier && (AttractModifierReactionTypes)element.FindPropertyRelative("AttractModifierReaction").intValue == AttractModifierReactionTypes.LookAtAttractSource)
|
||||
{
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 11, rect.width, EditorGUIUtility.singleLineHeight),
|
||||
new GUIContent(" ", AttractModifierInfo));
|
||||
EditorGUI.PropertyField(new Rect(rect.x, rect.y + 34, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("AttractModifierReaction"), new GUIContent("Attract Modifier Reaction", "Allows the AI to look at the detected AttractSource, " +
|
||||
"given that the Look At feature is enabled."));
|
||||
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.Two;
|
||||
}
|
||||
|
||||
//Three line elements
|
||||
else if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.PlaySound)
|
||||
{
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 11, rect.width, EditorGUIUtility.singleLineHeight),
|
||||
new GUIContent(" ", PlaySoundInfo));
|
||||
EditorGUI.PropertyField(new Rect(rect.x, rect.y + 34, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("SoundRef"), new GUIContent("Audio Clip", "The audio clip that will play when this reaction is triggered."));
|
||||
EditorGUI.Slider(new Rect(rect.x, rect.y + 57, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("FloatValue"), 0f, 1f, new GUIContent("Volume", "Controls the volume of the Audio Clip."));
|
||||
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.Three;
|
||||
}
|
||||
else if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.MoveToLoudestTarget)
|
||||
{
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 11, rect.width, EditorGUIUtility.singleLineHeight),
|
||||
new GUIContent(" ", MoveToLoudestTargetInfo));
|
||||
EditorGUI.Slider(new Rect(rect.x, rect.y + 34, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("FloatValue"), 0f, 10f, new GUIContent("Wait Seconds", "The amount of seconds the AI will wait at the loudest target position."));
|
||||
EditorGUI.PropertyField(new Rect(rect.x, rect.y + 57, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("BoolValue"), new GUIContent("Delay Next Reaction", "Delays the next reaction until the AI has reached its destination " +
|
||||
"or finished moving through all of its waypoints from this reaction. (Highly Recommended)"));
|
||||
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.Three;
|
||||
}
|
||||
|
||||
//Four line elements
|
||||
else if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.AttractModifier && (AttractModifierReactionTypes)element.FindPropertyRelative("AttractModifierReaction").intValue == AttractModifierReactionTypes.MoveToAttractSource)
|
||||
{
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 11, rect.width, EditorGUIUtility.singleLineHeight),
|
||||
new GUIContent(" ", AttractModifierInfo));
|
||||
EditorGUI.PropertyField(new Rect(rect.x, rect.y + 34, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("AttractModifierReaction"), new GUIContent("Attract Modifier Reaction", "Moves the AI to the AttractSource position."));
|
||||
EditorGUI.Slider(new Rect(rect.x, rect.y + 57, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("FloatValue"), 0f, 10f, new GUIContent("Wait Seconds", "The amount of seconds the AI will wait at the AttractSource position."));
|
||||
EditorGUI.PropertyField(new Rect(rect.x, rect.y + 80, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("BoolValue"), new GUIContent("Delay Next Reaction", "Delays the next reaction until the AI has reached its destination " +
|
||||
"or finished moving through all of its waypoints from this reaction. (Highly Recommended)"));
|
||||
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.Four;
|
||||
}
|
||||
|
||||
//Five line elements
|
||||
else if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.MoveAroundCurrentPosition)
|
||||
{
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 11, rect.width, EditorGUIUtility.singleLineHeight),
|
||||
new GUIContent(" ", MoveAroundCurrentPositionInfo));
|
||||
EditorGUI.IntSlider(new Rect(rect.x, rect.y + 34, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("IntValue1"), 1, 25, new GUIContent("Radius", "The radius in which a waypoint will be generated from the AI's current position."));
|
||||
EditorGUI.IntSlider(new Rect(rect.x, rect.y + 57, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("IntValue2"), 1, 10, new GUIContent("Total Waypoints", "The amount of waypoints that will be generated from this Wander Reaction. " +
|
||||
"The AI won't stop until all waypoints have been arrived at, unless the AI sees a target."));
|
||||
EditorGUI.Slider(new Rect(rect.x, rect.y + 80, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("FloatValue"), 0f, 10f, new GUIContent("Wait Seconds", "The amount of seconds it will take to generate the next waypoint."));
|
||||
EditorGUI.PropertyField(new Rect(rect.x, rect.y + 103, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("BoolValue"), new GUIContent("Delay Next Reaction", "Delays the next reaction until the AI has reached its destination " +
|
||||
"or finished moving through all of its waypoints from this reaction. (Highly Recommended)"));
|
||||
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.Five;
|
||||
}
|
||||
else if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.MoveAroundLoudestTarget)
|
||||
{
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 11, rect.width, EditorGUIUtility.singleLineHeight),
|
||||
new GUIContent(" ", MoveAroundLoudestTargetInfo));
|
||||
EditorGUI.IntSlider(new Rect(rect.x, rect.y + 34, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("IntValue1"), 1, 25, new GUIContent("Radius", "The radius in which a waypoint will be generated from the AI's loudest target position."));
|
||||
EditorGUI.IntSlider(new Rect(rect.x, rect.y + 57, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("IntValue2"), 1, 10, new GUIContent("Total Waypoints", "The amount of waypoints that will be generated from this Wander Reaction. " +
|
||||
"The AI won't stop until all waypoints have been arrived at, unless the AI sees a target."));
|
||||
EditorGUI.Slider(new Rect(rect.x, rect.y + 80, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("FloatValue"), 0f, 10f, new GUIContent("Wait Seconds", "The amount of seconds it will take to generate the next waypoint."));
|
||||
EditorGUI.PropertyField(new Rect(rect.x, rect.y + 103, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("BoolValue"), new GUIContent("Delay Next Reaction", "Delays the next reaction until the AI has reached its destination " +
|
||||
"or finished moving through all of its waypoints from this reaction. (Highly Recommended)"));
|
||||
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.Five;
|
||||
}
|
||||
|
||||
//Six line elements
|
||||
else if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.AttractModifier && (AttractModifierReactionTypes)element.FindPropertyRelative("AttractModifierReaction").intValue == AttractModifierReactionTypes.MoveAroundAttractSource)
|
||||
{
|
||||
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 11, rect.width, EditorGUIUtility.singleLineHeight),
|
||||
new GUIContent(" ", AttractModifierInfo));
|
||||
EditorGUI.PropertyField(new Rect(rect.x, rect.y + 34, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("AttractModifierReaction"), new GUIContent("Attract Modifier Reaction", "Allows the AI to generate new waypoints from the detected " +
|
||||
" AttractSource based on the user set waypoint amount and radius."));
|
||||
EditorGUI.IntSlider(new Rect(rect.x, rect.y + 57, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("IntValue1"), 1, 25, new GUIContent("Radius", "The radius in which a position will be generated from the Attract Modifier."));
|
||||
EditorGUI.IntSlider(new Rect(rect.x, rect.y + 80, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("IntValue2"), 1, 10, new GUIContent("Total Waypoints", "The amount of waypoints that will be generated from this Wander Reaction. " +
|
||||
"The AI won't stop until all waypoints have been arrived at, unless the AI sees a target."));
|
||||
EditorGUI.Slider(new Rect(rect.x, rect.y + 103, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("FloatValue"), 0f, 10f, new GUIContent("Wait Seconds", "The amount of seconds it will take to generate the next waypoint."));
|
||||
EditorGUI.PropertyField(new Rect(rect.x, rect.y + 126, rect.width, EditorGUIUtility.singleLineHeight), element.FindPropertyRelative("BoolValue"), new GUIContent("Delay Next Reaction", "Delays the next reaction until the AI has reached its destination " +
|
||||
"or finished moving through all of its waypoints from this reaction. (Highly Recommended)"));
|
||||
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.Six;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 327ec50e4458ef54fbf15890ec465f41
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,693 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using System.Linq;
|
||||
|
||||
namespace EmeraldAI.SoundDetection
|
||||
{
|
||||
/// <summary>
|
||||
/// Gives AI that ability to hear noises and detect unseen player targets.
|
||||
/// </summary>
|
||||
[HelpURL("https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-optional/sound-detector-component")]
|
||||
public class EmeraldSoundDetector : MonoBehaviour
|
||||
{
|
||||
#region Sound Detector Variables
|
||||
public GameObject DetectedAttractModifier;
|
||||
public ThreatLevels CurrentThreatLevel = ThreatLevels.Unaware;
|
||||
public LayerMask DetectableLayers = 1;
|
||||
public float CurrentThreatAmount;
|
||||
public float CheckIncrement = 0.25f;
|
||||
public float MinVelocityThreshold = 0.5f;
|
||||
public float AttentionRate = 0.1f;
|
||||
public float AttentionFalloff = 0.05f;
|
||||
public float DelayUnawareSeconds = 5f;
|
||||
public float AttractModifierCooldown = 5;
|
||||
public bool MovingTargetDetected;
|
||||
|
||||
//Unaware
|
||||
public float UnawareThreatLevel = 0.05f;
|
||||
bool UnawareTriggered;
|
||||
[SerializeField]
|
||||
public ReactionObject UnawareReaction;
|
||||
public UnityEvent UnawareEvent;
|
||||
|
||||
//Suspicious
|
||||
public float SuspiciousThreatLevel = 0.5f;
|
||||
bool SuspiciousTriggered;
|
||||
[SerializeField]
|
||||
public ReactionObject SuspiciousReaction;
|
||||
public UnityEvent SuspiciousEvent;
|
||||
|
||||
//Aware
|
||||
public float AwareThreatLevel = 1f;
|
||||
bool AwareTriggered;
|
||||
[SerializeField]
|
||||
public ReactionObject AwareReaction;
|
||||
public UnityEvent AwareEvent;
|
||||
|
||||
//Private variables
|
||||
float DelayUnawareTimer = 0;
|
||||
float CheckIncrementTimer = 0;
|
||||
EmeraldSystem EmeraldComponent;
|
||||
EmeraldDetection EmeraldDetection;
|
||||
EmeraldMovement EmeraldMovement;
|
||||
bool ArrivedAtDestination;
|
||||
Coroutine CurrentReactionCoroutine;
|
||||
Coroutine CalculateMovementCoroutine;
|
||||
float TimeSinceLastAttractModifier;
|
||||
bool SoundDetectorEnabled = true;
|
||||
|
||||
[SerializeField]
|
||||
public List<TargetDataClass> CurrentTargetData = new List<TargetDataClass>();
|
||||
[System.Serializable]
|
||||
public class TargetDataClass
|
||||
{
|
||||
public Transform Target;
|
||||
public Vector3 LastPosition;
|
||||
public float Velocty;
|
||||
public float Distance;
|
||||
public float NoiseLevel;
|
||||
|
||||
public TargetDataClass (Transform m_Target, Vector3 m_LastPosition, float m_Velocty, float m_Distance, float m_NoiseLevel)
|
||||
{
|
||||
Target = m_Target;
|
||||
LastPosition = m_LastPosition;
|
||||
Velocty = m_Velocty;
|
||||
Distance = m_Distance;
|
||||
NoiseLevel = m_NoiseLevel;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Editor Variables
|
||||
public bool HideSettingsFoldout;
|
||||
public bool SoundDetectorFoldout;
|
||||
public bool UnawareFoldout;
|
||||
public bool SuspiciousFoldout;
|
||||
public bool AwareFoldout;
|
||||
#endregion
|
||||
|
||||
void Start()
|
||||
{
|
||||
CurrentThreatAmount = 0;
|
||||
TimeSinceLastAttractModifier = AttractModifierCooldown;
|
||||
EmeraldComponent = GetComponent<EmeraldSystem>();
|
||||
EmeraldMovement = GetComponent<EmeraldMovement>();
|
||||
EmeraldDetection = GetComponent<EmeraldDetection>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows the Sound Detector to run after already having called DisableSoundDetector as all Sound Detectors are enabled by default.
|
||||
/// </summary>
|
||||
public void EnableSoundDetector ()
|
||||
{
|
||||
SoundDetectorEnabled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the Sound Detector from running.
|
||||
/// </summary>
|
||||
public void DisableSoundDetector()
|
||||
{
|
||||
SoundDetectorEnabled = false;
|
||||
CancelAll();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks for sounds levels for each LineOfSightTargets (targets detected within the AI's detection radius, but have not been seen).
|
||||
/// </summary>
|
||||
void CheckForSounds ()
|
||||
{
|
||||
//If the AI enters combat or there are no EmeraldDetection.LineOfSightTargets, return as nothing further needs to be done.
|
||||
if (EmeraldComponent.CombatComponent.CombatState || EmeraldDetection.LineOfSightTargets.Count == 0)
|
||||
{
|
||||
MovingTargetDetected = false;
|
||||
return;
|
||||
}
|
||||
|
||||
CheckIncrementTimer += Time.deltaTime;
|
||||
|
||||
if (CheckIncrementTimer >= CheckIncrement)
|
||||
{
|
||||
//Add each target from LineOfSightTargets to CurrentTargetData, given that it hasn't already been added and it has the EmeraldComponent.PlayerTag.
|
||||
for (int i = 0; i < EmeraldDetection.LineOfSightTargets.Count; i++)
|
||||
{
|
||||
if (!CurrentTargetData.Exists(x => x.Target == EmeraldDetection.LineOfSightTargets[i].transform))
|
||||
{
|
||||
if (!EmeraldDetection.LineOfSightTargets[i].gameObject.CompareTag(EmeraldDetection.PlayerTag)) continue; //Skip non-player targets
|
||||
float DistanceFromTarget = Vector3.Distance(transform.position, EmeraldDetection.LineOfSightTargets[i].transform.position);
|
||||
CurrentTargetData.Add(new TargetDataClass(EmeraldDetection.LineOfSightTargets[i].transform, EmeraldDetection.LineOfSightTargets[i].transform.position, MinVelocityThreshold, DistanceFromTarget, 0));
|
||||
}
|
||||
}
|
||||
|
||||
UpdateTargetData();
|
||||
CheckIncrementTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (EmeraldComponent.TargetToFollow) return;
|
||||
|
||||
if (!EmeraldComponent.AnimationComponent.IsDead && SoundDetectorEnabled)
|
||||
{
|
||||
TimeSinceLastAttractModifier += Time.deltaTime;
|
||||
if (EmeraldDetection.LineOfSightTargets.Count > 0 || CurrentThreatLevel != ThreatLevels.Unaware)
|
||||
{
|
||||
CheckForSounds();
|
||||
CheckEvents();
|
||||
CalculateThreatLevel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates all info for each target and stores it in CurrentTargetData.
|
||||
/// </summary>
|
||||
void UpdateTargetData ()
|
||||
{
|
||||
for (int i = 0; i < CurrentTargetData.Count; i++)
|
||||
{
|
||||
//Calculate the velocity of each target by storing its previous distance and comparing it to its current distance (with each CheckIncrement).
|
||||
float DistanceFromTarget = Vector3.Distance(transform.position, CurrentTargetData[i].Target.position);
|
||||
float TargetVelocity = (CurrentTargetData[i].Target.position - CurrentTargetData[i].LastPosition).magnitude;
|
||||
//float DistanceVariable = (EmeraldComponent.AttackDistance / DistanceFromTarget);
|
||||
CurrentTargetData[i].LastPosition = CurrentTargetData[i].Target.position;
|
||||
CurrentTargetData[i].NoiseLevel = TargetVelocity;
|
||||
|
||||
if (TargetVelocity >= MinVelocityThreshold)
|
||||
{
|
||||
MovingTargetDetected = true;
|
||||
}
|
||||
else if (CurrentThreatAmount > 0 && TargetVelocity < MinVelocityThreshold)
|
||||
{
|
||||
MovingTargetDetected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simply increases or descreases the CurrentThreatLevel depending on whether or not detected targets are moving.
|
||||
/// </summary>
|
||||
void CalculateThreatLevel ()
|
||||
{
|
||||
if (MovingTargetDetected)
|
||||
{
|
||||
CurrentThreatAmount += Time.deltaTime * AttentionRate;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentThreatAmount -= Time.deltaTime * AttentionFalloff;
|
||||
}
|
||||
|
||||
CurrentThreatAmount = Mathf.Clamp(CurrentThreatAmount, 0f, 1f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels all sound detection fuctionality and reactions.
|
||||
/// </summary>
|
||||
void CancelAll ()
|
||||
{
|
||||
StopAllCoroutines();
|
||||
CurrentTargetData.Clear();
|
||||
EmeraldComponent.MovementComponent.ChangeWanderType((EmeraldMovement.WanderTypes)EmeraldMovement.StartingWanderingType);
|
||||
}
|
||||
|
||||
void CheckEvents()
|
||||
{
|
||||
//Cancels all sound detection fuctionality and reactions if the AI goes into its combat state.
|
||||
if (EmeraldComponent.CombatComponent.CombatState && CurrentTargetData.Count > 0)
|
||||
{
|
||||
CancelAll();
|
||||
}
|
||||
//Return if the AI is incombat and the CurrentTargetData has already been cleared.
|
||||
else if (EmeraldComponent.CombatComponent.CombatState && CurrentTargetData.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//If any threat has been triggered, and the CurrentThreatLevel reaches UnawareLevel for the GiveUpSeconds cooldown amount, there's no detectable threats.
|
||||
if (SuspiciousTriggered || AwareTriggered)
|
||||
{
|
||||
if (CurrentThreatAmount <= UnawareThreatLevel)
|
||||
{
|
||||
if (!EmeraldComponent.CombatComponent.CombatState || EmeraldComponent.CombatComponent.CombatState && EmeraldDetection.TargetObstructed)
|
||||
{
|
||||
DelayUnawareTimer += Time.deltaTime;
|
||||
|
||||
if (DelayUnawareTimer >= DelayUnawareSeconds)
|
||||
{
|
||||
InvokeReactionList(UnawareReaction);
|
||||
UnawareEvent.Invoke();
|
||||
ClearThreats();
|
||||
DelayUnawareTimer = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (CurrentThreatAmount > UnawareThreatLevel)
|
||||
{
|
||||
DelayUnawareTimer = 0; //Reset the DelayUnawareTimer if a threat is detected
|
||||
}
|
||||
|
||||
if (CurrentThreatAmount >= SuspiciousThreatLevel && CurrentThreatAmount < AwareThreatLevel && !SuspiciousTriggered)
|
||||
{
|
||||
//Only invoke reactions and events when not in combat mode
|
||||
if (!EmeraldComponent.CombatComponent.CombatState)
|
||||
{
|
||||
InvokeReactionList(SuspiciousReaction);
|
||||
SuspiciousEvent.Invoke();
|
||||
}
|
||||
|
||||
CurrentThreatLevel = ThreatLevels.Suspicious;
|
||||
SuspiciousTriggered = true;
|
||||
}
|
||||
else if (CurrentThreatAmount >= AwareThreatLevel && !AwareTriggered)
|
||||
{
|
||||
//Only invoke reactions and events when not in combat mode
|
||||
if (!EmeraldComponent.CombatComponent.CombatState)
|
||||
{
|
||||
InvokeReactionList(AwareReaction);
|
||||
AwareEvent.Invoke();
|
||||
}
|
||||
|
||||
CurrentThreatLevel = ThreatLevels.Aware;
|
||||
AwareTriggered = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ClearThreats ()
|
||||
{
|
||||
CurrentThreatLevel = ThreatLevels.Unaware;
|
||||
CurrentThreatAmount = 0;
|
||||
SuspiciousTriggered = false;
|
||||
AwareTriggered = false;
|
||||
|
||||
//Only remove AI that don't exist within the current LineOfSightTargets after the AI has reached its Unaware ThreatLevel.
|
||||
//This allows an AI to finish out their current reactions that may rely on recent target data.
|
||||
for (int i = 0; i < CurrentTargetData.Count; i++)
|
||||
{
|
||||
if (!EmeraldDetection.LineOfSightTargets.Exists(x => x.transform == CurrentTargetData[i].Target))
|
||||
{
|
||||
CurrentTargetData.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (EmeraldDetection.LineOfSightTargets.Count == 0) CurrentTargetData.Clear();
|
||||
}
|
||||
|
||||
public void InvokeReactionList (ReactionObject SentReactionObject, bool SentByAttractModifier = false)
|
||||
{
|
||||
//Only allow reactions to be invoked if the AI is not in combat as combat logic is handled separately.
|
||||
if (EmeraldComponent.CombatComponent.CombatState || TimeSinceLastAttractModifier < AttractModifierCooldown)
|
||||
return;
|
||||
|
||||
if (SentReactionObject == null)
|
||||
{
|
||||
if (SentByAttractModifier)
|
||||
Debug.Log("A sent Reaction Object to the AI " + gameObject.name + " by the " + DetectedAttractModifier.name + " Attract Modifier was null. Please ensure the Reaction Object slot on this Attract Modifier object is not null.");
|
||||
return;
|
||||
}
|
||||
|
||||
//Ensure the AI is using its Starting WanderType.
|
||||
EmeraldComponent.MovementComponent.ChangeWanderType((EmeraldMovement.WanderTypes)EmeraldMovement.StartingWanderingType);
|
||||
|
||||
if (CurrentReactionCoroutine != null) { StopAllCoroutines(); }
|
||||
CurrentReactionCoroutine = StartCoroutine(InvokeReactionListInternal(SentReactionObject, SentByAttractModifier));
|
||||
}
|
||||
|
||||
IEnumerator InvokeReactionListInternal (ReactionObject SentReactionObject, bool SentByAttractModifier)
|
||||
{
|
||||
//Add a slight random delay before initializing the reactions list so no two AI reactions play exactly at the same time.
|
||||
float RandomDelay = Random.Range(0f, 0.15f);
|
||||
yield return new WaitForSeconds(RandomDelay);
|
||||
|
||||
for (int i = 0; i < SentReactionObject.ReactionList.Count; i++)
|
||||
{
|
||||
//Update the target's sound detection data before checking each reaction, in case something has changed.
|
||||
yield return new WaitForSeconds(0.001f);
|
||||
EmeraldComponent.DetectionComponent.UpdateAIDetection();
|
||||
yield return new WaitForSeconds(0.001f);
|
||||
CheckForSounds();
|
||||
yield return new WaitForSeconds(0.001f);
|
||||
|
||||
//Go through the list, in order, and play each reaction according to its enum Reaction Type (not the most elegant)
|
||||
if (SentReactionObject.ReactionList[i].ReactionType == ReactionTypes.Delay)
|
||||
{
|
||||
yield return new WaitForSeconds(SentReactionObject.ReactionList[i].FloatValue);
|
||||
}
|
||||
else if (SentReactionObject.ReactionList[i].ReactionType == ReactionTypes.DebugLogMessage)
|
||||
{
|
||||
DebugLogMessage(SentReactionObject.ReactionList[i].StringValue);
|
||||
}
|
||||
else if (SentReactionObject.ReactionList[i].ReactionType == ReactionTypes.PlaySound)
|
||||
{
|
||||
EmeraldComponent.SoundComponent.m_AudioSource.volume = SentReactionObject.ReactionList[i].FloatValue;
|
||||
EmeraldComponent.SoundComponent.m_AudioSource.PlayOneShot(SentReactionObject.ReactionList[i].SoundRef);
|
||||
}
|
||||
else if (SentReactionObject.ReactionList[i].ReactionType == ReactionTypes.PlayEmoteAnimation)
|
||||
{
|
||||
EmeraldComponent.AnimationComponent.PlayEmoteAnimation(SentReactionObject.ReactionList[i].IntValue1);
|
||||
}
|
||||
else if (SentReactionObject.ReactionList[i].ReactionType == ReactionTypes.LookAtLoudestTarget)
|
||||
{
|
||||
LookAtLoudestTarget();
|
||||
}
|
||||
else if (SentReactionObject.ReactionList[i].ReactionType == ReactionTypes.ReturnToStartingPosition)
|
||||
{
|
||||
ReturnToDefaultPosition();
|
||||
}
|
||||
else if (SentReactionObject.ReactionList[i].ReactionType == ReactionTypes.ExpandDetectionDistance)
|
||||
{
|
||||
ExpandDetectionDistance(SentReactionObject.ReactionList[i].IntValue1);
|
||||
}
|
||||
else if (SentReactionObject.ReactionList[i].ReactionType == ReactionTypes.SetMovementState)
|
||||
{
|
||||
SetMovementState(SentReactionObject.ReactionList[i].MovementState);
|
||||
}
|
||||
else if (SentReactionObject.ReactionList[i].ReactionType == ReactionTypes.ResetDetectionDistance)
|
||||
{
|
||||
ResetDetectionDistance();
|
||||
}
|
||||
else if (SentReactionObject.ReactionList[i].ReactionType == ReactionTypes.ResetLookAtPosition)
|
||||
{
|
||||
ResetLookAtPosition();
|
||||
}
|
||||
else if (SentReactionObject.ReactionList[i].ReactionType == ReactionTypes.ResetAllToDefault)
|
||||
{
|
||||
ResetAllToDefault();
|
||||
}
|
||||
else if (SentReactionObject.ReactionList[i].ReactionType == ReactionTypes.ReturnToStartingPosition)
|
||||
{
|
||||
ReturnToDefaultPosition();
|
||||
}
|
||||
else if (SentReactionObject.ReactionList[i].ReactionType == ReactionTypes.EnterCombatState)
|
||||
{
|
||||
SetCombatState(true);
|
||||
}
|
||||
else if (SentReactionObject.ReactionList[i].ReactionType == ReactionTypes.ExitCombatState)
|
||||
{
|
||||
SetCombatState(false);
|
||||
}
|
||||
else if (SentReactionObject.ReactionList[i].ReactionType == ReactionTypes.FleeFromLoudestTarget)
|
||||
{
|
||||
FleeFromLoudestTarget();
|
||||
}
|
||||
else if (SentReactionObject.ReactionList[i].ReactionType == ReactionTypes.AttractModifier)
|
||||
{
|
||||
TimeSinceLastAttractModifier = 0;
|
||||
AttractModifierInternal(SentReactionObject.ReactionList[i].IntValue2, SentReactionObject.ReactionList[i].IntValue1, SentReactionObject.ReactionList[i].FloatValue, SentReactionObject.ReactionList[i].ReactionType, SentReactionObject.ReactionList[i].AttractModifierReaction);
|
||||
|
||||
if (SentReactionObject.ReactionList[i].AttractModifierReaction != AttractModifierReactionTypes.LookAtAttractSource)
|
||||
{
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
if (SentReactionObject.ReactionList[i].BoolValue == true) yield return new WaitUntil(() => ArrivedAtDestination);
|
||||
}
|
||||
}
|
||||
else if (SentReactionObject.ReactionList[i].ReactionType == ReactionTypes.MoveToLoudestTarget)
|
||||
{
|
||||
CalculateMovement(1, 0, SentReactionObject.ReactionList[i].FloatValue, SentReactionObject.ReactionList[i].ReactionType, SentReactionObject.ReactionList[i].AttractModifierReaction);
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
if (SentReactionObject.ReactionList[i].BoolValue == true) yield return new WaitUntil(() => ArrivedAtDestination);
|
||||
}
|
||||
else if (SentReactionObject.ReactionList[i].ReactionType == ReactionTypes.MoveAroundCurrentPosition)
|
||||
{
|
||||
CalculateMovement(SentReactionObject.ReactionList[i].IntValue2, SentReactionObject.ReactionList[i].IntValue1, SentReactionObject.ReactionList[i].FloatValue, SentReactionObject.ReactionList[i].ReactionType, SentReactionObject.ReactionList[i].AttractModifierReaction);
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
if (SentReactionObject.ReactionList[i].BoolValue == true) yield return new WaitUntil(() => ArrivedAtDestination);
|
||||
}
|
||||
else if (SentReactionObject.ReactionList[i].ReactionType == ReactionTypes.MoveAroundLoudestTarget)
|
||||
{
|
||||
CalculateMovement(SentReactionObject.ReactionList[i].IntValue2, SentReactionObject.ReactionList[i].IntValue1, SentReactionObject.ReactionList[i].FloatValue, SentReactionObject.ReactionList[i].ReactionType, SentReactionObject.ReactionList[i].AttractModifierReaction);
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
if (SentReactionObject.ReactionList[i].BoolValue == true) yield return new WaitUntil(() => ArrivedAtDestination);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Debug Logs a message to the Unity Console (useful for testing mechanics and values).
|
||||
/// </summary>
|
||||
public void DebugLogMessage(string DebugMessage)
|
||||
{
|
||||
Debug.Log(DebugMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a new position to move to within the specified radius based on the passed transform.
|
||||
/// </summary>
|
||||
public void GenerateWaypoint(int Radius, Transform DestinationTransform)
|
||||
{
|
||||
if (DestinationTransform == null)
|
||||
{
|
||||
Debug.Log("Destination Transform is null. This reaction has been canceled.");
|
||||
return;
|
||||
}
|
||||
|
||||
//Destination within radius
|
||||
if (Radius > 0)
|
||||
{
|
||||
Vector3 NewDestination = DestinationTransform.transform.position + new Vector3(Random.Range(-1, 2), 0, Random.Range(-1, 2)) * Radius;
|
||||
RaycastHit HitDown;
|
||||
if (Physics.Raycast(new Vector3(NewDestination.x, NewDestination.y + 5, NewDestination.z), -transform.up, out HitDown, 10, EmeraldMovement.DynamicWanderLayerMask, QueryTriggerInteraction.Ignore))
|
||||
{
|
||||
UnityEngine.AI.NavMeshHit hit;
|
||||
if (UnityEngine.AI.NavMesh.SamplePosition(NewDestination, out hit, 5f, EmeraldComponent.m_NavMeshAgent.areaMask))
|
||||
{
|
||||
EmeraldComponent.m_NavMeshAgent.SetDestination(NewDestination);
|
||||
}
|
||||
}
|
||||
}
|
||||
//Exact destination
|
||||
else
|
||||
{
|
||||
EmeraldComponent.m_NavMeshAgent.SetDestination(DestinationTransform.transform.position);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the AI's current Look At Target.
|
||||
/// </summary>
|
||||
public void ClearLookAtTarget ()
|
||||
{
|
||||
if (CurrentTargetData.Exists(x => x.Target == EmeraldComponent.LookAtTarget))
|
||||
{
|
||||
EmeraldComponent.LookAtTarget = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the AI's Look At Target to the loudest detected target.
|
||||
/// </summary>
|
||||
public void LookAtLoudestTarget ()
|
||||
{
|
||||
if (CurrentTargetData.Count == 0)
|
||||
return;
|
||||
|
||||
EmeraldComponent.LookAtTarget = GetLoudestTarget(); //Assign the loudest detected target as the Look At Target.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the AI to its starting position (which is the value set within the Emerald AI Editor).
|
||||
/// </summary>
|
||||
public void ReturnToDefaultPosition()
|
||||
{
|
||||
EmeraldComponent.m_NavMeshAgent.destination = EmeraldMovement.StartingDestination;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expand the AI's Detection Distance, in addition to its current detection distance (useful for detecting a target that may have recently attacked a nearby target).
|
||||
/// </summary>
|
||||
public void ExpandDetectionDistance(int Distance)
|
||||
{
|
||||
if ((EmeraldDetection.StartingDetectionRadius + Distance) != EmeraldDetection.DetectionRadius)
|
||||
EmeraldDetection.DetectionRadius = EmeraldDetection.DetectionRadius + Distance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the AI's movement type.
|
||||
/// </summary>
|
||||
public void SetMovementState (EmeraldMovement.MovementStates MovementState)
|
||||
{
|
||||
EmeraldComponent.MovementComponent.CurrentMovementState = MovementState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the AI's Detection Distance to its default/starting value.
|
||||
/// </summary>
|
||||
void ResetDetectionDistance ()
|
||||
{
|
||||
EmeraldDetection.DetectionRadius = EmeraldDetection.StartingDetectionRadius;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the AI's Look at Position to its default/starting value after the passed amount of seconds has passed.
|
||||
/// </summary>
|
||||
void ResetLookAtPosition()
|
||||
{
|
||||
//If the AI dies before the needed amount of seconds has passed.
|
||||
if (EmeraldComponent.AnimationComponent.IsDead)
|
||||
return;
|
||||
|
||||
EmeraldComponent.LookAtTarget = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets all modified values back to their default values (Look At Position, Detection Distance, Movement State, and Combat State).
|
||||
/// </summary>
|
||||
void ResetAllToDefault ()
|
||||
{
|
||||
EmeraldDetection.DetectionRadius = EmeraldDetection.StartingDetectionRadius;
|
||||
EmeraldComponent.LookAtTarget = null;
|
||||
EmeraldMovement.CurrentMovementState = EmeraldMovement.StartingMovementState;
|
||||
SetCombatState(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows external mechanics from an AttractModifier (collisions, triggers, OnStart, and custom calls) to invoke a Reaction Object.
|
||||
/// </summary>
|
||||
public void AttractModifierInternal(int TotalWaypoints, int Radius, float WaitTime, ReactionTypes ReactionType, AttractModifierReactionTypes AttractModifierReaction)
|
||||
{
|
||||
if (DetectedAttractModifier == null)
|
||||
return;
|
||||
|
||||
if (AttractModifierReaction == AttractModifierReactionTypes.MoveToAttractSource)
|
||||
{
|
||||
CalculateMovement(1, 0, WaitTime, ReactionType, AttractModifierReaction);
|
||||
}
|
||||
else if (AttractModifierReaction == AttractModifierReactionTypes.MoveAroundAttractSource)
|
||||
{
|
||||
CalculateMovement(TotalWaypoints, Radius, WaitTime, ReactionType, AttractModifierReaction);
|
||||
}
|
||||
else if (AttractModifierReaction == AttractModifierReactionTypes.LookAtAttractSource)
|
||||
{
|
||||
EmeraldComponent.GetComponent<EmeraldSystem>().LookAtTarget = DetectedAttractModifier.transform;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the combat state. If true, this allows the AI to use its combat state animations. If false, returns the AI to its default state using non-combat animations.
|
||||
/// </summary>
|
||||
public void SetCombatState (bool State)
|
||||
{
|
||||
EmeraldComponent.m_NavMeshAgent.ResetPath();
|
||||
EmeraldComponent.AIAnimator.SetBool("Idle Active", false);
|
||||
EmeraldComponent.AIAnimator.SetBool("Combat State Active", State);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the proper waypoint based on the reaction passed.
|
||||
/// </summary>
|
||||
void GenerateWaypointInternal (int Radius, ReactionTypes ReactionType, AttractModifierReactionTypes AttractModifierReaction)
|
||||
{
|
||||
if (CurrentTargetData.Count > 0 && ReactionType == ReactionTypes.MoveAroundLoudestTarget || CurrentTargetData.Count > 0 && ReactionType == ReactionTypes.MoveToLoudestTarget)
|
||||
{
|
||||
GenerateWaypoint(Radius, GetLoudestTarget());
|
||||
}
|
||||
else if (ReactionType == ReactionTypes.AttractModifier)
|
||||
{
|
||||
GenerateWaypoint(Radius, DetectedAttractModifier.transform);
|
||||
}
|
||||
else
|
||||
{
|
||||
GenerateWaypoint(Radius, transform);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calulates the AI's next series of moves triggered by a reaction.
|
||||
/// </summary>
|
||||
void CalculateMovement (int TotalWaypoints, int Radius, float WaitTime, ReactionTypes ReactionType, AttractModifierReactionTypes AttractModifierReaction)
|
||||
{
|
||||
if (CalculateMovementCoroutine != null) StopCoroutine(CalculateMovementCoroutine);
|
||||
CalculateMovementCoroutine = StartCoroutine(CalculateMovementInternal(TotalWaypoints, Radius, WaitTime, ReactionType, AttractModifierReaction));
|
||||
}
|
||||
|
||||
IEnumerator CalculateMovementInternal(int TotalWaypoints, int Radius, float WaitTime, ReactionTypes ReactionType, AttractModifierReactionTypes AttractModifierReaction)
|
||||
{
|
||||
EmeraldComponent.MovementComponent.ChangeWanderType(EmeraldMovement.WanderTypes.Stationary); //Changes the AI's Wander Type to Stationary so that its default wandering type doesn't interfere with this waypoint generation process.
|
||||
ArrivedAtDestination = false; //Used for confirming when an AI arrives at its destination elsewhere. A variable and a delay is needed to avoid this giving a false positive.
|
||||
EmeraldComponent.m_NavMeshAgent.ResetPath(); //Reset the AI's current path/destination
|
||||
int CurrentWaypoints = 1; //Count the current of generated waypoints.
|
||||
float WaitTimer = 0; //Used to allow the AI to stay at each waypoint according to the user set WaitTime.
|
||||
|
||||
GenerateWaypointInternal(Radius, ReactionType, AttractModifierReaction);
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
ClearTurningValues();
|
||||
|
||||
while (CurrentWaypoints <= TotalWaypoints)
|
||||
{
|
||||
//If the AI goes into Combat Mode, exit generating waypoints and set the Wander Type back to its default.
|
||||
if (EmeraldComponent.CombatComponent.CombatState)
|
||||
{
|
||||
EmeraldComponent.MovementComponent.ChangeWanderType((EmeraldMovement.WanderTypes)EmeraldMovement.StartingWanderingType);
|
||||
yield break;
|
||||
}
|
||||
|
||||
//Generate a waypoint for until the TotalWaypoints have been met. When the AI has arrived at each waypoint, wait according to the WaitTime.
|
||||
if (EmeraldComponent.m_NavMeshAgent.remainingDistance < EmeraldMovement.StoppingDistance && !EmeraldComponent.m_NavMeshAgent.pathPending)
|
||||
{
|
||||
WaitTimer += Time.deltaTime;
|
||||
|
||||
if (WaitTimer > WaitTime)
|
||||
{
|
||||
GenerateWaypointInternal(Radius, ReactionType, AttractModifierReaction);
|
||||
ClearTurningValues();
|
||||
WaitTimer = 0;
|
||||
|
||||
if (CurrentWaypoints == TotalWaypoints)
|
||||
{
|
||||
EmeraldComponent.m_NavMeshAgent.ResetPath();
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentWaypoints++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(WaitTime);
|
||||
ArrivedAtDestination = true;
|
||||
EmeraldMovement.WaypointTimer = 0;
|
||||
//Change the AI's Wander Type back to its default so it can continue functioning as it was.
|
||||
EmeraldComponent.MovementComponent.ChangeWanderType((EmeraldMovement.WanderTypes)EmeraldMovement.StartingWanderingType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the AI's flee target as the loudest detected target. (Cautious Coward AI Only)
|
||||
/// </summary>
|
||||
void FleeFromLoudestTarget()
|
||||
{
|
||||
EmeraldComponent.DetectionComponent.SetDetectedTarget(GetLoudestTarget());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the loudest detected target.
|
||||
/// </summary>
|
||||
Transform GetLoudestTarget ()
|
||||
{
|
||||
//Return null of there's no current targets.
|
||||
if (CurrentTargetData.Count == 0)
|
||||
return null;
|
||||
|
||||
float MaxNoiseLevel = CurrentTargetData.Max(x => x.NoiseLevel); //Find the highest level of noise within CurrentTargetData
|
||||
Transform LoudestTarget = CurrentTargetData.Find(x => x.NoiseLevel == MaxNoiseLevel).Target; //Using the highest level of noise, find that target and assign its position as the PositionOfInterest
|
||||
return LoudestTarget;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the AI's internal turning values.
|
||||
/// </summary>
|
||||
void ClearTurningValues ()
|
||||
{
|
||||
EmeraldComponent.AnimationComponent.IsTurning = false;
|
||||
EmeraldComponent.MovementComponent.LockTurning = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d813b5e51839b2945ae81518686daa6f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f9495de641a94b4193c18f7d20a0e9b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
namespace EmeraldAI.SoundDetection
|
||||
{
|
||||
public enum AttractModifierReactionTypes
|
||||
{
|
||||
LookAtAttractSource = 0,
|
||||
MoveAroundAttractSource = 25,
|
||||
MoveToAttractSource = 50,
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 93b419bc9c0acc74aa082d2d545479e9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace EmeraldAI.SoundDetection
|
||||
{
|
||||
public enum ReactionTypes
|
||||
{
|
||||
None = 0,
|
||||
AttractModifier = 25,
|
||||
DebugLogMessage = 50,
|
||||
Delay = 75,
|
||||
EnterCombatState = 100,
|
||||
ExitCombatState = 125,
|
||||
ExpandDetectionDistance = 150,
|
||||
FleeFromLoudestTarget = 162,
|
||||
LookAtLoudestTarget = 175,
|
||||
MoveAroundCurrentPosition = 200,
|
||||
MoveAroundLoudestTarget = 225,
|
||||
MoveToLoudestTarget = 250,
|
||||
PlayEmoteAnimation = 275,
|
||||
PlaySound = 300,
|
||||
ResetAllToDefault = 325,
|
||||
ResetDetectionDistance = 350,
|
||||
ResetLookAtPosition = 375,
|
||||
ReturnToStartingPosition = 400,
|
||||
SetMovementState = 425,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 13401eaea48e3dc42bcead79faa52962
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace EmeraldAI.SoundDetection
|
||||
{
|
||||
public enum ThreatLevels
|
||||
{
|
||||
Unaware,
|
||||
Suspicious,
|
||||
Aware,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 79cccb429dd32eb46af20be65981d537
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace EmeraldAI.SoundDetection
|
||||
{
|
||||
public enum TriggerTypes
|
||||
{
|
||||
OnStart = 0,
|
||||
OnTrigger = 5,
|
||||
OnCollision = 10,
|
||||
OnCustomCall = 15,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0bc8a66927c16c4b83ee5dcf5643429
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EmeraldAI.SoundDetection
|
||||
{
|
||||
[System.Serializable]
|
||||
public class Reaction
|
||||
{
|
||||
public ReactionTypes ReactionType = ReactionTypes.None;
|
||||
public int IntValue1 = 5;
|
||||
public int IntValue2 = 2;
|
||||
public string StringValue = "New Message";
|
||||
public float FloatValue = 1f;
|
||||
public bool BoolValue = true;
|
||||
public AudioClip SoundRef;
|
||||
public AttractModifierReactionTypes AttractModifierReaction = AttractModifierReactionTypes.MoveToAttractSource;
|
||||
public EmeraldMovement.MovementStates MovementState = EmeraldMovement.MovementStates.Walk;
|
||||
public ElementLineHeights ElementLineHeight = ElementLineHeights.One;
|
||||
public enum ElementLineHeights
|
||||
{ One,Two,Three,Four,Five,Six }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e03a6e0879362894da3b01d7fce3c89c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EmeraldAI.SoundDetection
|
||||
{
|
||||
[CreateAssetMenu(fileName = "Reaction Object", menuName = "Emerald AI/Reaction Object")]
|
||||
[System.Serializable]
|
||||
public class ReactionObject : ScriptableObject
|
||||
{
|
||||
[SerializeField]
|
||||
public List<Reaction> ReactionList = new List<Reaction>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c8fd5025364866646808a99081f6911f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,35 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace EmeraldAI.Utility
|
||||
{
|
||||
[ExecuteInEditMode]
|
||||
[HelpURL("https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-optional/target-position-modifier-component")]
|
||||
public class TargetPositionModifier : MonoBehaviour
|
||||
{
|
||||
public bool HideSettingsFoldout;
|
||||
public bool TPMSettingsFoldout = false;
|
||||
public Transform TransformSource;
|
||||
public float PositionModifier = 0;
|
||||
public float GizmoRadius = 0.15f;
|
||||
public Color GizmoColor = new Color(1f, 0, 0, 0.8f);
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (TransformSource == null && Application.isPlaying)
|
||||
{
|
||||
Debug.LogError("<b>Target Position Modifier:</b> " + "No Transform Source has been assigned on " + gameObject.name + ". The Transform Source will be set as this object instead (which may be undesirable). To resolve this, add a proper Transform Source through the Target Position Modifier editor.");
|
||||
TransformSource = transform;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
if (TransformSource == null || !TPMSettingsFoldout || HideSettingsFoldout)
|
||||
return;
|
||||
|
||||
Gizmos.color = GizmoColor;
|
||||
Gizmos.DrawSphere(TransformSource.position + (Vector3.up * PositionModifier), GizmoRadius);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8baf1386c3b869a498388fe85639e4f4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2286257fed130f94ea33741f706d2d75
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea0329fda166bc34897dc91faeeff562
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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:
|
||||
@@ -0,0 +1,722 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using EmeraldAI.Utility;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[HelpURL("https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-required/animation-component")]
|
||||
public class EmeraldAnimation : MonoBehaviour
|
||||
{
|
||||
#region Animation States
|
||||
public AnimatorStateInfo CurrentStateInfo;
|
||||
public bool InternalDodge; //In order to detect dodges between transitions, a custom bool is needed to avoid it being missed or playing while hit is playing.
|
||||
public bool InternalBlock; //In order to detect blocks between transitions, a custom bool is needed to avoid it being missed or playing while attack is playing.
|
||||
public bool InternalHit; //In order to detect hits between transitions, a custom bool is needed to avoid it being missed or playing while dodge is playing.
|
||||
public bool IsEmoting;
|
||||
public bool IsIdling;
|
||||
public bool IsAttacking;
|
||||
public bool IsStrafing;
|
||||
public bool IsBlocking;
|
||||
public bool IsDodging;
|
||||
public bool IsRecoiling;
|
||||
public bool IsStunned;
|
||||
public bool IsGettingHit;
|
||||
public bool IsEquipping;
|
||||
public bool IsBackingUp;
|
||||
public bool IsTurning;
|
||||
public bool IsTurningLeft, IsTurningRight;
|
||||
public bool IsSwitchingWeapons;
|
||||
public bool IsWarning;
|
||||
public bool IsMoving;
|
||||
public bool IsDead;
|
||||
public bool m_IdleAnimaionIndexOverride = false;
|
||||
#endregion
|
||||
|
||||
#region Animation Variables
|
||||
public EmeraldAI.Utility.AnimationProfile m_AnimationProfile;
|
||||
public bool AnimatorControllerGenerated = false;
|
||||
public bool AnimationListsChanged = false;
|
||||
public bool MissingRuntimeController = false;
|
||||
public bool AnimationsUpdated = false;
|
||||
public Animator AIAnimator;
|
||||
public bool AttackingTracker; //Called right when an attack is generated
|
||||
public bool AttackTriggered; //Briefly called while an attack is playing
|
||||
public bool WarningAnimationTriggered = false;
|
||||
public bool BusyBetweenStates = false;
|
||||
public AnimationStateTypes CurrentAnimationState = AnimationStateTypes.Idling;
|
||||
public delegate void GetHitHandler();
|
||||
public event GetHitHandler OnGetHit;
|
||||
public delegate void RecoilHandler();
|
||||
public event RecoilHandler OnRecoil;
|
||||
public delegate void StartAttackAnimationHandler();
|
||||
public event StartAttackAnimationHandler OnStartAttackAnimation;
|
||||
public delegate void EndAttackAnimationHandler();
|
||||
public event StartAttackAnimationHandler OnEndAttackAnimation;
|
||||
float LastHitTime;
|
||||
Coroutine StunnedCoroutine;
|
||||
EmeraldSystem EmeraldComponent;
|
||||
#endregion
|
||||
|
||||
#region Editor Variables
|
||||
public string[] Type1AttackEnumAnimations;
|
||||
public string[] Type2AttackEnumAnimations;
|
||||
public string[] Type1AttackBlankOptions = { "No Type 1 Attack Animations" };
|
||||
public string[] Type2AttackBlankOptions = { "No Type 2 Attack Animations" };
|
||||
public bool HideSettingsFoldout;
|
||||
public bool AnimationProfileFoldout;
|
||||
#endregion
|
||||
|
||||
void Start()
|
||||
{
|
||||
InitailizeAnimations();
|
||||
SetupAnimator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the Animation Component.
|
||||
/// </summary>
|
||||
void InitailizeAnimations()
|
||||
{
|
||||
AIAnimator = GetComponent<Animator>();
|
||||
AIAnimator.runtimeAnimatorController = m_AnimationProfile.AIAnimator;
|
||||
EmeraldComponent = GetComponent<EmeraldSystem>();
|
||||
|
||||
EmeraldComponent.HealthComponent.OnTakeDamage += PlayHitAnimation; //Subscribe to the OnTakeDamage event for Hit Animations
|
||||
EmeraldComponent.HealthComponent.OnTakeCritDamage += PlayHitAnimation; //Subscribe to the OnTakeCritDamage event for Hit Animations
|
||||
EmeraldComponent.HealthComponent.OnBlock += PlayHitAnimation; //Subscribe to the OnTakeCritDamage event for Block Hit Animations
|
||||
EmeraldComponent.HealthComponent.OnDeath += PlayDeathAnimation; //Subscribe to the OnDeath event for Death Animations
|
||||
EmeraldComponent.MovementComponent.OnReachedWaypoint += PlayIdleAnimation; //Subscribe to the OnReachedWaypoint event for Idle Animations
|
||||
EmeraldComponent.CombatComponent.OnExitCombat += ReturnToDefaultState; //Subscribe to the OnExitCombat event for ReturnToDefaultState
|
||||
AIAnimator.cullingMode = m_AnimationProfile.AnimatorCullingMode;
|
||||
|
||||
InitializeWeaponTypeAnimationAndSettings();
|
||||
AIAnimator.updateMode = AnimatorUpdateMode.Normal;
|
||||
AIAnimator.SetFloat("Offset", Random.Range(0.0f, 1.0f)); //Add a randomized offset so AI sharing animations don't start at the exact same frame.
|
||||
}
|
||||
|
||||
public void AnimationUpdate ()
|
||||
{
|
||||
EmeraldComponent.AnimationComponent.CurrentStateInfo = AIAnimator.GetCurrentAnimatorStateInfo(0); //Update the CurrentStateInfo, which is used for getting the current state and tracking the AI's current states and animations.
|
||||
CheckAnimationStates(); //Keeps track of the current animation state.
|
||||
|
||||
//While simple, this is needed to check when the current attack animation finishes. When it does, generate a new attack.
|
||||
if (IsAttacking && !AttackingTracker)
|
||||
{
|
||||
OnStartAttackAnimation?.Invoke();
|
||||
AttackingTracker = true;
|
||||
AttackTriggered = true;
|
||||
Invoke(nameof(StopAttackTrigger), 0.5f);
|
||||
}
|
||||
else if (!IsAttacking && AttackingTracker)
|
||||
{
|
||||
OnEndAttackAnimation?.Invoke();
|
||||
EmeraldCombatManager.GenerateNextAttack(EmeraldComponent); //Generate the next attack once the current one has concluded.
|
||||
}
|
||||
|
||||
//Set AttackTriggered to false if an priority state is triggered.
|
||||
//This can happen if an attack was generated, but another state gets set active before it could trigger through the Animator.
|
||||
if (AttackTriggered)
|
||||
{
|
||||
if (IsMoving || IsTurning || IsStunned || IsStrafing || IsBackingUp || IsBlocking || IsDodging || InternalHit || !AttackingTracker) AttackTriggered = false;
|
||||
}
|
||||
}
|
||||
|
||||
void StopAttackTrigger()
|
||||
{
|
||||
AttackTriggered = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Keeps track of the current animation state.
|
||||
/// </summary>
|
||||
public void CheckAnimationStates()
|
||||
{
|
||||
if (!EmeraldComponent.CombatComponent.CombatState) IsIdling = CurrentStateInfo.IsName("Movement") && AIAnimator.GetFloat("Speed") < 0.1f && !IsBackingUp || CurrentStateInfo.IsTag("Idle");
|
||||
if (!EmeraldComponent.CombatComponent.CombatState) IsMoving = CurrentStateInfo.IsName("Movement") && AIAnimator.GetFloat("Speed") >= 0.1f && !IsBackingUp;
|
||||
|
||||
if (EmeraldComponent.CombatComponent.CombatState && EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1)
|
||||
{
|
||||
IsIdling = CurrentStateInfo.IsName("Combat Movement (Type 1)") && AIAnimator.GetFloat("Speed") < 0.1f;
|
||||
IsMoving = CurrentStateInfo.IsName("Combat Movement (Type 1)") && AIAnimator.GetFloat("Speed") >= 0.1f && !IsBackingUp && !IsAttacking;
|
||||
}
|
||||
else if (EmeraldComponent.CombatComponent.CombatState && EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2)
|
||||
{
|
||||
IsIdling = CurrentStateInfo.IsName("Combat Movement (Type 2)") && AIAnimator.GetFloat("Speed") < 0.1f;
|
||||
IsMoving = CurrentStateInfo.IsName("Combat Movement (Type 2)") && AIAnimator.GetFloat("Speed") >= 0.1f && !IsBackingUp && !IsAttacking;
|
||||
}
|
||||
|
||||
IsEquipping = CurrentStateInfo.IsTag("Equip");
|
||||
IsBlocking = CurrentStateInfo.IsTag("Block");
|
||||
IsRecoiling = CurrentStateInfo.IsTag("Recoil");
|
||||
IsStunned = CurrentStateInfo.IsTag("Stunned");
|
||||
IsStrafing = CurrentStateInfo.IsTag("Strafing");
|
||||
IsDodging = CurrentStateInfo.IsTag("Dodging") || InternalDodge;
|
||||
IsBackingUp = CurrentStateInfo.IsTag("Backing Up") || AIAnimator.GetBool("Walk Backwards");
|
||||
IsAttacking = CurrentStateInfo.IsTag("Attack");
|
||||
IsGettingHit = CurrentStateInfo.IsTag("Hit");
|
||||
IsWarning = CurrentStateInfo.IsTag("Warning");
|
||||
IsEmoting = CurrentStateInfo.IsTag("Emote");
|
||||
|
||||
//This is used to determine when an AI is in between combat and non-combat states. This stops undsired rotations that happens during these transitions and allows the mechanics to function much smoother.
|
||||
BusyBetweenStates = AIAnimator.GetAnimatorTransitionInfo(0).IsName("Combat Movement (Type 1) -> Movement") || AIAnimator.GetAnimatorTransitionInfo(0).IsName("Combat Movement (Type 2) -> Movement") ||
|
||||
AIAnimator.GetAnimatorTransitionInfo(0).IsName("Movement -> Combat Movement (Type 1)") || AIAnimator.GetAnimatorTransitionInfo(0).IsName("Movement -> Combat Movement (Type 2)") ||
|
||||
AIAnimator.GetAnimatorTransitionInfo(0).IsName("Combat Movement (Type 1) -> Put Away Weapon (Type 1)") || AIAnimator.GetAnimatorTransitionInfo(0).IsName("Combat Movement (Type 2) -> Put Away Weapon (Type 2)") ||
|
||||
AIAnimator.GetAnimatorTransitionInfo(0).IsName("Put Away Weapon (Type 1) -> Movement") || AIAnimator.GetAnimatorTransitionInfo(0).IsName("Put Away Weapon (Type 2) -> Movement");
|
||||
|
||||
if (IsIdling) CurrentAnimationState = AnimationStateTypes.Idling;
|
||||
if (IsMoving) CurrentAnimationState = AnimationStateTypes.Moving;
|
||||
if (IsTurningLeft) CurrentAnimationState = AnimationStateTypes.TurningLeft;
|
||||
if (IsTurningRight) CurrentAnimationState = AnimationStateTypes.TurningRight;
|
||||
if (IsEquipping) CurrentAnimationState = AnimationStateTypes.Equipping;
|
||||
if (IsBlocking) CurrentAnimationState = AnimationStateTypes.Blocking;
|
||||
if (IsRecoiling) CurrentAnimationState = AnimationStateTypes.Recoiling;
|
||||
if (IsStunned) CurrentAnimationState = AnimationStateTypes.Stunned;
|
||||
if (IsStrafing) CurrentAnimationState = AnimationStateTypes.Strafing;
|
||||
if (IsDodging) CurrentAnimationState = AnimationStateTypes.Dodging;
|
||||
if (IsBackingUp) CurrentAnimationState = AnimationStateTypes.BackingUp;
|
||||
if (IsAttacking) CurrentAnimationState = AnimationStateTypes.Attacking;
|
||||
if (IsGettingHit) CurrentAnimationState = AnimationStateTypes.GettingHit;
|
||||
if (IsDead) CurrentAnimationState = AnimationStateTypes.Dead;
|
||||
if (IsEmoting) CurrentAnimationState = AnimationStateTypes.Emoting;
|
||||
if (IsSwitchingWeapons) CurrentAnimationState = AnimationStateTypes.SwitchingWeapons;
|
||||
}
|
||||
|
||||
public void ResetSettings()
|
||||
{
|
||||
//Reapply the AI's Animator Controller settings applied on Start because, when the
|
||||
//Animator Controller is disabled, they're reset to their default settings.
|
||||
SetWeaponTypeAnimationState();
|
||||
AIAnimator.SetBool("Idle Active", false);
|
||||
InitializeWeaponTypeAnimationAndSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets up all Animator related settings.
|
||||
/// </summary>
|
||||
public void SetupAnimator ()
|
||||
{
|
||||
AIAnimator = GetComponent<Animator>();
|
||||
EmeraldComponent = GetComponent<EmeraldSystem>(); ;
|
||||
|
||||
if (AIAnimator.layerCount >= 2)
|
||||
AIAnimator.SetLayerWeight(1, 1);
|
||||
|
||||
if (GetComponent<EmeraldMovement>().MovementType == EmeraldMovement.MovementTypes.RootMotion)
|
||||
{
|
||||
EmeraldComponent.m_NavMeshAgent.speed = 0;
|
||||
AIAnimator.applyRootMotion = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AIAnimator.applyRootMotion = false;
|
||||
}
|
||||
|
||||
if (AIAnimator.layerCount >= 2)
|
||||
{
|
||||
AIAnimator.SetLayerWeight(1, 1);
|
||||
}
|
||||
|
||||
SetWeaponTypeAnimationState();
|
||||
|
||||
AIAnimator.SetInteger("Idle Index", Random.Range(0, m_AnimationProfile.NonCombatAnimations.IdleList.Count));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets up all Animator related weapon type settings.
|
||||
/// </summary>
|
||||
public void InitializeWeaponTypeAnimationAndSettings()
|
||||
{
|
||||
if (EmeraldComponent.CombatComponent.WeaponTypeAmount == EmeraldCombat.WeaponTypeAmounts.Two)
|
||||
{
|
||||
if (EmeraldComponent.CombatComponent.StartingWeaponType == EmeraldCombat.WeaponTypes.Type1)
|
||||
{
|
||||
AIAnimator.SetInteger("Weapon Type State", 1);
|
||||
EmeraldComponent.CombatComponent.CurrentWeaponType = EmeraldCombat.WeaponTypes.Type1;
|
||||
EmeraldComponent.DetectionComponent.PickTargetType = EmeraldComponent.CombatComponent.Type1PickTargetType;
|
||||
}
|
||||
else if (EmeraldComponent.CombatComponent.StartingWeaponType == EmeraldCombat.WeaponTypes.Type2)
|
||||
{
|
||||
AIAnimator.SetInteger("Weapon Type State", 2);
|
||||
EmeraldComponent.CombatComponent.CurrentWeaponType = EmeraldCombat.WeaponTypes.Type2;
|
||||
EmeraldComponent.DetectionComponent.PickTargetType = EmeraldComponent.CombatComponent.Type2PickTargetType;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AIAnimator.SetInteger("Weapon Type State", 1);
|
||||
EmeraldComponent.CombatComponent.CurrentWeaponType = EmeraldCombat.WeaponTypes.Type1;
|
||||
EmeraldComponent.DetectionComponent.PickTargetType = EmeraldComponent.CombatComponent.Type1PickTargetType;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether or not Animate Weapon State will be enabled (depending on if the user has applied the equipping and uneuipping animations).
|
||||
/// </summary>
|
||||
void SetWeaponTypeAnimationState ()
|
||||
{
|
||||
if (EmeraldComponent.CombatComponent.WeaponTypeAmount == EmeraldCombat.WeaponTypeAmounts.One)
|
||||
{
|
||||
if (m_AnimationProfile.Type1Animations.PutAwayWeapon.AnimationClip == null || m_AnimationProfile.Type1Animations.PullOutWeapon.AnimationClip == null)
|
||||
AIAnimator.SetBool("Animate Weapon State", false);
|
||||
else if (m_AnimationProfile.Type1Animations.PutAwayWeapon.AnimationClip != null && m_AnimationProfile.Type1Animations.PullOutWeapon.AnimationClip != null)
|
||||
AIAnimator.SetBool("Animate Weapon State", true);
|
||||
}
|
||||
else if (EmeraldComponent.CombatComponent.WeaponTypeAmount == EmeraldCombat.WeaponTypeAmounts.Two)
|
||||
{
|
||||
if (m_AnimationProfile.Type1Animations.PutAwayWeapon.AnimationClip == null && m_AnimationProfile.Type1Animations.PullOutWeapon.AnimationClip == null &&
|
||||
m_AnimationProfile.Type2Animations.PutAwayWeapon.AnimationClip == null && m_AnimationProfile.Type2Animations.PullOutWeapon.AnimationClip == null)
|
||||
AIAnimator.SetBool("Animate Weapon State", false);
|
||||
else if (m_AnimationProfile.Type1Animations.PutAwayWeapon.AnimationClip != null && m_AnimationProfile.Type1Animations.PullOutWeapon.AnimationClip != null &&
|
||||
m_AnimationProfile.Type2Animations.PutAwayWeapon.AnimationClip != null && m_AnimationProfile.Type2Animations.PullOutWeapon.AnimationClip != null)
|
||||
AIAnimator.SetBool("Animate Weapon State", true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random idle animation index and plays an idle animation.
|
||||
/// </summary>
|
||||
public void PlayIdleAnimation ()
|
||||
{
|
||||
if (!EmeraldComponent.AnimationComponent.m_IdleAnimaionIndexOverride && m_AnimationProfile.NonCombatAnimations.IdleList.Count > 0 &&
|
||||
EmeraldComponent.MovementComponent.WaypointType != EmeraldMovement.WaypointTypes.Loop)
|
||||
{
|
||||
AIAnimator.SetInteger("Idle Index", Random.Range(1, m_AnimationProfile.NonCombatAnimations.IdleList.Count+1));
|
||||
AIAnimator.SetBool("Idle Active", true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays an AI's Warning animation for the current Weapon Type.
|
||||
/// </summary>
|
||||
public void PlayWarningAnimation ()
|
||||
{
|
||||
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1 && m_AnimationProfile.Type1Animations.IdleWarning.AnimationClip == null ||
|
||||
EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2 && m_AnimationProfile.Type2Animations.IdleWarning.AnimationClip == null ||
|
||||
WarningAnimationTriggered)
|
||||
return;
|
||||
|
||||
AIAnimator.SetTrigger("Warning");
|
||||
WarningAnimationTriggered = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a stunned animation depending on the StunnedLength.
|
||||
/// </summary>
|
||||
public void PlayStunnedAnimation (float StunnedLength)
|
||||
{
|
||||
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1 && m_AnimationProfile.Type1Animations.Stunned.AnimationClip == null ||
|
||||
EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2 && m_AnimationProfile.Type2Animations.Stunned.AnimationClip == null) return;
|
||||
|
||||
if (!IsStunned && !AIAnimator.GetBool("Blocking") && !AIAnimator.GetBool("Dodge Triggered") && !IsDodging && transform.localScale != Vector3.one * 0.003f)
|
||||
{
|
||||
if (StunnedCoroutine != null) StopCoroutine(StunnedCoroutine);
|
||||
StunnedCoroutine = StartCoroutine(SetStunned(StunnedLength));
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator SetStunned(float StunnedLength)
|
||||
{
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
if (IsDodging || IsDead) yield break; //If this AI is doding or is dead, don't trigger a stun.
|
||||
AIAnimator.SetBool("Stunned Active", true);
|
||||
yield return new WaitForSeconds(StunnedLength);
|
||||
AIAnimator.SetBool("Stunned Active", false);
|
||||
EmeraldComponent.BehaviorsComponent.IsAiming = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a random death animation from the AI's DeathList. If either DeathList is empty, it is assumed ragdoll deaths are being used.
|
||||
/// </summary>
|
||||
public void PlayDeathAnimation ()
|
||||
{
|
||||
//Only play a death animation if the current weapon type death animation lists have animations in them.
|
||||
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1 && m_AnimationProfile.Type1Animations.DeathList.Count == 0 ||
|
||||
EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2 && m_AnimationProfile.Type2Animations.DeathList.Count == 0)
|
||||
return;
|
||||
|
||||
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1)
|
||||
{
|
||||
AIAnimator.SetInteger("Death Index", Random.Range(1, m_AnimationProfile.Type1Animations.DeathList.Count + 1));
|
||||
int DeathIndex = AIAnimator.GetInteger("Death Index");
|
||||
StartCoroutine(DisableAnimator(m_AnimationProfile.Type1Animations.DeathList[DeathIndex-1].AnimationClip.length / m_AnimationProfile.Type1Animations.DeathList[DeathIndex - 1].AnimationSpeed));
|
||||
}
|
||||
else if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2)
|
||||
{
|
||||
AIAnimator.SetInteger("Death Index", Random.Range(1, m_AnimationProfile.Type2Animations.DeathList.Count + 1));
|
||||
int DeathIndex = AIAnimator.GetInteger("Death Index");
|
||||
StartCoroutine(DisableAnimator(m_AnimationProfile.Type2Animations.DeathList[DeathIndex-1].AnimationClip.length / m_AnimationProfile.Type2Animations.DeathList[DeathIndex - 1].AnimationSpeed));
|
||||
}
|
||||
|
||||
AIAnimator.SetTrigger("Dead");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a hit animation depending on the user set Animation State Conditions.
|
||||
/// </summary>
|
||||
public void PlayHitAnimation ()
|
||||
{
|
||||
//Get the current hit animation cooldown depending on the weapon type.
|
||||
float CurrentHitAnimationCooldown = EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1 ? m_AnimationProfile.Type1HitAnimationCooldown : m_AnimationProfile.Type2HitAnimationCooldown;
|
||||
|
||||
//Don't play a hit animation if an AI is dead or if the CurrentHitAnimationCooldown hasn't passed.
|
||||
if (EmeraldComponent.HealthComponent.CurrentHealth <= 0 || Time.time < (LastHitTime + CurrentHitAnimationCooldown))
|
||||
return;
|
||||
|
||||
LastHitTime = Time.time;
|
||||
|
||||
if (!EmeraldComponent.CombatComponent.CombatState)
|
||||
{
|
||||
if (m_AnimationProfile.NonCombatAnimations.HitList.Count == 0 && !IsBlocking)
|
||||
return;
|
||||
|
||||
int CurrentIndex = AIAnimator.GetInteger("Hit Index");
|
||||
AIAnimator.SetInteger("Hit Index", CurrentIndex);
|
||||
CurrentIndex++;
|
||||
if (CurrentIndex == m_AnimationProfile.NonCombatAnimations.HitList.Count + 1) CurrentIndex = 1;
|
||||
AIAnimator.SetInteger("Hit Index", CurrentIndex);
|
||||
}
|
||||
else if (EmeraldComponent.CombatComponent.CombatState && !IsBlocking)
|
||||
{
|
||||
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1)
|
||||
{
|
||||
if (m_AnimationProfile.Type1Animations.HitList.Count == 0 && !IsBlocking)
|
||||
return;
|
||||
|
||||
int CurrentIndex = AIAnimator.GetInteger("Hit Index");
|
||||
AIAnimator.SetInteger("Hit Index", CurrentIndex);
|
||||
CurrentIndex++;
|
||||
if (CurrentIndex >= m_AnimationProfile.Type1Animations.HitList.Count + 1) CurrentIndex = 1;
|
||||
AIAnimator.SetInteger("Hit Index", CurrentIndex);
|
||||
}
|
||||
else if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2)
|
||||
{
|
||||
if (m_AnimationProfile.Type2Animations.HitList.Count == 0 && !IsBlocking)
|
||||
return;
|
||||
|
||||
int CurrentIndex = AIAnimator.GetInteger("Hit Index");
|
||||
AIAnimator.SetInteger("Hit Index", CurrentIndex);
|
||||
CurrentIndex++;
|
||||
if (CurrentIndex >= m_AnimationProfile.Type2Animations.HitList.Count + 1) CurrentIndex = 1;
|
||||
AIAnimator.SetInteger("Hit Index", CurrentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
//This helps cancel block if an AI doesn't block in time
|
||||
if (!IsBlocking && AIAnimator.GetBool("Blocking"))
|
||||
{
|
||||
AIAnimator.SetBool("Blocking", false);
|
||||
}
|
||||
|
||||
//Only play a hit animation if the conditions are right. Some states are automatically excluded.
|
||||
if (!IsDodging && !IsSwitchingWeapons && !IsEquipping && !AIAnimator.GetBool("Dodge Triggered") && EmeraldComponent.HealthComponent.CurrentActiveEffects.Count == 0)
|
||||
{
|
||||
var Type1Conditions = (((int)m_AnimationProfile.Type1HitConditions) & ((int)CurrentAnimationState)) != 0;
|
||||
var Type2Conditions = (((int)m_AnimationProfile.Type2HitConditions) & ((int)CurrentAnimationState)) != 0;
|
||||
|
||||
if (Type1Conditions && EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1 || Type2Conditions && EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2)
|
||||
{
|
||||
//In order to bypass issues between frames and triggers, use an internal bool to determine when an AI is hit.
|
||||
//If an AI was hit within 0.5 seconds, an AI will ignore a dodge if it was triggered.
|
||||
InternalHit = true;
|
||||
Invoke(nameof(ResetInternalHit), 0.5f);
|
||||
AttackTriggered = false;
|
||||
|
||||
AIAnimator.SetTrigger("Hit");
|
||||
OnGetHit?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
AIAnimator.ResetTrigger("Attack");
|
||||
}
|
||||
|
||||
void ResetInternalHit()
|
||||
{
|
||||
InternalHit = false;
|
||||
AttackTriggered = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays an attack animation depending on the EmeraldComponent.CombatComponent.CurrentAnimationIndex.
|
||||
/// </summary>
|
||||
public void PlayAttackAnimation ()
|
||||
{
|
||||
if (!EmeraldComponent.CombatComponent.CurrentAttackData.CooldownIgnored) EmeraldComponent.CombatComponent.CurrentAttackData.CooldownTimeStamp = Time.time;
|
||||
|
||||
AIAnimator.SetInteger("Attack Index", EmeraldComponent.CombatComponent.CurrentAnimationIndex + 1);
|
||||
AIAnimator.SetTrigger("Attack");
|
||||
AttackTriggered = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates which turn animations to use while stationary.
|
||||
/// </summary>
|
||||
public void CalculateTurnAnimations(bool ByPassConditions = false)
|
||||
{
|
||||
//Lock turning, as soon as the angle threshold is met, for 1 second. This prevents an AI from getting stuck transitioning between two turning animations.
|
||||
if (!EmeraldComponent.CombatComponent.CombatState && EmeraldComponent.MovementComponent.DestinationAdjustedAngle <= EmeraldComponent.MovementComponent.AngleToTurn && !EmeraldComponent.MovementComponent.LockTurning)
|
||||
{
|
||||
EmeraldComponent.MovementComponent.LockTurning = true;
|
||||
StartCoroutine(LockTurns());
|
||||
DisableTurning();
|
||||
}
|
||||
|
||||
Vector3 DestinationDirection = EmeraldComponent.MovementComponent.DestinationDirection;
|
||||
|
||||
if (ByPassConditions || CanPlayTurningAnimation(DestinationDirection))
|
||||
{
|
||||
if (Time.timeSinceLevelLoad < 1f || EmeraldComponent.MovementComponent.LockTurning && !EmeraldComponent.CombatComponent.CombatState || IsBackingUp)
|
||||
return;
|
||||
|
||||
Vector3 cross = Vector3.Cross(transform.forward, Quaternion.LookRotation(DestinationDirection, Vector3.up) * Vector3.forward);
|
||||
|
||||
if (cross.y > 0.0f) //Right
|
||||
{
|
||||
EmeraldComponent.AnimationComponent.IsTurning = true;
|
||||
EmeraldComponent.AnimationComponent.IsTurningRight = true;
|
||||
EmeraldComponent.AnimationComponent.IsTurningLeft = false;
|
||||
AIAnimator.SetBool("Idle Active", false);
|
||||
AIAnimator.SetBool("Turn Right", true);
|
||||
AIAnimator.SetBool("Turn Left", false);
|
||||
}
|
||||
else if (cross.y < 0.0f) //Left
|
||||
{
|
||||
EmeraldComponent.AnimationComponent.IsTurning = true;
|
||||
EmeraldComponent.AnimationComponent.IsTurningLeft = true;
|
||||
EmeraldComponent.AnimationComponent.IsTurningRight = false;
|
||||
AIAnimator.SetBool("Idle Active", false);
|
||||
AIAnimator.SetBool("Turn Left", true);
|
||||
AIAnimator.SetBool("Turn Right", false);
|
||||
}
|
||||
|
||||
}
|
||||
else if (EmeraldComponent.CombatComponent.CombatState)
|
||||
{
|
||||
EmeraldComponent.AnimationComponent.IsTurning = false;
|
||||
EmeraldComponent.AnimationComponent.IsTurningLeft = false;
|
||||
EmeraldComponent.AnimationComponent.IsTurningRight = false;
|
||||
AIAnimator.SetBool("Turn Left", false);
|
||||
AIAnimator.SetBool("Turn Right", false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if a turn animation can be played.
|
||||
/// </summary>
|
||||
bool CanPlayTurningAnimation (Vector3 DestinationDirection)
|
||||
{
|
||||
if (!EmeraldComponent.CombatComponent.CombatState)
|
||||
{
|
||||
return EmeraldComponent.MovementComponent.DestinationAdjustedAngle >= EmeraldComponent.MovementComponent.AngleToTurn && DestinationDirection != Vector3.zero &&
|
||||
EmeraldComponent.MovementComponent.AIAgentActive && EmeraldComponent.m_NavMeshAgent.remainingDistance > EmeraldComponent.m_NavMeshAgent.stoppingDistance;
|
||||
}
|
||||
else
|
||||
{
|
||||
return !EmeraldComponent.CombatComponent.DeathDelayActive && EmeraldComponent.MovementComponent.DestinationAdjustedAngle >= EmeraldComponent.MovementComponent.AngleToTurn && DestinationDirection != Vector3.zero &&
|
||||
EmeraldComponent.MovementComponent.AIAgentActive && !IsAttacking && !IsBlocking && !IsGettingHit && !IsRecoiling && !IsStrafing && !IsDodging && !IsStunned && !IsSwitchingWeapons && !IsEquipping;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lock turning, as soon as the angle threshold is met, for 1 second. This prevents an AI from getting stuck transitioning between two turning animations.
|
||||
/// </summary>
|
||||
IEnumerator LockTurns()
|
||||
{
|
||||
yield return new WaitForSeconds(1f);
|
||||
EmeraldComponent.MovementComponent.LockTurning = false;
|
||||
}
|
||||
|
||||
void DisableTurning()
|
||||
{
|
||||
IsTurning = false;
|
||||
IsTurningLeft = false;
|
||||
IsTurningRight = false;
|
||||
AIAnimator.SetBool("Turn Right", false);
|
||||
AIAnimator.SetBool("Turn Left", false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a recoil animation when an AI is attacking and their target blocks.
|
||||
/// </summary>
|
||||
public void PlayRecoilAnimation ()
|
||||
{
|
||||
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1 && m_AnimationProfile.Type1Animations.Recoil.AnimationClip == null ||
|
||||
EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2 && m_AnimationProfile.Type2Animations.Recoil.AnimationClip == null) return;
|
||||
|
||||
if (EmeraldComponent.CombatTarget != null && EmeraldComponent.CurrentTargetInfo != null && EmeraldComponent.CurrentTargetInfo.CurrentICombat.IsBlocking())
|
||||
{
|
||||
AIAnimator.ResetTrigger("Attack");
|
||||
AIAnimator.SetTrigger("Recoil");
|
||||
OnRecoil?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the Strafe State according to the passed state bool parameter.
|
||||
/// </summary>
|
||||
public void SetStrafeState (bool State)
|
||||
{
|
||||
int Direction = AIAnimator.GetInteger("Strafe Direction");
|
||||
if (State) Direction = Random.Range(0, 2); //Only change the strafe direction if setting the strafe state to true.
|
||||
|
||||
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1)
|
||||
{
|
||||
if (Direction == 0 && m_AnimationProfile.Type1Animations.StrafeLeft.AnimationClip == null) return;
|
||||
if (Direction == 1 && m_AnimationProfile.Type1Animations.StrafeRight.AnimationClip == null) return;
|
||||
}
|
||||
else if(EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1)
|
||||
{
|
||||
if (Direction == 0 && m_AnimationProfile.Type2Animations.StrafeLeft.AnimationClip == null) return;
|
||||
if (Direction == 1 && m_AnimationProfile.Type2Animations.StrafeRight.AnimationClip == null) return;
|
||||
}
|
||||
|
||||
AIAnimator.SetBool("Strafe Active", State);
|
||||
if (State) AIAnimator.SetInteger("Strafe Direction", Direction);
|
||||
if (State) AIAnimator.SetTrigger("Strafing Triggered");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the Strafe State according to the passed state bool parameter.
|
||||
/// </summary>
|
||||
public void TriggerDodgeState()
|
||||
{
|
||||
int Direction = Random.Range(0, 3);
|
||||
|
||||
//Override the dodge direction to be equal to the strafe direction, if strafing is active during a dodge.
|
||||
if (IsStrafing || AIAnimator.GetBool("Strafe Active"))
|
||||
{
|
||||
int StrafeDirection = AIAnimator.GetInteger("Strafe Direction");
|
||||
if (StrafeDirection == 0) Direction = 0;
|
||||
if (StrafeDirection == 1) Direction = 2;
|
||||
}
|
||||
/*
|
||||
//Override the dodge direction to be backwards, if the AI is currently backing up during a dodge.
|
||||
else if (IsBackingUp)
|
||||
{
|
||||
Direction = 1;
|
||||
}
|
||||
*/
|
||||
|
||||
//Return if the chosen dodge animation is empty.
|
||||
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1)
|
||||
{
|
||||
if (Direction == 0 && m_AnimationProfile.Type1Animations.DodgeLeft.AnimationClip == null) return;
|
||||
if (Direction == 1 && m_AnimationProfile.Type1Animations.DodgeBack.AnimationClip == null) return;
|
||||
if (Direction == 2 && m_AnimationProfile.Type1Animations.DodgeRight.AnimationClip == null) return;
|
||||
}
|
||||
else if(EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2)
|
||||
{
|
||||
if (Direction == 0 && m_AnimationProfile.Type2Animations.DodgeLeft.AnimationClip == null) return;
|
||||
if (Direction == 1 && m_AnimationProfile.Type2Animations.DodgeBack.AnimationClip == null) return;
|
||||
if (Direction == 2 && m_AnimationProfile.Type2Animations.DodgeRight.AnimationClip == null) return;
|
||||
}
|
||||
|
||||
AIAnimator.SetInteger("Dodge Direction", Direction);
|
||||
AIAnimator.SetTrigger("Dodge Triggered");
|
||||
AIAnimator.SetBool("Walk Backwards", false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the Block State according to the passed state bool parameter.
|
||||
/// </summary>
|
||||
public void PlayBlockAnimation (bool State)
|
||||
{
|
||||
if (EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type1 && m_AnimationProfile.Type1Animations.BlockIdle.AnimationClip == null ||
|
||||
EmeraldComponent.CombatComponent.CurrentWeaponType == EmeraldCombat.WeaponTypes.Type2 && m_AnimationProfile.Type2Animations.BlockIdle.AnimationClip == null) return;
|
||||
AIAnimator.SetBool("Blocking", State);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets all deafult Animator values before initializing another action. This is to prevent multiple triggers being active at once, which can cause actions to be missed or skipped.
|
||||
/// </summary>
|
||||
public void ResetTriggers(float Delay)
|
||||
{
|
||||
StartCoroutine(ResetTriggersInternal(Delay));
|
||||
}
|
||||
|
||||
IEnumerator ResetTriggersInternal(float Delay)
|
||||
{
|
||||
yield return new WaitForSeconds(Delay);
|
||||
EmeraldComponent.AIAnimator.ResetTrigger("Hit");
|
||||
EmeraldComponent.AIAnimator.ResetTrigger("Attack");
|
||||
EmeraldComponent.AIAnimator.SetBool("Blocking", false);
|
||||
EmeraldComponent.AIAnimator.ResetTrigger("Dodge Triggered");
|
||||
EmeraldComponent.AIAnimator.ResetTrigger("Strafing Triggered");
|
||||
EmeraldComponent.AIAnimator.SetBool("Strafe Active", false);
|
||||
EmeraldComponent.AIAnimator.ResetTrigger("Attack Cancelled");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Animator back to its default (non-combat) state. This is called through the OnExitCombat callback.
|
||||
/// </summary>
|
||||
void ReturnToDefaultState ()
|
||||
{
|
||||
EmeraldComponent.AIAnimator.SetBool("Combat State Active", false);
|
||||
EmeraldComponent.AnimationComponent.WarningAnimationTriggered = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays an emote animation according to the Animation Clip parameter.
|
||||
/// </summary>
|
||||
public void PlayEmoteAnimation(int EmoteAnimationID)
|
||||
{
|
||||
//Look through each animation in the EmoteAnimationList for the appropriate ID.
|
||||
//Once found, play the animaition of the same index as the found ID.
|
||||
for (int i = 0; i < m_AnimationProfile.EmoteAnimationList.Count; i++)
|
||||
{
|
||||
if (m_AnimationProfile.EmoteAnimationList[i].AnimationID == EmoteAnimationID)
|
||||
{
|
||||
AIAnimator.SetInteger("Emote Index", EmoteAnimationID);
|
||||
AIAnimator.SetTrigger("Emote Trigger");
|
||||
IsMoving = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loops an emote animation according to the Animation Clip parameter until it is called to stop. Note: If you are using this during combat,
|
||||
/// it is important that you handle canceling it or the AI will not be able to return to combat.
|
||||
/// </summary>
|
||||
public void LoopEmoteAnimation(int EmoteAnimationID)
|
||||
{
|
||||
//Look through each animation in the EmoteAnimationList for the appropriate ID.
|
||||
//Once found, play the animaition of the same index as the found ID.
|
||||
for (int i = 0; i < m_AnimationProfile.EmoteAnimationList.Count; i++)
|
||||
{
|
||||
if (m_AnimationProfile.EmoteAnimationList[i].AnimationID == EmoteAnimationID)
|
||||
{
|
||||
AIAnimator.SetInteger("Emote Index", EmoteAnimationID);
|
||||
AIAnimator.SetBool("Emote Loop", true);
|
||||
IsMoving = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loops an emote animation according to the Animation Clip parameter until it is called to stop.
|
||||
/// </summary>
|
||||
public void StopLoopEmoteAnimation(int EmoteAnimationID)
|
||||
{
|
||||
//Look through each animation in the EmoteAnimationList for the appropriate ID.
|
||||
//Once found, play the animaition of the same index as the found ID.
|
||||
for (int i = 0; i < m_AnimationProfile.EmoteAnimationList.Count; i++)
|
||||
{
|
||||
if (m_AnimationProfile.EmoteAnimationList[i].AnimationID == EmoteAnimationID)
|
||||
{
|
||||
AIAnimator.SetInteger("Emote Index", EmoteAnimationID);
|
||||
AIAnimator.SetBool("Emote Loop", false);
|
||||
IsMoving = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delays the call to disable the Emerald AI components until after the death animation has finished playing.
|
||||
/// </summary>
|
||||
IEnumerator DisableAnimator(float AnimationLength)
|
||||
{
|
||||
yield return new WaitForSeconds(AnimationLength);
|
||||
EmeraldComponent.AIAnimator.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 871be6a68bd875f49b5587968e1ac56c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,368 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using EmeraldAI.Utility;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
/// <summary>
|
||||
/// This script handles all of Emerald AI's behaviors and states. Most functions can be overridden to create custom behaviors or functionality.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
[HelpURL("https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-required/behaviors-component")]
|
||||
public class EmeraldBehaviors : MonoBehaviour
|
||||
{
|
||||
#region Behavior Variables
|
||||
protected EmeraldSystem EmeraldComponent;
|
||||
public enum BehaviorTypes { Passive = 0, Coward = 1, Aggressive = 2};
|
||||
public BehaviorTypes CurrentBehaviorType = BehaviorTypes.Aggressive;
|
||||
|
||||
public Transform TargetToFollow;
|
||||
public int CautiousSeconds = 0;
|
||||
public bool InfititeChase;
|
||||
public int ChaseSeconds = 5;
|
||||
public int FleeSeconds = 5;
|
||||
public bool RequireObstruction;
|
||||
public int PercentToFlee = 20;
|
||||
public float UpdateFleePositionSeconds = 1.5f;
|
||||
public int MaxDistanceFromStartingArea = 30;
|
||||
public float FollowingStoppingDistance = 2f;
|
||||
public bool IsAiming;
|
||||
|
||||
public delegate void StartFleeHandler();
|
||||
public event StartFleeHandler OnFlee;
|
||||
|
||||
public YesOrNo FleeOnLowHealth = YesOrNo.No;
|
||||
public YesOrNo StayNearStartingArea = YesOrNo.No;
|
||||
|
||||
/// <summary>
|
||||
/// A timer used for tracking how long an AI is in the cautious state.
|
||||
/// </summary>
|
||||
protected float CautiousTimer;
|
||||
/// <summary>
|
||||
/// A timer used for controlling how often flee positions are updated.
|
||||
/// </summary>
|
||||
protected float UpdateFleePositionTimer;
|
||||
/// <summary>
|
||||
/// A timer used for tracking how long a target is outside of an AI's detection radius.
|
||||
/// </summary>
|
||||
protected float GiveUpTimer;
|
||||
/// <summary>
|
||||
/// A timer used for tracking the cooldown length of an AI's attacks.
|
||||
/// </summary>
|
||||
protected float AttackTimer;
|
||||
/// <summary>
|
||||
/// A string used for tracking an AI's current behavior state. This is a string so it can be customized as needed, given that a behvaior has multiple stages or states.
|
||||
/// </summary>
|
||||
public string BehaviorState = "Non Combat";
|
||||
#endregion
|
||||
|
||||
#region Editor Variables
|
||||
[HideInInspector] public bool HideSettingsFoldout;
|
||||
[HideInInspector] public bool BehaviorSettingsFoldout;
|
||||
[HideInInspector] public bool CustomSettingsFoldout;
|
||||
#endregion
|
||||
|
||||
public virtual void Start()
|
||||
{
|
||||
InitailizeBehaviors();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the Behavior Component.
|
||||
/// </summary>
|
||||
public virtual void InitailizeBehaviors ()
|
||||
{
|
||||
EmeraldComponent = GetComponent<EmeraldSystem>();
|
||||
|
||||
ResetState();
|
||||
|
||||
if (TargetToFollow != null)
|
||||
{
|
||||
if (!TargetToFollow.gameObject.activeSelf)
|
||||
{
|
||||
Debug.LogError("The '" + gameObject.name + "' AI's Follower Target '" + TargetToFollow.name + "' is disabled so it has been removed as the AI's follower. You can enable said gameobject or use the SetFollowerTarget(Transform) API to assign a follower through code if needed.");
|
||||
TargetToFollow = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
StartCoroutine(SetFollowerTargetInternal()); //Use a slight delay to ensure all other components have been initialized before assigning the AI's Target to Follow.
|
||||
}
|
||||
}
|
||||
|
||||
EmeraldComponent.DetectionComponent.OnEnemyTargetDetected += OnDetectTarget;
|
||||
EmeraldComponent.DetectionComponent.OnNullTarget += ResetState;
|
||||
EmeraldComponent.CombatComponent.OnKilledTarget += OnKilledTarget;
|
||||
EmeraldComponent.HealthComponent.OnTakeDamage += OnTakeDamage;
|
||||
|
||||
if (CurrentBehaviorType == BehaviorTypes.Passive)
|
||||
{
|
||||
if (gameObject.tag != "Untagged")
|
||||
{
|
||||
gameObject.tag = "Untagged";
|
||||
}
|
||||
if (gameObject.layer != 0)
|
||||
{
|
||||
gameObject.layer = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use a slight delay to ensure all other components have been initialized before assigning the AI's Target to Follow.
|
||||
/// </summary>
|
||||
IEnumerator SetFollowerTargetInternal ()
|
||||
{
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
EmeraldComponent.DetectionComponent.SetTargetToFollow(TargetToFollow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Continiously updates the BehaviorObject. This acts like an Update function that can run within this behavior using the information from the passed EmeraldComponent and its
|
||||
/// </summary>
|
||||
public virtual void BehaviorUpdate()
|
||||
{
|
||||
if (EmeraldComponent.AnimationComponent.IsDead)
|
||||
return;
|
||||
|
||||
switch (BehaviorState)
|
||||
{
|
||||
case "Non Combat":
|
||||
WanderBehavior();
|
||||
break;
|
||||
case "Cautious":
|
||||
CautiousBehavior();
|
||||
break;
|
||||
case "Aggressive":
|
||||
AggressiveBehavior();
|
||||
break;
|
||||
case "Flee":
|
||||
CowardBehavior();
|
||||
break;
|
||||
}
|
||||
|
||||
//Update the DetectTargetTracker virtual method (which tracks when targets are within the detection radius and clears them when needed)
|
||||
//This can be overridden if these mechanics need to be customized.
|
||||
DetectTargetTracker();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play a warning animation and look at the current target. If the CautiousSeconds are met, change the state to be aggressive.
|
||||
/// </summary>
|
||||
public virtual void CautiousBehavior()
|
||||
{
|
||||
if (EmeraldComponent.CombatTarget && EmeraldComponent.CombatComponent.CombatState && EmeraldComponent.MovementComponent.AIAgentActive && CurrentBehaviorType != BehaviorTypes.Passive)
|
||||
{
|
||||
//Bypass the cautious timer if the AI has a follower target.
|
||||
if (CurrentBehaviorType == BehaviorTypes.Aggressive && EmeraldComponent.TargetToFollow) BehaviorState = "Aggressive";
|
||||
|
||||
CautiousTimer += Time.deltaTime;
|
||||
|
||||
if (CautiousTimer >= CautiousSeconds)
|
||||
{
|
||||
if (CurrentBehaviorType == BehaviorTypes.Aggressive)
|
||||
{
|
||||
BehaviorState = "Aggressive";
|
||||
}
|
||||
else if (CurrentBehaviorType == BehaviorTypes.Coward)
|
||||
{
|
||||
OnFlee?.Invoke(); //Invoke the OnFlee delegate.
|
||||
BehaviorState = "Flee";
|
||||
}
|
||||
|
||||
CautiousTimer = 0;
|
||||
}
|
||||
|
||||
if (CautiousTimer > 2)
|
||||
{
|
||||
EmeraldComponent.AnimationComponent.PlayWarningAnimation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actively chase and attack the current target.
|
||||
/// </summary>
|
||||
public virtual void AggressiveBehavior()
|
||||
{
|
||||
if (EmeraldComponent.CombatComponent.CombatState && EmeraldComponent.MovementComponent.AIAgentActive && EmeraldComponent.CombatTarget)
|
||||
{
|
||||
//Only attempt to chase and attack the current target if a path exists to them.
|
||||
bool CanReachTarget = EmeraldComponent.MovementComponent.CanReachTarget;
|
||||
|
||||
//Use Emerald AI's built-in Combat Movement (which simply sets the AI's destination equal to the current target's position). A custom function can be used for added functionality, if desired.
|
||||
//This will also backup the AI if they get too close to their target.
|
||||
if (!EmeraldComponent.MovementComponent.DefaultMovementPaused)
|
||||
{
|
||||
EmeraldComponent.MovementComponent.CombatMovement();
|
||||
}
|
||||
else if (!EmeraldComponent.MovementComponent.DefaultMovementPaused && CanReachTarget)
|
||||
{
|
||||
EmeraldComponent.m_NavMeshAgent.SetDestination(transform.position + transform.forward * 2);
|
||||
}
|
||||
|
||||
EmeraldComponent.CombatComponent.UpdateActions(); //Updates an AI's list of Combat Actions, but only while in the Aggressive state (Combat State).
|
||||
|
||||
Attack(); //Continuously check to see if the conditions are right to trigger an attack, given the AI is in the Aggressive State.
|
||||
|
||||
//If FleeOnLowHealth is enabled, and the AI's health reaches the threshold, set the AI's BehaviorState to Flee.
|
||||
if (FleeOnLowHealth == YesOrNo.Yes && ((float)EmeraldComponent.HealthComponent.CurrentHealth / (float)EmeraldComponent.HealthComponent.StartingHealth) < (PercentToFlee * 0.01f))
|
||||
{
|
||||
EmeraldComponent.AnimationComponent.ResetTriggers(0);
|
||||
OnFlee?.Invoke(); //Invoke the OnFlee delegate.
|
||||
BehaviorState = "Flee";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use Emerald AI's built-in Flee Movement (which simply generates a destination opposite to the current target's position). This is updated based
|
||||
/// on the UpdateFleePositionSeconds or if the AI gets close the generated waypoint. A custom function can be used for added functionality, if desired.
|
||||
/// </summary>
|
||||
public virtual void CowardBehavior()
|
||||
{
|
||||
if (!EmeraldComponent.MovementComponent.DefaultMovementPaused)
|
||||
{
|
||||
UpdateFleePositionTimer += Time.deltaTime;
|
||||
if (UpdateFleePositionTimer > UpdateFleePositionSeconds || EmeraldComponent.m_NavMeshAgent.remainingDistance <= EmeraldComponent.MovementComponent.StoppingDistance)
|
||||
{
|
||||
EmeraldComponent.MovementComponent.FleeMovement();
|
||||
UpdateFleePositionTimer = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use Emerald AI's built-in Wandering when not in combat (based off of the user set WanderType within the Emerald AI Movement Editor)
|
||||
/// </summary>
|
||||
public virtual void WanderBehavior()
|
||||
{
|
||||
if (EmeraldComponent.MovementComponent.AIAgentActive && !EmeraldComponent.CombatComponent.CombatState && !EmeraldComponent.CombatComponent.DeathDelayActive)
|
||||
{
|
||||
if (!EmeraldComponent.TargetToFollow)
|
||||
{
|
||||
EmeraldComponent.MovementComponent.Wander();
|
||||
}
|
||||
else
|
||||
{
|
||||
EmeraldComponent.MovementComponent.FollowCompanionTarget(FollowingStoppingDistance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for tracking when a target is outside of an AI's detection radius.
|
||||
/// </summary>
|
||||
public virtual void DetectTargetTracker()
|
||||
{
|
||||
if (!EmeraldComponent.CombatComponent.CombatState || EmeraldComponent.CombatComponent.DeathDelayActive || InfititeChase || EmeraldComponent.TargetToFollow)
|
||||
return;
|
||||
|
||||
//Track how long a target is outside of the AI's detection radius. If the time is exceeded, give up on the target and set the AI to its defaul state.
|
||||
if (EmeraldComponent.CombatComponent.DistanceFromTarget > EmeraldComponent.DetectionComponent.DetectionRadius && !RequireObstruction || RequireObstruction && EmeraldComponent.DetectionComponent.TargetObstructed)
|
||||
{
|
||||
GiveUpTimer += Time.deltaTime;
|
||||
|
||||
if (GiveUpTimer >= ChaseSeconds && CurrentBehaviorType == BehaviorTypes.Aggressive || GiveUpTimer >= FleeSeconds && CurrentBehaviorType == BehaviorTypes.Coward || BehaviorState == "Cautious")
|
||||
{
|
||||
CancelCombat(); //Stops the AI from fighting and chasing, or fleeing from, its current target.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GiveUpTimer = 0;
|
||||
}
|
||||
|
||||
//Used for tracking when an AI's distance from its starting position is exceeded.
|
||||
if (CurrentBehaviorType == BehaviorTypes.Aggressive && StayNearStartingArea == YesOrNo.Yes && Vector3.Distance(EmeraldComponent.MovementComponent.StartingDestination, transform.position) > MaxDistanceFromStartingArea)
|
||||
{
|
||||
EmeraldComponent.MovementComponent.EnableReturnToStart(); //Returns the AI to its starting area
|
||||
CancelCombat(); //Stops the AI from fighting and chasing its current target
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Continuously check to see if the conditions are right to trigger an attack, given the AI is in the Aggressive State. This is a priority state.
|
||||
/// </summary>
|
||||
public virtual void Attack()
|
||||
{
|
||||
var EnterConditions = EmeraldComponent.AnimationComponent.IsIdling || EmeraldComponent.AnimationComponent.IsMoving;
|
||||
var CooldownConditions = EmeraldComponent.AnimationComponent.IsIdling || EmeraldComponent.AnimationComponent.IsMoving || EmeraldComponent.AnimationComponent.IsBackingUp ||
|
||||
EmeraldComponent.AnimationComponent.IsTurningLeft || EmeraldComponent.AnimationComponent.IsTurningRight || EmeraldComponent.AnimationComponent.IsGettingHit;
|
||||
|
||||
if (CooldownConditions) AttackTimer += Time.deltaTime;
|
||||
|
||||
if (EmeraldCombatManager.AllowedToAttack(EmeraldComponent) && EnterConditions && !IsAiming && AttackTimer >= EmeraldComponent.CombatComponent.CurrentAttackCooldown)
|
||||
{
|
||||
EmeraldComponent.AnimationComponent.IsMoving = false;
|
||||
EmeraldComponent.CombatComponent.AdjustCooldowns();
|
||||
EmeraldComponent.CombatComponent.AttackPosition = EmeraldComponent.CombatTarget.position - transform.position;
|
||||
EmeraldComponent.CombatComponent.AttackPosition.y = 0;
|
||||
EmeraldComponent.AnimationComponent.PlayAttackAnimation();
|
||||
AttackTimer = 0;
|
||||
}
|
||||
|
||||
//Cancel the attack if it's triggered and the target is out of range
|
||||
if (AttackTimer >= EmeraldComponent.CombatComponent.CurrentAttackCooldown)
|
||||
{
|
||||
if (EmeraldComponent.m_NavMeshAgent.remainingDistance > EmeraldComponent.m_NavMeshAgent.stoppingDistance && EmeraldComponent.AIAnimator.GetBool("Attack"))
|
||||
{
|
||||
EmeraldComponent.AIAnimator.ResetTrigger("Attack");
|
||||
AttackTimer = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the AI from fighting and chasing, or fleeing from, its current target.
|
||||
/// </summary>
|
||||
public virtual void CancelCombat()
|
||||
{
|
||||
EmeraldComponent.CombatComponent.ClearTarget();
|
||||
EmeraldComponent.CombatComponent.DeathDelayActive = true;
|
||||
EmeraldComponent.m_NavMeshAgent.SetDestination(transform.position + EmeraldComponent.transform.forward * (EmeraldComponent.MovementComponent.StoppingDistance * 1.5f));
|
||||
EmeraldComponent.AnimationComponent.ResetTriggers(0);
|
||||
ResetState();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the settings back to their default values.
|
||||
/// </summary>
|
||||
public virtual void ResetState ()
|
||||
{
|
||||
BehaviorState = "Non Combat";
|
||||
CautiousTimer = 0;
|
||||
UpdateFleePositionTimer = 0;
|
||||
GiveUpTimer = 0;
|
||||
AttackTimer = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When killing a target, return the BehaviorState back to Non Combat. If another target is found, it will be updated.
|
||||
/// </summary>
|
||||
public virtual void OnKilledTarget()
|
||||
{
|
||||
BehaviorState = "Non Combat";
|
||||
GiveUpTimer = 0;
|
||||
EmeraldComponent.AnimationComponent.WarningAnimationTriggered = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When detecting a target, update the AI's current destination to be equal to their current position until the combat movement code can take over.
|
||||
/// </summary>
|
||||
public virtual void OnDetectTarget()
|
||||
{
|
||||
BehaviorState = "Cautious";
|
||||
if (isActiveAndEnabled) EmeraldComponent.m_NavMeshAgent.SetDestination(transform.position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If an AI takes damage before its Cautious State has finished, change the BehaviorState so it can handle its attack according to its behavior.
|
||||
/// </summary>
|
||||
public virtual void OnTakeDamage()
|
||||
{
|
||||
if (BehaviorState == "Cautious" && CurrentBehaviorType == BehaviorTypes.Aggressive) BehaviorState = "Aggressive";
|
||||
else if (BehaviorState == "Cautious" && CurrentBehaviorType == BehaviorTypes.Coward) BehaviorState = "Flee";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff18b11debafb2944b9a076aa0d8052a
|
||||
timeCreated: 1548786587
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,579 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using EmeraldAI.Utility;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[HelpURL("https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-required/combat-component")]
|
||||
public class EmeraldCombat : MonoBehaviour, ICombat
|
||||
{
|
||||
#region Combat Variables
|
||||
public List<EmeraldWeaponCollision> WeaponColliders = new List<EmeraldWeaponCollision>();
|
||||
public EmeraldWeaponCollision CurrentWeaponCollision;
|
||||
|
||||
public int MinResumeWander = 2;
|
||||
public int MaxResumeWander = 4;
|
||||
|
||||
public float CurrentAttackCooldown;
|
||||
public float Type1AttackCooldown = 0.35f;
|
||||
public float Type2AttackCooldown = 0.35f;
|
||||
|
||||
public float SwitchWeaponTimer = 0;
|
||||
public bool SwitchWeaponTypeTriggered = false;
|
||||
public bool CombatActionActive;
|
||||
|
||||
[SerializeField] public List<ActionsClass> CombatActions = new List<ActionsClass>();
|
||||
[SerializeField] public List<ActionsClass> Type1CombatActions = new List<ActionsClass>();
|
||||
[SerializeField] public List<ActionsClass> Type2CombatActions = new List<ActionsClass>();
|
||||
|
||||
public Vector3 AttackPosition;
|
||||
|
||||
//These can be set through Action Objects so actions like block and dodge can mitigate damage.
|
||||
public int MitigationAmount = 50;
|
||||
public float MaxMitigationAngle = 75;
|
||||
|
||||
public delegate void KilledTargetHandler();
|
||||
public event KilledTargetHandler OnKilledTarget;
|
||||
public delegate void DoDamageHandler();
|
||||
public event DoDamageHandler OnDoDamage;
|
||||
public delegate void DoCritDamageHandler();
|
||||
public event DoCritDamageHandler OnDoCritDamage;
|
||||
public delegate void StartCombatHandler();
|
||||
public event StartCombatHandler OnStartCombat;
|
||||
public delegate void EndCombatHandler();
|
||||
public event EndCombatHandler OnEndCombat;
|
||||
|
||||
//This differs from OnEndCombat as it's called when AI actually exits their combat state.
|
||||
//There are various components that subscribe to this for transitioning back to their non-combat states.
|
||||
public delegate void ExitCombatHandler();
|
||||
public event ExitCombatHandler OnExitCombat;
|
||||
|
||||
public bool CombatState;
|
||||
|
||||
public enum WeaponTypes { Type1 = 0, Type2 = 1 };
|
||||
public WeaponTypes StartingWeaponType = WeaponTypes.Type1;
|
||||
public WeaponTypes CurrentWeaponType = WeaponTypes.Type1;
|
||||
|
||||
public PickTargetTypes Type1PickTargetType = PickTargetTypes.Closest;
|
||||
public PickTargetTypes Type2PickTargetType = PickTargetTypes.Closest;
|
||||
|
||||
public enum WeaponTypeAmounts { One, Two };
|
||||
public WeaponTypeAmounts WeaponTypeAmount = WeaponTypeAmounts.One;
|
||||
|
||||
[SerializeField]
|
||||
public AttackClass Type1Attacks;
|
||||
[SerializeField]
|
||||
public AttackClass Type2Attacks;
|
||||
|
||||
public int SwitchWeaponTypesCooldown = 10;
|
||||
public int SwitchWeaponTypesDistance = 8;
|
||||
|
||||
public Transform CurrentAttackTransform;
|
||||
public List<Transform> WeaponType1AttackTransforms = new List<Transform>();
|
||||
public List<Transform> WeaponType2AttackTransforms = new List<Transform>();
|
||||
|
||||
public enum SwitchWeaponTypes { Distance, Timed, None};
|
||||
public SwitchWeaponTypes SwitchWeaponType = SwitchWeaponTypes.Timed;
|
||||
public int SwitchWeaponTimeMin = 10;
|
||||
public int SwitchWeaponTimeMax = 20;
|
||||
public float SwitchWeaponTime = 0;
|
||||
|
||||
public float DistanceFromTarget;
|
||||
public float TargetAngle;
|
||||
public int ReceivedRagdollForceAmount;
|
||||
public Transform RagdollTransform;
|
||||
public Vector3 TargetDestination;
|
||||
public bool FirstTimeInCombat = true;
|
||||
public float DeathDelay;
|
||||
public bool DeathDelayActive;
|
||||
public float DeathDelayTimer;
|
||||
public int CurrentAnimationIndex = 0;
|
||||
public bool TargetDetectionActive;
|
||||
public float TooCloseDistance = 1;
|
||||
public float AttackDistance = 2.5f;
|
||||
public EmeraldAbilityObject CurrentEmeraldAIAbility;
|
||||
EmeraldSystem EmeraldComponent;
|
||||
public AttackClass.AttackData CurrentAttackData;
|
||||
public Transform LastAttacker;
|
||||
#endregion
|
||||
|
||||
#region Private Variables
|
||||
bool m_WeaponTypeSwitchDelay;
|
||||
Coroutine SwitchWeaponCoroutine;
|
||||
#endregion
|
||||
|
||||
#region Editor Variable
|
||||
public bool HideSettingsFoldout;
|
||||
public bool DamageSettingsFoldout;
|
||||
public bool CombatActionSettingsFoldout;
|
||||
public bool SwitchWeaponSettingsFoldout;
|
||||
public bool WeaponType1SettingsFoldout;
|
||||
public bool WeaponType2SettingsFoldout;
|
||||
#endregion
|
||||
|
||||
void Start()
|
||||
{
|
||||
InitializeCombat();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the Combat Component.
|
||||
/// </summary>
|
||||
void InitializeCombat ()
|
||||
{
|
||||
EmeraldComponent = GetComponent<EmeraldSystem>();
|
||||
EmeraldComponent.HealthComponent.OnDeath += CancelAllCombatActions; //Subscribe to the OnDeath event for CancelCombatActions
|
||||
EmeraldComponent.DetectionComponent.OnEnemyTargetDetected += EnterCombat; //Subscribe to the OnDeath event for CancelCombatActions
|
||||
EmeraldComponent.DetectionComponent.OnNullTarget += NullCombatTarget; //Subscribe to the OnNullTarget event for NullCombatTarget
|
||||
OnKilledTarget += CancelAllCombatActions; //Subscribe to the OnKilledTarget event for CancelCombatActions
|
||||
TargetDetectionActive = true;
|
||||
FirstTimeInCombat = true;
|
||||
SwitchWeaponTime = Random.Range((float)SwitchWeaponTimeMin, SwitchWeaponTimeMax + 1);
|
||||
DeathDelay = Random.Range(MinResumeWander, MaxResumeWander + 1);
|
||||
Invoke(nameof(InitializeAttacks), 0.1f);
|
||||
}
|
||||
|
||||
void InitializeAttacks()
|
||||
{
|
||||
//Generate an attack based on the current weapon type
|
||||
if (CurrentWeaponType == WeaponTypes.Type1)
|
||||
{
|
||||
EmeraldCombatManager.GenerateAttack(EmeraldComponent, Type1Attacks);
|
||||
EmeraldComponent.DetectionComponent.PickTargetType = Type1PickTargetType;
|
||||
CurrentAttackCooldown = Type1AttackCooldown;
|
||||
|
||||
}
|
||||
else if (CurrentWeaponType == WeaponTypes.Type2)
|
||||
{
|
||||
EmeraldCombatManager.GenerateAttack(EmeraldComponent, Type2Attacks);
|
||||
EmeraldComponent.DetectionComponent.PickTargetType = Type2PickTargetType;
|
||||
CurrentAttackCooldown = Type2AttackCooldown;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A custom update function for the EmeraldCombat script called through the EmeraldAISystem script.
|
||||
/// </summary>
|
||||
public void CombatUpdate()
|
||||
{
|
||||
if (CombatState)
|
||||
{
|
||||
DistanceFromTarget = EmeraldCombatManager.GetDistanceFromTarget(EmeraldComponent); //Update current distance from the target.
|
||||
TargetAngle = EmeraldCombatManager.TargetAngle(EmeraldComponent); //Update current angle from the target.
|
||||
}
|
||||
else if (!CombatState)
|
||||
{
|
||||
DistanceFromTarget = EmeraldCombatManager.GetDistanceFromLookTarget(EmeraldComponent); //Update current distance from the target.
|
||||
TargetAngle = EmeraldCombatManager.TransformAngle(EmeraldComponent, EmeraldComponent.LookAtTarget); //Update current angle from the target.
|
||||
}
|
||||
|
||||
CheckForTargetDeath(); //Monitor the current target's health for when it dies.
|
||||
UpdateWeaponTypeState(); //Check for when to switch weapons.
|
||||
UpdateDeathDelay(); //Controls when an AI will go back to its non-combat state after killing a target.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Controls when the death delay feature has lapsed.
|
||||
/// </summary>
|
||||
void UpdateDeathDelay ()
|
||||
{
|
||||
if (DeathDelayActive)
|
||||
{
|
||||
DeathDelayTimer += Time.deltaTime;
|
||||
|
||||
if (DeathDelayTimer > DeathDelay)
|
||||
{
|
||||
ExitCombat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the AI's current target is within the angle limit to be attacked.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool TargetWithinAngleLimit ()
|
||||
{
|
||||
return TargetAngle <= EmeraldComponent.MovementComponent.CombatAngleToTurn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoke the OnEnterCombat event that happens when the AI starts fighting its first target for the current battle.
|
||||
/// </summary>
|
||||
public void EnterCombat ()
|
||||
{
|
||||
if (FirstTimeInCombat) OnStartCombat?.Invoke();
|
||||
FirstTimeInCombat = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets various settings when exiting combat.
|
||||
/// </summary>
|
||||
public void ExitCombat ()
|
||||
{
|
||||
CombatState = false;
|
||||
SwitchWeaponTimer = 0;
|
||||
ClearTarget();
|
||||
FirstTimeInCombat = true;
|
||||
DeathDelayTimer = 0;
|
||||
DeathDelayActive = false;
|
||||
OnExitCombat?.Invoke(); //This is used in the Movement, Detection, and Animation components to return them to their default states.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an AI's list of actions while in combat.
|
||||
/// </summary>
|
||||
public void UpdateActions()
|
||||
{
|
||||
if (CurrentWeaponType == WeaponTypes.Type1) CombatActions = Type1CombatActions;
|
||||
else if (CurrentWeaponType == WeaponTypes.Type2) CombatActions = Type2CombatActions;
|
||||
|
||||
if (EmeraldComponent.CombatTarget != null && !EmeraldComponent.AnimationComponent.IsDead && !EmeraldComponent.AnimationComponent.IsStunned && !EmeraldComponent.AIAnimator.GetBool("Stunned Active"))
|
||||
{
|
||||
for (int i = 0; i < CombatActions.Count; i++)
|
||||
{
|
||||
if (CombatActions[i].Enabled)
|
||||
{
|
||||
CombatActions[i].emeraldAction.UpdateAction(EmeraldComponent, CombatActions[i]);
|
||||
var Conditions = (((int)CombatActions[i].emeraldAction.CooldownConditions) & ((int)EmeraldComponent.AnimationComponent.CurrentAnimationState)) != 0;
|
||||
|
||||
//Only update the cooldown timer if the conditions are met for this action.
|
||||
if (Conditions && !EmeraldComponent.AIAnimator.GetBool("Attack") && !CombatActions[i].IsActive)
|
||||
CombatActions[i].CooldownLengthTimer += Time.deltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels all combat actions that are currently active.
|
||||
/// </summary>
|
||||
public void CancelAllCombatActions ()
|
||||
{
|
||||
if (CombatActions.Count == 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < CombatActions.Count; i++)
|
||||
{
|
||||
if (CombatActions[i].IsActive)
|
||||
{
|
||||
CombatActions[i].emeraldAction.CancelAction(EmeraldComponent, CombatActions[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts the all action cooldowns so two actions aren't triggered simultaneously (due to Animation States needing time to transition). This is called after some actions have been successfully triggered.
|
||||
/// </summary>
|
||||
public void AdjustCooldowns()
|
||||
{
|
||||
for (int i = 0; i < CombatActions.Count; i++)
|
||||
{
|
||||
if (CombatActions[i].CooldownLengthTimer >= CombatActions[i].emeraldAction.CooldownLength - 0.25f)
|
||||
CombatActions[i].CooldownLengthTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function invokes the ability attached to the AI's Ability Object slot, of its Attack List of the Combat Component, when the set attack animation is playing. This should be set through an Animation Event.
|
||||
/// </summary>
|
||||
public void CreateAbility(AnimationEvent AttackEventParameters)
|
||||
{
|
||||
//Allow the objectReferenceParameter to override the current ability
|
||||
if (AttackEventParameters.objectReferenceParameter != null)
|
||||
{
|
||||
CurrentEmeraldAIAbility = (EmeraldAbilityObject)AttackEventParameters.objectReferenceParameter;
|
||||
}
|
||||
|
||||
EmeraldCombatManager.UpdateAttackTransforms(EmeraldComponent, AttackEventParameters.stringParameter); //Updates the AI's current attack and weapon transforms based on the sent AttackTransformName from an EmeraldAttackEvent Animation Event.
|
||||
if (CurrentEmeraldAIAbility != null) CurrentEmeraldAIAbility.InvokeAbility(gameObject, CurrentAttackTransform); //Invoke the ability, if the ability slot is not emepty.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function invokes a charge effect from the attached Ability Object slot, of its Attack List of the Combat Component, when the set attack animation is playing. This should be set through an Animation Event.
|
||||
/// </summary>
|
||||
public void ChargeEffect(AnimationEvent AttackEventParameters)
|
||||
{
|
||||
Transform AttackTransform = EmeraldCombatManager.GetAttackTransform(EmeraldComponent, AttackEventParameters.stringParameter); //Gets the weapon transform based on the sent AttackTransformName from an EmeraldChargeAttack Animation Event.
|
||||
if (CurrentEmeraldAIAbility != null && AttackTransform != null) CurrentEmeraldAIAbility.ChargeAbility(gameObject, AttackTransform); //Invoke the ability's charge, if the ability slot is not emepty.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called through the OnNullTarget callback when a target becomes null. Clear the Current Target and look for another.
|
||||
/// If no new target is found, wait for death delay to lapse before returning to the non-combat state.
|
||||
/// </summary>
|
||||
void NullCombatTarget()
|
||||
{
|
||||
if (EmeraldComponent.CombatTarget == null && CombatState && !EmeraldComponent.MovementComponent.ReturningToStartInProgress && !DeathDelayActive)
|
||||
{
|
||||
DeathDelay = Random.Range(MinResumeWander, MaxResumeWander + 1);
|
||||
DeathDelayActive = true;
|
||||
EmeraldComponent.m_NavMeshAgent.ResetPath();
|
||||
ClearTarget();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Watch the CurrentIDamageable for when the health reaches 0.
|
||||
/// </summary>
|
||||
void CheckForTargetDeath()
|
||||
{
|
||||
if (EmeraldComponent.CurrentTargetInfo.CurrentIDamageable != null)
|
||||
{
|
||||
if (EmeraldComponent.CurrentTargetInfo.CurrentIDamageable.Health <= 0 && !DeathDelayActive)
|
||||
{
|
||||
OnKilledTarget?.Invoke();
|
||||
DeathDelay = Random.Range(MinResumeWander, MaxResumeWander + 1);
|
||||
DeathDelayActive = true;
|
||||
EmeraldComponent.m_NavMeshAgent.ResetPath();
|
||||
Invoke(nameof(ClearTarget), 0.75f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the AI's current target.
|
||||
/// </summary>
|
||||
public void ClearTarget()
|
||||
{
|
||||
if (EmeraldComponent.CombatTarget != null)
|
||||
{
|
||||
//Remove the CurrentTarget from the AI's LineOfSightTargets list.
|
||||
if (EmeraldComponent.DetectionComponent.LineOfSightTargets.Contains(EmeraldComponent.CombatTarget.GetComponent<Collider>()))
|
||||
EmeraldComponent.DetectionComponent.LineOfSightTargets.Remove(EmeraldComponent.CombatTarget.GetComponent<Collider>());
|
||||
}
|
||||
else
|
||||
{
|
||||
//The CurrentTarget is null, remove it, and any other null targes, from the list.
|
||||
for (int i = 0; i < EmeraldComponent.DetectionComponent.LineOfSightTargets.Count; i++)
|
||||
{
|
||||
if (EmeraldComponent.DetectionComponent.LineOfSightTargets[i] == null)
|
||||
{
|
||||
EmeraldComponent.DetectionComponent.LineOfSightTargets.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Clear the current target references
|
||||
EmeraldComponent.CombatTarget = null;
|
||||
EmeraldComponent.CurrentTargetInfo.TargetSource = null;
|
||||
EmeraldComponent.CurrentTargetInfo.CurrentIDamageable = null;
|
||||
EmeraldComponent.CurrentTargetInfo.CurrentICombat = null;
|
||||
|
||||
//Invoke the OnEndCombat callback if there's no remaining detectable enemy targets nearby.
|
||||
if (EmeraldComponent.DetectionComponent.LineOfSightTargets.Count == 0) OnEndCombat?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked below with cooldown delay and controls the AI's weapon type switching from happening too often
|
||||
/// </summary>
|
||||
void WeaponSwitchCooldown()
|
||||
{
|
||||
m_WeaponTypeSwitchDelay = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the AI's weapon type to switch between weapon types.
|
||||
/// </summary>
|
||||
void UpdateWeaponTypeState()
|
||||
{
|
||||
if (!CombatState || DeathDelayActive)
|
||||
return;
|
||||
|
||||
if (WeaponTypeAmount == WeaponTypeAmounts.Two)
|
||||
{
|
||||
if (SwitchWeaponTypeTriggered && !m_WeaponTypeSwitchDelay)
|
||||
{
|
||||
SwitchWeaponTypeTriggered = false;
|
||||
m_WeaponTypeSwitchDelay = true;
|
||||
Invoke(nameof(WeaponSwitchCooldown), SwitchWeaponTypesCooldown);
|
||||
}
|
||||
|
||||
//Switches the current weapon type based on distance.
|
||||
if (SwitchWeaponType == SwitchWeaponTypes.Distance && EmeraldComponent.CombatTarget != null && !m_WeaponTypeSwitchDelay &&
|
||||
!EmeraldComponent.AnimationComponent.IsSwitchingWeapons && !EmeraldComponent.AnimationComponent.IsAttacking && !EmeraldComponent.AnimationComponent.IsMoving && !EmeraldComponent.AnimationComponent.IsBackingUp && !EmeraldComponent.AnimationComponent.IsTurning && !EmeraldComponent.AIAnimator.GetBool("Strafe Active"))
|
||||
{
|
||||
if (DistanceFromTarget > SwitchWeaponTypesDistance && CurrentWeaponType != StartingWeaponType && !SwitchWeaponTypeTriggered)
|
||||
{
|
||||
SwapWeaponType();
|
||||
SwitchWeaponTypeTriggered = true;
|
||||
}
|
||||
if (DistanceFromTarget < SwitchWeaponTypesDistance && CurrentWeaponType == StartingWeaponType && !SwitchWeaponTypeTriggered)
|
||||
{
|
||||
SwapWeaponType();
|
||||
SwitchWeaponTypeTriggered = true;
|
||||
}
|
||||
}
|
||||
//Switches the current weapon type based on a random time of SwitchWeaponTimeMin and SwitchWeaponTimeMax.
|
||||
else if (SwitchWeaponType == SwitchWeaponTypes.Timed)
|
||||
{
|
||||
if (!EmeraldComponent.AnimationComponent.IsSwitchingWeapons && !EmeraldComponent.AnimationComponent.IsEquipping && !EmeraldComponent.AnimationComponent.IsMoving)
|
||||
SwitchWeaponTimer += Time.deltaTime;
|
||||
|
||||
if (EmeraldComponent.CombatTarget != null && SwitchWeaponTimer >= SwitchWeaponTime &&
|
||||
!EmeraldComponent.AnimationComponent.IsSwitchingWeapons && !EmeraldComponent.AnimationComponent.IsEquipping && !EmeraldComponent.AnimationComponent.IsGettingHit && !EmeraldComponent.AnimationComponent.IsAttacking && !EmeraldComponent.AnimationComponent.IsMoving && !EmeraldComponent.AnimationComponent.IsBackingUp && !EmeraldComponent.AnimationComponent.IsTurning && !EmeraldComponent.AIAnimator.GetBool("Strafe Active"))
|
||||
{
|
||||
SwapWeaponType();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Swaps the current weapon type.
|
||||
/// </summary>
|
||||
public void SwapWeaponType()
|
||||
{
|
||||
if (CurrentWeaponType == WeaponTypes.Type1)
|
||||
{
|
||||
if (SwitchWeaponCoroutine != null) StopCoroutine(SwitchWeaponCoroutine);
|
||||
SwitchWeaponCoroutine = StartCoroutine(ChangeWeaponType("Type2")); //Switch to the Weapon Type 2
|
||||
}
|
||||
else if (CurrentWeaponType == WeaponTypes.Type2)
|
||||
{
|
||||
if (SwitchWeaponCoroutine != null) StopCoroutine(SwitchWeaponCoroutine);
|
||||
SwitchWeaponCoroutine = StartCoroutine(ChangeWeaponType("Type1")); //Switch to the Weapon Type 1
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator ChangeWeaponType(string WeaponTypeName)
|
||||
{
|
||||
EmeraldComponent.AnimationComponent.IsSwitchingWeapons = true;
|
||||
EmeraldCombatManager.ResetWeaponSwapTime(EmeraldComponent);
|
||||
CurrentAnimationIndex = 1;
|
||||
EmeraldComponent.AIAnimator.SetInteger("Attack Index", 1);
|
||||
EmeraldComponent.AIAnimator.SetInteger("Hit Index", 1);
|
||||
EmeraldComponent.AIAnimator.ResetTrigger("Attack");
|
||||
EmeraldComponent.AIAnimator.SetBool("Walk Backwards", false);
|
||||
EmeraldComponent.AIAnimator.ResetTrigger("Hit");
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
|
||||
if (WeaponTypeName == "Type1")
|
||||
{
|
||||
yield return new WaitUntil(()=>EmeraldComponent.AnimationComponent.IsIdling);
|
||||
EmeraldComponent.AIAnimator.SetInteger("Weapon Type State", 1);
|
||||
CurrentAttackCooldown = Type1AttackCooldown;
|
||||
EmeraldComponent.DetectionComponent.PickTargetType = Type1PickTargetType;
|
||||
}
|
||||
else if (WeaponTypeName == "Type2")
|
||||
{
|
||||
yield return new WaitUntil(() => EmeraldComponent.AnimationComponent.IsIdling);
|
||||
EmeraldComponent.AIAnimator.SetInteger("Weapon Type State", 2);
|
||||
CurrentAttackCooldown = Type2AttackCooldown;
|
||||
EmeraldComponent.DetectionComponent.PickTargetType = Type2PickTargetType;
|
||||
}
|
||||
|
||||
CurrentWeaponType = (WeaponTypes)System.Enum.Parse(typeof(WeaponTypes), WeaponTypeName);
|
||||
|
||||
if (EmeraldComponent.AIAnimator.GetBool("Animate Weapon State"))
|
||||
{
|
||||
while (!EmeraldComponent.AnimationComponent.IsEquipping)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//If an AI doesn't have equipping animations, bypass the need for equipping animations and enable/disable them here.
|
||||
EmeraldItems m_EmeraldItems = GetComponent<EmeraldItems>();
|
||||
|
||||
if (m_EmeraldItems != null)
|
||||
{
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
if (WeaponTypeName == "Type1")
|
||||
{
|
||||
m_EmeraldItems.UnequipWeapon("Weapon Type 2");
|
||||
m_EmeraldItems.EquipWeapon("Weapon Type 1");
|
||||
}
|
||||
else if (WeaponTypeName == "Type2")
|
||||
{
|
||||
m_EmeraldItems.UnequipWeapon("Weapon Type 1");
|
||||
m_EmeraldItems.EquipWeapon("Weapon Type 2");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EmeraldComponent.AIAnimator.ResetTrigger("Hit");
|
||||
UpdateWeaponTypeValues();
|
||||
EmeraldComponent.AIAnimator.SetBool("Walk Backwards", false);
|
||||
EmeraldComponent.AnimationComponent.IsSwitchingWeapons = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Regenerate an attack based on the current weapon type and update the needed settings.
|
||||
/// </summary>
|
||||
void UpdateWeaponTypeValues()
|
||||
{
|
||||
EmeraldComponent.AIAnimator.ResetTrigger("Attack");
|
||||
EmeraldComponent.AIAnimator.SetInteger("Attack Index", 1);
|
||||
if (CurrentWeaponType == WeaponTypes.Type1)
|
||||
EmeraldCombatManager.GenerateAttack(EmeraldComponent, Type1Attacks);
|
||||
else if (CurrentWeaponType == WeaponTypes.Type2)
|
||||
EmeraldCombatManager.GenerateAttack(EmeraldComponent, Type2Attacks);
|
||||
}
|
||||
|
||||
public void InvokeDoDamage ()
|
||||
{
|
||||
OnDoDamage?.Invoke();
|
||||
}
|
||||
|
||||
public void InvokeDoCritDamage()
|
||||
{
|
||||
OnDoCritDamage?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for getting the transform of the target.
|
||||
/// </summary>
|
||||
public Transform TargetTransform()
|
||||
{
|
||||
return transform;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for detecting when a target is attacking.
|
||||
/// </summary>
|
||||
public bool IsAttacking()
|
||||
{
|
||||
return EmeraldComponent.AnimationComponent.AttackTriggered;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for detecting when this target is blocking.
|
||||
/// </summary>
|
||||
public bool IsBlocking()
|
||||
{
|
||||
return EmeraldComponent.AnimationComponent.IsBlocking && EmeraldComponent.AIAnimator.GetBool("Blocking");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for detecting when this target is dodging.
|
||||
/// </summary>
|
||||
public bool IsDodging()
|
||||
{
|
||||
return EmeraldComponent.AnimationComponent.IsDodging;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used referencing the AI's damage position when an AI takes damage from external sources.
|
||||
/// </summary>
|
||||
public Vector3 DamagePosition()
|
||||
{
|
||||
if (EmeraldComponent.TPMComponent != null)
|
||||
return new Vector3(EmeraldComponent.TPMComponent.TransformSource.position.x, EmeraldComponent.TPMComponent.TransformSource.position.y + EmeraldComponent.TPMComponent.PositionModifier, EmeraldComponent.TPMComponent.TransformSource.position.z);
|
||||
else
|
||||
return transform.position + new Vector3(0, transform.localScale.y, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when an ability generates a stun (used through the ICombat inferface).
|
||||
/// </summary>
|
||||
public void TriggerStun(float StunLength)
|
||||
{
|
||||
EmeraldComponent.AnimationComponent.PlayStunnedAnimation(StunLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e451fe8b9a2336b4189fd779cf157926
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,650 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
using static UnityEngine.GraphicsBuffer;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[HelpURL("https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-required/detection-component")]
|
||||
public class EmeraldDetection : MonoBehaviour, IFaction
|
||||
{
|
||||
#region Detection Variables
|
||||
public List<Collider> IgnoredColliders = new List<Collider>();
|
||||
public static LayerMask LBDLayers;
|
||||
public Transform CurrentObstruction;
|
||||
public Transform HeadTransform;
|
||||
public float DetectionFrequency = 1;
|
||||
public LayerMask DetectionLayerMask = 3;
|
||||
public LayerMask ObstructionDetectionLayerMask = 4;
|
||||
LayerMask InternalObstructionLayerMask = 4;
|
||||
public string PlayerTag = "Player";
|
||||
public float ObstructionDetectionFrequency = 0.1f;
|
||||
public float ObstructionDetectionUpdateTimer;
|
||||
public float ObstructionSeconds = 1.5f;
|
||||
public int StartingDetectionRadius;
|
||||
public int DetectionRadius = 18;
|
||||
public int StartingChaseDistance;
|
||||
public int FieldOfViewAngle = 270;
|
||||
public int StartingFieldOfViewAngle;
|
||||
public enum DetectionStates { Alert = 0, Unaware = 1 };
|
||||
public DetectionStates CurrentDetectionState = DetectionStates.Unaware;
|
||||
public PickTargetTypes PickTargetType = PickTargetTypes.Closest;
|
||||
public bool TargetObstructed = false;
|
||||
public enum ObstructedTypes { AI, Other, None };
|
||||
public ObstructedTypes ObstructionType = ObstructedTypes.None;
|
||||
public List<Collider> LineOfSightTargets = new List<Collider>();
|
||||
public List<Transform> CurrentFollowers = new List<Transform>();
|
||||
public delegate void OnDetectTargetHandler();
|
||||
public event OnDetectTargetHandler OnDetectionUpdate;
|
||||
public delegate void OnEnemyTargetDetectedHandler();
|
||||
public event OnEnemyTargetDetectedHandler OnEnemyTargetDetected;
|
||||
public delegate void OnNullTargetHandler();
|
||||
public event OnNullTargetHandler OnNullTarget;
|
||||
public delegate void OnPlayerDetectedHandler();
|
||||
public event OnPlayerDetectedHandler OnPlayerDetected;
|
||||
public static List<Transform> IgnoredTargetsList = new List<Transform>();
|
||||
#endregion
|
||||
|
||||
#region Faction Variables
|
||||
[SerializeField]
|
||||
public int CurrentFaction;
|
||||
public static EmeraldFactionData FactionData;
|
||||
[SerializeField]
|
||||
public static List<string> StringFactionList = new List<string>();
|
||||
public List<int> FactionRelations = new List<int>();
|
||||
[SerializeField]
|
||||
public List<FactionClass> FactionRelationsList = new List<FactionClass>();
|
||||
[SerializeField]
|
||||
public List<int> AIFactionsList = new List<int>();
|
||||
#endregion
|
||||
|
||||
#region Editor Specific Variables
|
||||
public bool HideSettingsFoldout;
|
||||
public bool DetectionFoldout;
|
||||
public bool TagFoldout;
|
||||
public bool FactionFoldout;
|
||||
#endregion
|
||||
|
||||
#region Private Variables
|
||||
float DetectionTimer;
|
||||
Vector3 TargetDirection;
|
||||
float ObstructionTimer;
|
||||
EmeraldSystem EmeraldComponent;
|
||||
#endregion
|
||||
|
||||
void Start()
|
||||
{
|
||||
InitializeDetection();
|
||||
Invoke(nameof(InitializeLayers), 0.1f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy the layers from the ObstructionDetectionLayerMask. This also adds the AI's internal
|
||||
/// collider layer to its layers so its own colliders don't cause a false obstruction.
|
||||
/// </summary>
|
||||
void InitializeLayers ()
|
||||
{
|
||||
InternalObstructionLayerMask = ObstructionDetectionLayerMask;
|
||||
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
if (LBDLayers == (LBDLayers | (1 << i)))
|
||||
{
|
||||
InternalObstructionLayerMask |= (1 << i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize all detection related settings.
|
||||
/// </summary>
|
||||
void InitializeDetection ()
|
||||
{
|
||||
EmeraldComponent = GetComponent<EmeraldSystem>();
|
||||
EmeraldComponent.CombatComponent.OnExitCombat += ReturnToDefaultState; //Subscribe the ReturnToDefaultState function to the OnExitCombat delegate
|
||||
EmeraldComponent.HealthComponent.OnDeath += ClearTargetToFollow; //Subscribe the RemoveTargetToFollow function to the OnDeath delegate
|
||||
OnNullTarget += NullNonCombatTarget; //Subscribe the NullNonCombatTarget function to the OnNullTarget delegate
|
||||
|
||||
if (FactionData == null) FactionData = Resources.Load("Faction Data") as EmeraldFactionData;
|
||||
if (EmeraldComponent.LBDComponent == null) Utility.EmeraldCombatManager.DisableRagdoll(EmeraldComponent);
|
||||
|
||||
StartingDetectionRadius = DetectionRadius;
|
||||
TargetObstructed = true;
|
||||
StartingFieldOfViewAngle = FieldOfViewAngle;
|
||||
StartingDetectionRadius = DetectionRadius;
|
||||
|
||||
//If the user forgot to add a head transform, create a temporary one to avoid an error and still allow the AI to function.
|
||||
if (HeadTransform == null)
|
||||
{
|
||||
Transform TempHeadTransform = new GameObject("AI Head Transform").transform;
|
||||
TempHeadTransform.SetParent(transform);
|
||||
TempHeadTransform.localPosition = new Vector3(0, 1, 0);
|
||||
HeadTransform = TempHeadTransform;
|
||||
}
|
||||
|
||||
SetupFactions();
|
||||
Invoke(nameof(CheckFactionRelations), 0.1f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used during initialization to check and notify the user if an AI has its own faction as an Enemy Relation.
|
||||
/// </summary>
|
||||
void CheckFactionRelations()
|
||||
{
|
||||
if (AIFactionsList.Contains(CurrentFaction) && FactionRelations[AIFactionsList.IndexOf(CurrentFaction)] == 0)
|
||||
{
|
||||
Debug.LogError("The AI '" + gameObject.name + "' contains an Enemy Faction Relation of its own Faction '" + GetTargetFactionName(transform) +
|
||||
"'. Please remove the faction from the AI Faction Relation List (within the AI's Detection Component) or change it to Friendly to avoid incorrect target detection.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets up the Factions to be used during runtime.
|
||||
/// </summary>
|
||||
void SetupFactions()
|
||||
{
|
||||
for (int i = 0; i < FactionRelationsList.Count; i++)
|
||||
{
|
||||
AIFactionsList.Add(FactionRelationsList[i].FactionIndex);
|
||||
FactionRelations.Add((int)FactionRelationsList[i].RelationType);
|
||||
}
|
||||
}
|
||||
|
||||
void FixedUpdate()
|
||||
{
|
||||
if (EmeraldComponent.BehaviorsComponent.CurrentBehaviorType == EmeraldBehaviors.BehaviorTypes.Passive) return; //Don't allow passive AI to use line of sight
|
||||
|
||||
if (!EmeraldComponent.CombatComponent.CombatState || EmeraldComponent.CombatComponent.DeathDelayActive)
|
||||
{
|
||||
LineOfSightDetection();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A custom update function for the EmeraldDetection script called through the EmeraldAISystem script.
|
||||
/// </summary>
|
||||
public void DetectionUpdate()
|
||||
{
|
||||
if (EmeraldComponent.CombatComponent.CombatState) CheckForObstructions(EmeraldComponent.CombatTarget); //When in combat, check for obstructions by casting a ray from the AI's Head Transform to its target.
|
||||
else if (!EmeraldComponent.CombatComponent.CombatState) CheckForObstructions(EmeraldComponent.LookAtTarget); //When in not in combat, check for obstructions by casting a ray from the AI's Head Transform to its look at target.
|
||||
|
||||
//Update the AI's OverlapShere function based on the DetectionFrequency
|
||||
if (EmeraldComponent.CombatComponent.TargetDetectionActive && !EmeraldComponent.MovementComponent.ReturningToStartInProgress)
|
||||
{
|
||||
DetectionTimer += Time.deltaTime;
|
||||
|
||||
if (DetectionTimer >= DetectionFrequency)
|
||||
{
|
||||
UpdateAIDetection(); //Casts a Physics.OverlapSphere and only searches for layers based on the user set DetectionLayerMask.
|
||||
LookAtTargetDistanceCheck(); //Check that the LookAtTarget is within the AI's DetectionRadius.
|
||||
OnDetectionUpdate?.Invoke(); //Invoke the OnDetectionUpdate event.
|
||||
DetectionTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
CheckForNullTarget(); //Monitors the AI's TargetSource to see if it becomes null. If it does, invoke the OnNullTarget callback.
|
||||
CheckLookAtTarget(); //Monitors the AI's LookAtTarget to see if its health reaches 0. If it does, clear the LookAtTarget information.
|
||||
ObstructionAction(); //Controls what happens depending on if the AI is obstructed by another AI or by something else, while in combat.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Monitors the AI's TargetSource to see if it becomes null. If it does, invoke the OnNullTarget callback.
|
||||
/// </summary>
|
||||
void CheckForNullTarget()
|
||||
{
|
||||
if (EmeraldComponent.CurrentTargetInfo.TargetSource == null && EmeraldComponent.CurrentTargetInfo.CurrentICombat != null)
|
||||
{
|
||||
OnNullTarget?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called through the OnNullTarget callback when a target becomes null. If this happens, clear all non-combat targets.
|
||||
/// </summary>
|
||||
void NullNonCombatTarget ()
|
||||
{
|
||||
if (!EmeraldComponent.CombatComponent.CombatState)
|
||||
{
|
||||
EmeraldComponent.TargetToFollow = null;
|
||||
EmeraldComponent.LookAtTarget = null;
|
||||
EmeraldComponent.CurrentTargetInfo.TargetSource = null;
|
||||
EmeraldComponent.CurrentTargetInfo.CurrentIDamageable = null;
|
||||
EmeraldComponent.CurrentTargetInfo.CurrentICombat = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Monitors the AI's LookAtTarget to see if its health reaches 0. If it does, clear the LookAtTarget information.
|
||||
/// </summary>
|
||||
void CheckLookAtTarget ()
|
||||
{
|
||||
if (EmeraldComponent.LookAtTarget && !EmeraldComponent.CombatComponent.CombatState && EmeraldComponent.CurrentTargetInfo.CurrentIDamageable.Health <= 0)
|
||||
{
|
||||
EmeraldComponent.LookAtTarget = null;
|
||||
EmeraldComponent.CurrentTargetInfo.CurrentIDamageable = null;
|
||||
EmeraldComponent.CurrentTargetInfo.CurrentICombat = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Controls what happens depending on if the AI is obstructed by another AI or by something else, while in combat.
|
||||
/// </summary>
|
||||
void ObstructionAction ()
|
||||
{
|
||||
if (TargetObstructed && EmeraldComponent.CombatComponent.CombatState && EmeraldComponent.MovementComponent.CanReachTarget)
|
||||
{
|
||||
ObstructionTimer += Time.deltaTime;
|
||||
if (ObstructionTimer >= ObstructionSeconds)
|
||||
{
|
||||
if (ObstructionType == ObstructedTypes.AI)
|
||||
{
|
||||
SearchForTarget(PickTargetTypes.Random);
|
||||
}
|
||||
else if (ObstructionType == ObstructedTypes.Other)
|
||||
{
|
||||
EmeraldComponent.m_NavMeshAgent.stoppingDistance = 3;
|
||||
EmeraldAPI.Internal.GenerateRandomPositionWithinRadius(EmeraldComponent);
|
||||
}
|
||||
|
||||
ObstructionTimer = 0;
|
||||
}
|
||||
}
|
||||
else if (!TargetObstructed && EmeraldComponent.CombatComponent.CombatState && !EmeraldComponent.AnimationComponent.IsBackingUp)
|
||||
{
|
||||
EmeraldComponent.m_NavMeshAgent.stoppingDistance = EmeraldComponent.CombatComponent.AttackDistance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Casts a Physics.OverlapSphere and only searches for layers based on the user set DetectionLayerMask.
|
||||
/// </summary>
|
||||
public void UpdateAIDetection()
|
||||
{
|
||||
if (LineOfSightTargets.Count > 0) LineOfSightTargetsDistanceCheck();
|
||||
|
||||
Collider[] CurrentlyDetectedTargets = Physics.OverlapSphere(transform.position, DetectionRadius, DetectionLayerMask);
|
||||
|
||||
foreach (Collider C in CurrentlyDetectedTargets)
|
||||
{
|
||||
if (C.gameObject != this.gameObject && IsValidTarget(C.transform))
|
||||
{
|
||||
DetectTarget(C.transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns a target based on the passed parameter and an AI's settings. Store the target within LineOfSightTargets to be used elsewhere.
|
||||
/// </summary>
|
||||
void DetectTarget(Transform Target)
|
||||
{
|
||||
if (IgnoredTargetsList.Contains(Target))
|
||||
return;
|
||||
|
||||
if (Target != EmeraldComponent.TargetToFollow && !CurrentFollowers.Contains(Target) && IsEnemyTarget(Target) && EmeraldComponent.BehaviorsComponent.CurrentBehaviorType != EmeraldBehaviors.BehaviorTypes.Passive)
|
||||
{
|
||||
CurrentDetectionState = DetectionStates.Alert;
|
||||
if (!LineOfSightTargets.Contains(Target.GetComponent<Collider>()))
|
||||
LineOfSightTargets.Add(Target.GetComponent<Collider>());
|
||||
}
|
||||
|
||||
if (EmeraldComponent.LookAtTarget == null && EmeraldComponent.CombatTarget == null)
|
||||
{
|
||||
if (IsLookAtTarget(Target))
|
||||
{
|
||||
EmeraldComponent.LookAtTarget = Target;
|
||||
GetTargetInfo(EmeraldComponent.LookAtTarget);
|
||||
OnPlayerDetected?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the AI's line of sight mechanics. For each target that is within the AI's LineOfSightTargets, cast a raycast. If a target is unobstructed, and within the AI's line of sight angle, call the SearchForTarget function.
|
||||
/// </summary>
|
||||
void LineOfSightDetection ()
|
||||
{
|
||||
if (CurrentDetectionState == DetectionStates.Alert && EmeraldComponent.CombatTarget == null && !EmeraldComponent.AnimationComponent.IsDead)
|
||||
{
|
||||
for (int i = LineOfSightTargets.Count - 1; i >= 0; --i)
|
||||
{
|
||||
if (LineOfSightTargets[i] == null)
|
||||
{
|
||||
LineOfSightTargets.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector3 direction = LineOfSightTargets[i].bounds.center - HeadTransform.position;
|
||||
float angle = Vector3.Angle(new Vector3(direction.x, 0, direction.z), transform.forward);
|
||||
|
||||
//Only check targets that are within the AI's line of sight.
|
||||
if (angle < FieldOfViewAngle * 0.5f)
|
||||
{
|
||||
if (!EmeraldComponent.CombatComponent.CombatState)
|
||||
{
|
||||
RaycastHit hit;
|
||||
//Use a special layer mask that also includes the layers of internal colliders (from the LDB component) as these can block the AI's line of sight.
|
||||
if (Physics.Raycast(HeadTransform.position, direction, out hit, DetectionRadius, ~InternalObstructionLayerMask))
|
||||
{
|
||||
if (hit.collider != null && LineOfSightTargets.Contains(hit.collider))
|
||||
{
|
||||
SearchForTarget(PickTargetType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (EmeraldComponent.CombatComponent.CombatState)
|
||||
{
|
||||
SearchForTarget(PickTargetType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return all currently visible targets within the AI's detection radius.
|
||||
/// </summary>
|
||||
public List<Transform> GetVisibleTargets()
|
||||
{
|
||||
List<Transform> VisibleTargets = new List<Transform>();
|
||||
|
||||
foreach (Collider C in LineOfSightTargets.ToArray())
|
||||
{
|
||||
RaycastHit hit;
|
||||
Vector3 direction = C.bounds.center - HeadTransform.position;
|
||||
|
||||
if (Physics.Raycast(HeadTransform.position, direction, out hit, DetectionRadius, ~InternalObstructionLayerMask))
|
||||
{
|
||||
if (hit.collider != null && LineOfSightTargets.Contains(hit.collider))
|
||||
{
|
||||
if (!VisibleTargets.Contains(hit.collider.transform) && EmeraldComponent.CombatTarget != hit.collider.transform || hit.collider.CompareTag("Player"))
|
||||
{
|
||||
VisibleTargets.Add(hit.collider.transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return VisibleTargets;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for a currently visible target within the LineOfSightTargets list using passed PickTargetType. This can be assigned using EmeraldDetection.PickTargetTypes.
|
||||
/// </summary>
|
||||
public void SearchForTarget (PickTargetTypes pickTargetType)
|
||||
{
|
||||
List<Transform> VisibleTargets = GetVisibleTargets();
|
||||
if (EmeraldComponent.CombatTarget != null) VisibleTargets.Remove(EmeraldComponent.CombatTarget); //Remove the current target so it isn't picked again
|
||||
|
||||
if (VisibleTargets.Count > 0)
|
||||
{
|
||||
if (pickTargetType == PickTargetTypes.Closest)
|
||||
{
|
||||
VisibleTargets = VisibleTargets.OrderBy(Target => (Target.position - transform.position).sqrMagnitude).ToList();
|
||||
SetDetectedTarget(VisibleTargets[0]);
|
||||
}
|
||||
else if (pickTargetType == PickTargetTypes.Random)
|
||||
{
|
||||
SetDetectedTarget(VisibleTargets[Random.Range(0, VisibleTargets.Count)]);
|
||||
}
|
||||
else if (pickTargetType == PickTargetTypes.FirstDetected)
|
||||
{
|
||||
SetDetectedTarget(VisibleTargets[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for obstructions by casting a ray from the AI's Head Transform to its target.
|
||||
/// </summary>
|
||||
void CheckForObstructions (Transform TargetSource)
|
||||
{
|
||||
ObstructionDetectionUpdateTimer += Time.deltaTime;
|
||||
|
||||
if (ObstructionDetectionUpdateTimer >= ObstructionDetectionFrequency && TargetSource != null && EmeraldComponent.CurrentTargetInfo.CurrentICombat != null)
|
||||
{
|
||||
TargetDirection = EmeraldComponent.CurrentTargetInfo.CurrentICombat.DamagePosition() - HeadTransform.position;
|
||||
|
||||
RaycastHit hit;
|
||||
|
||||
//Check for obstructions and incrementally lower our AI's stopping distance until one is found. If none are found when the distance has reached 5 or below, search for a new target to see if there is a better option
|
||||
if (Physics.Raycast(HeadTransform.position, (TargetDirection), out hit, EmeraldComponent.CombatComponent.DistanceFromTarget, ~InternalObstructionLayerMask))
|
||||
{
|
||||
if (!hit.collider.transform.IsChildOf(TargetSource) && !hit.collider.transform.IsChildOf(this.transform) && hit.collider.transform != TargetSource && !IgnoredColliders.Contains(hit.collider))
|
||||
{
|
||||
//Set the ObstructionType so different actions can be taken when an AI's line of sight becomes obstructed.
|
||||
if ((LBDLayers & (1 << hit.collider.gameObject.layer)) != 0 || (DetectionLayerMask & (1 << hit.collider.gameObject.layer)) != 0)
|
||||
{
|
||||
ObstructionType = ObstructedTypes.AI;
|
||||
}
|
||||
else
|
||||
{
|
||||
ObstructionType = ObstructedTypes.Other;
|
||||
}
|
||||
|
||||
EmeraldComponent.AIAnimator.ResetTrigger("Attack");
|
||||
TargetObstructed = true;
|
||||
CurrentObstruction = hit.collider.transform;
|
||||
}
|
||||
else
|
||||
{
|
||||
TargetObstructed = false;
|
||||
CurrentObstruction = null;
|
||||
ObstructionType = ObstructedTypes.None;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TargetObstructed = false;
|
||||
CurrentObstruction = null;
|
||||
ObstructionType = ObstructedTypes.None;
|
||||
}
|
||||
|
||||
ObstructionDetectionUpdateTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detects the passed target's Target Type and assigns it as the AI's current target.
|
||||
/// </summary>
|
||||
public void SetDetectedTarget (Transform DetectedTarget)
|
||||
{
|
||||
//Don't assign the newly detected target if it's the same as the current target.
|
||||
if (EmeraldComponent.CombatTarget == DetectedTarget) return;
|
||||
|
||||
EmeraldAI.Utility.EmeraldCombatManager.ActivateCombatState(EmeraldComponent); //Active the Combat State
|
||||
ResetDetectionValues(); //Once a target has been found, reset some of its settings back to their defaults.
|
||||
GetTargetInfo(DetectedTarget);
|
||||
EmeraldComponent.CombatTarget = DetectedTarget;
|
||||
OnEnemyTargetDetected?.Invoke(); //Invoke the OnEnemyTargetDetected when an enemy target has been found.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Once a target has been found, reset some of its settings back to their defaults.
|
||||
/// </summary>
|
||||
void ResetDetectionValues ()
|
||||
{
|
||||
DetectionRadius = StartingDetectionRadius;
|
||||
FieldOfViewAngle = StartingFieldOfViewAngle;
|
||||
EmeraldComponent.m_NavMeshAgent.stoppingDistance = EmeraldComponent.CombatComponent.AttackDistance;
|
||||
EmeraldComponent.AnimationComponent.IsTurning = false;
|
||||
EmeraldComponent.CombatComponent.DeathDelayActive = false;
|
||||
EmeraldComponent.CombatComponent.DeathDelayTimer = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the target's info (from the passed Target parameter).
|
||||
/// </summary>
|
||||
public void GetTargetInfo (Transform Target, bool? OverrideFactionRequirement = false)
|
||||
{
|
||||
if (Target != null)
|
||||
{
|
||||
EmeraldComponent.CurrentTargetInfo.TargetSource = Target;
|
||||
EmeraldComponent.CurrentTargetInfo.CurrentIDamageable = Target.GetComponent<IDamageable>();
|
||||
EmeraldComponent.CurrentTargetInfo.CurrentICombat = Target.GetComponent<ICombat>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check that each LineOfSightTarget is within the AI's DetectionRadius. If not, remove it from the list.
|
||||
/// </summary>
|
||||
void LineOfSightTargetsDistanceCheck()
|
||||
{
|
||||
for (int i = 0; i < LineOfSightTargets.Count; i++)
|
||||
{
|
||||
//Remove any targets that become null during the distance check.
|
||||
if (LineOfSightTargets[i] == null)
|
||||
{
|
||||
LineOfSightTargets.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
float distance = Vector3.Distance(LineOfSightTargets[i].transform.position, transform.position);
|
||||
|
||||
//If the distance of the detected target is greater than the DetectionRadius, remove it from the LineOfSightTargets list.
|
||||
if (distance > DetectionRadius)
|
||||
LineOfSightTargets.Remove(LineOfSightTargets[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check that the LookAtTarget is within the AI's DetectionRadius. If not, remove it as the current LookAtTarget.
|
||||
/// </summary>
|
||||
void LookAtTargetDistanceCheck ()
|
||||
{
|
||||
if (EmeraldComponent.LookAtTarget != null)
|
||||
{
|
||||
float distance = Vector3.Distance(EmeraldComponent.LookAtTarget.transform.position, transform.position);
|
||||
|
||||
//If the distance of the detected LookAtTarget is greater than the DetectionRadius, remove it as the current LookAtTarget.
|
||||
if (distance > DetectionRadius)
|
||||
NullNonCombatTarget();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return to the default state and assign the Look At Target info, if it's not null. This is called through the OnExitCombat callback.
|
||||
/// </summary>
|
||||
void ReturnToDefaultState ()
|
||||
{
|
||||
CurrentDetectionState = DetectionStates.Unaware;
|
||||
if (EmeraldComponent.LookAtTarget != null)
|
||||
GetTargetInfo(EmeraldComponent.LookAtTarget);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Faction Relation name of the passed target and this AI in the form of a string (Enemy, Neutral, or Friendly). If a faction cannot be found, or if it is not a valid target, you will receive a value of Invalid Target.
|
||||
/// </summary>
|
||||
public string GetTargetFactionRelation (Transform Target)
|
||||
{
|
||||
return EmeraldAPI.Faction.GetTargetFactionRelation(EmeraldComponent, Target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the faction name of the passed AI target.
|
||||
/// </summary>
|
||||
public string GetTargetFactionName(Transform Target)
|
||||
{
|
||||
return EmeraldAPI.Faction.GetTargetFactionName(Target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns a new follow target for an AI to follow.
|
||||
/// </summary>
|
||||
public void SetTargetToFollow(Transform Target, bool CopyFactionData = true)
|
||||
{
|
||||
EmeraldSystem TargetEmeraldComponent = Target.GetComponent<EmeraldSystem>(); //Attempt to get the Target's EmeraldComponent
|
||||
if (TargetEmeraldComponent != null)
|
||||
{
|
||||
if (TargetEmeraldComponent.CombatTarget == transform) TargetEmeraldComponent.CombatComponent.ClearTarget(); //If the Target is another AI, clear its targets
|
||||
TargetEmeraldComponent.DetectionComponent.CurrentFollowers.Add(transform); //Add this AI as a follower of the leader AI
|
||||
if (TargetEmeraldComponent.CombatComponent.CombatState) TargetEmeraldComponent.CombatComponent.DeathDelayActive = true;
|
||||
|
||||
//Copies the Target to Follow's Faction Data so it will react the same way the follower does to detected targets.
|
||||
if (CopyFactionData)
|
||||
{
|
||||
CurrentFaction = TargetEmeraldComponent.DetectionComponent.CurrentFaction; //Make the Current Faction the same as the AI's new Target to Follow
|
||||
AIFactionsList = TargetEmeraldComponent.DetectionComponent.AIFactionsList; //Make the Faction List the same as the AI's new Target to Follow
|
||||
FactionRelations = TargetEmeraldComponent.DetectionComponent.FactionRelations; //Make the Faction Relations the same as the AI's new Target to Follow
|
||||
FactionRelationsList = TargetEmeraldComponent.DetectionComponent.FactionRelationsList; //Make the Faction Relations List the same as the AI's new Target to Follow
|
||||
}
|
||||
}
|
||||
|
||||
if (Target == EmeraldComponent.CombatTarget) EmeraldComponent.CombatComponent.ClearTarget();
|
||||
if (EmeraldComponent.CombatComponent.CombatState) EmeraldComponent.CombatComponent.DeathDelayActive = true;
|
||||
//EmeraldComponent.m_NavMeshAgent.stoppingDistance = EmeraldComponent.MovementComponent.FollowingStoppingDistance;
|
||||
EmeraldComponent.TargetToFollow = Target;
|
||||
EmeraldComponent.BehaviorsComponent.TargetToFollow = Target;
|
||||
EmeraldComponent.BehaviorsComponent.ResetState();
|
||||
EmeraldComponent.MovementComponent.CurrentMovementState = EmeraldMovement.MovementStates.Run;
|
||||
EmeraldComponent.MovementComponent.WanderType = EmeraldMovement.WanderTypes.Stationary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the AI's Target to Follow transform so it will be no longer following it. This will also stop the AI from being a Companion AI.
|
||||
/// </summary>
|
||||
public void ClearTargetToFollow()
|
||||
{
|
||||
//This is also called through the OnDeath callback and removes this AI as a follower of its Target to Follow.
|
||||
if (EmeraldComponent.TargetToFollow)
|
||||
{
|
||||
EmeraldSystem TargetEmeraldComponent = EmeraldComponent.TargetToFollow.GetComponent<EmeraldSystem>(); //Attempt to get this AI's Target to Follow
|
||||
|
||||
if (TargetEmeraldComponent)
|
||||
{
|
||||
TargetEmeraldComponent.DetectionComponent.CurrentFollowers.Remove(transform); //Remove this AI as a follower of the leader AI
|
||||
}
|
||||
}
|
||||
|
||||
EmeraldComponent.BehaviorsComponent.TargetToFollow = null;
|
||||
EmeraldComponent.TargetToFollow = null;
|
||||
|
||||
if (!EmeraldComponent.AnimationComponent.IsDead)
|
||||
{
|
||||
EmeraldComponent.BehaviorsComponent.ResetState();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the currently passed transform is an enemy target.
|
||||
/// </summary>
|
||||
bool IsEnemyTarget (Transform Target)
|
||||
{
|
||||
int ReceivedFaction = Target.GetComponent<IFaction>().GetFaction();
|
||||
return AIFactionsList.Contains(ReceivedFaction) && FactionRelations[AIFactionsList.IndexOf(ReceivedFaction)] == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the currently passed transform is a valid look at target.
|
||||
/// </summary>
|
||||
bool IsLookAtTarget(Transform Target)
|
||||
{
|
||||
return Target.gameObject.CompareTag(PlayerTag) && GetTargetFactionRelation(Target) != "Enemy";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the passed target is a valid player, AI, or non-AI target.
|
||||
/// </summary>
|
||||
bool IsValidTarget (Transform Target)
|
||||
{
|
||||
if (Target.GetComponent<IFaction>() != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("The " + Target.name + " object is set as a valid target (both Tag and Layer), but does not have a Faction Extension component on it. Please add one in order for this target to be properly detected.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetFaction()
|
||||
{
|
||||
return CurrentFaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fed907ec0d3ff2a4b90eb7a27b5fad73
|
||||
timeCreated: 1540066880
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,249 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using EmeraldAI.Utility;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles how an receives damage and track health. The Damage function is called through the IDamageable interface script.
|
||||
/// </summary>
|
||||
[HelpURL("https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-required/health-component")]
|
||||
public class EmeraldHealth : MonoBehaviour, IDamageable
|
||||
{
|
||||
#region Health variables
|
||||
public int CurrentHealth = 50;
|
||||
public int StartingHealth = 50;
|
||||
public int HealRate = 0;
|
||||
public bool Immortal = false;
|
||||
public List<string> CurrentActiveEffects;
|
||||
public bool HitEffectFoldout;
|
||||
public YesOrNo UseHitEffect = YesOrNo.No;
|
||||
public Vector3 HitEffectPosOffset;
|
||||
public float HitEffectTimeoutSeconds = 3f;
|
||||
public List<GameObject> HitEffectsList = new List<GameObject>();
|
||||
public delegate void DamageHandler();
|
||||
public event DamageHandler OnTakeDamage;
|
||||
public delegate void TakeCritDamageHandler();
|
||||
public event TakeCritDamageHandler OnTakeCritDamage;
|
||||
public delegate void AnyDamageHandler();
|
||||
public event DamageHandler OnTakeAnyDamage;
|
||||
public delegate void BlockHandler();
|
||||
public event BlockHandler OnBlock;
|
||||
public delegate void DodgeHandler();
|
||||
public event DodgeHandler OnDodge;
|
||||
public delegate void DeathHandler();
|
||||
public event DeathHandler OnDeath;
|
||||
public delegate void HealRateTickHandler();
|
||||
public event HealRateTickHandler OnHealRateTick;
|
||||
public delegate void HealthChangeHandler();
|
||||
public event HealthChangeHandler OnHealthChange;
|
||||
EmeraldSystem EmeraldComponent;
|
||||
|
||||
public List<string> ActiveEffects { get => CurrentActiveEffects; set => CurrentActiveEffects = value; }
|
||||
public int Health { get => CurrentHealth; set => CurrentHealth = value; }
|
||||
public int StartHealth { get => StartingHealth; set => StartingHealth = value; }
|
||||
#endregion
|
||||
|
||||
#region Editor Variables
|
||||
public bool HideSettingsFoldout;
|
||||
public bool HealthFoldout;
|
||||
#endregion
|
||||
|
||||
void Start ()
|
||||
{
|
||||
CurrentHealth = StartingHealth;
|
||||
EmeraldComponent = GetComponent<EmeraldSystem>();
|
||||
EmeraldComponent.CombatComponent.OnExitCombat += StartHealing; //Subscribe to the OnExitCombat event for StartHealing
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Damages the AI and allows it to block and mitigate damage, if enabled. To use the ragdoll feature, all
|
||||
/// parameters need to be used where AttackerTransform is the current attacker.
|
||||
/// </summary>
|
||||
/// <param name="DamageAmount">Amount of damage caused during attack.</param>
|
||||
/// <param name="AttackerTransform">The transform of the current attacker.</param>
|
||||
/// <param name="RagdollForce">The amount of force to apply to this AI when they die. (Use Ragdoll must be enabled on this AI)</param>
|
||||
public void Damage(int DamageAmount, Transform AttackerTransform = null, int RagdollForce = 100, bool CriticalHit = false)
|
||||
{
|
||||
if (EmeraldComponent.AnimationComponent.IsDead || transform.localScale == Vector3.one * 0.003f || AttackerTransform && AttackerTransform == EmeraldComponent.TargetToFollow || AttackerTransform && EmeraldComponent.DetectionComponent.GetTargetFactionRelation(AttackerTransform) == "Friendly") return;
|
||||
|
||||
//Check for an attacker if there's no current target.
|
||||
if (AttackerTransform != null) CheckForAttacker(AttackerTransform);
|
||||
|
||||
//Cache the reference to the newest/current attacker.
|
||||
EmeraldComponent.CombatComponent.LastAttacker = AttackerTransform;
|
||||
|
||||
//Get the angle from the current attacker to determine if the incoming hit can be blocked or dodge.
|
||||
float AttackerAngle = EmeraldCombatManager.TransformAngle(EmeraldComponent, AttackerTransform);
|
||||
|
||||
//Check to see if the current attack is being blocked
|
||||
bool Blocked = (EmeraldComponent.AnimationComponent.IsBlocking && AttackerAngle <= EmeraldComponent.CombatComponent.MaxMitigationAngle && EmeraldComponent.AIAnimator.GetBool("Blocking"));
|
||||
|
||||
//Check to see if the current attack is being dodged
|
||||
bool Dodged = (EmeraldComponent.AnimationComponent.IsDodging && AttackerAngle <= EmeraldComponent.CombatComponent.MaxMitigationAngle);
|
||||
|
||||
//Set the base calculated damage equal to DamageAmount. If it is dodged or blocked, the amount will be adjusted below.
|
||||
int CalculatedDamage = DamageAmount;
|
||||
|
||||
if (Blocked) CalculatedDamage = Mathf.FloorToInt(Mathf.Abs((DamageAmount * ((EmeraldComponent.CombatComponent.MitigationAmount) * 0.01f)) - DamageAmount)); //Mitigate the damage from blocking
|
||||
if (Dodged) CalculatedDamage = Mathf.FloorToInt(Mathf.Abs((DamageAmount * ((EmeraldComponent.CombatComponent.MitigationAmount) * 0.01f)) - DamageAmount)); //Mitigate the damage from dodging
|
||||
|
||||
//Don't reduce an AI's health if Immortal is enabled
|
||||
if (!Immortal)
|
||||
Health -= CalculatedDamage;
|
||||
|
||||
//Display the damage dealt through the Combat Text System, given that it's enabled.
|
||||
if (CalculatedDamage > 0) CombatTextSystem.Instance.CreateCombatTextAI(CalculatedDamage, EmeraldComponent.CombatComponent.DamagePosition(), CriticalHit, false);
|
||||
|
||||
//In order to have the most reliable On Do Damage events, simply invoke the attacker's OnDoDoamage callback through a public function, given it is an Emerald AI agent.
|
||||
if (AttackerTransform != null)
|
||||
{
|
||||
EmeraldSystem AttackEmeraldComponent = AttackerTransform.GetComponent<EmeraldSystem>();
|
||||
if (AttackEmeraldComponent != null) AttackEmeraldComponent.CombatComponent.InvokeDoDamage();
|
||||
if (AttackEmeraldComponent != null && CriticalHit) AttackEmeraldComponent.CombatComponent.InvokeDoCritDamage();
|
||||
}
|
||||
|
||||
//Invoke the damage delegates
|
||||
if (!CriticalHit && CalculatedDamage > 0) OnTakeDamage?.Invoke();
|
||||
else if (CriticalHit && CalculatedDamage > 0) OnTakeCritDamage?.Invoke();
|
||||
OnTakeAnyDamage?.Invoke();
|
||||
|
||||
//Create hit effect, if it's enabled, the AI is not blocking or dodging, and the damage is greater than 0.
|
||||
if (!Blocked && !Dodged && CalculatedDamage > 0) CreateHitEffect();
|
||||
|
||||
if (Blocked) OnBlock?.Invoke(); //Invoke the block delegate
|
||||
else if (Dodged) OnDodge?.Invoke(); //Invoke the dodge delegate
|
||||
|
||||
//The AI has died, initialize its death state.
|
||||
if (Health <= 0 && !EmeraldComponent.AnimationComponent.IsDead)
|
||||
{
|
||||
EmeraldComponent.CombatComponent.ReceivedRagdollForceAmount = RagdollForce;
|
||||
Health = 0;
|
||||
Death();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when an AI receives damage, but has no current target (typically from an unseen attacker). When this happenss, assign the
|
||||
/// attacker as the current target (if the have a Neutral Relation Type or higher) or search for any visible targets within the AI's Line of Sight.
|
||||
/// </summary>
|
||||
void CheckForAttacker (Transform AttackerTransform)
|
||||
{
|
||||
if (EmeraldComponent.CombatTarget == null && !EmeraldComponent.CombatComponent.CombatState)
|
||||
{
|
||||
StartCoroutine(DelaySetDetectedTarget(AttackerTransform));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delay SetDetectedTarget to give the AI time to play its non-combat hit animation and transfer to its combat state.
|
||||
/// </summary>
|
||||
IEnumerator DelaySetDetectedTarget(Transform AttackerTransform)
|
||||
{
|
||||
string RelationName = EmeraldComponent.DetectionComponent.GetTargetFactionRelation(AttackerTransform);
|
||||
|
||||
yield return new WaitForSeconds(0.6f);
|
||||
|
||||
if (RelationName == "Neutral" || RelationName == "Enemy")
|
||||
{
|
||||
EmeraldComponent.DetectionComponent.SetDetectedTarget(AttackerTransform);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmeraldComponent.DetectionComponent.SearchForTarget(PickTargetTypes.Closest);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the AI's health reaches 0. The OnDeath delegate is also invoked and is responsible for triggering any death functionality to external subscribers.
|
||||
/// </summary>
|
||||
void Death()
|
||||
{
|
||||
OnDeath?.Invoke(); //Invoke the AI death event.
|
||||
EmeraldComponent.AnimationComponent.IsDead = true;
|
||||
EmeraldCombatManager.DisableComponents(EmeraldComponent);
|
||||
EmeraldCombatManager.EnableRagdoll(EmeraldComponent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refills the AI's health to full instantly
|
||||
/// </summary>
|
||||
public void InstantlyRefillAIHealth()
|
||||
{
|
||||
Health = StartHealth;
|
||||
CurrentHealth = StartHealth;
|
||||
OnHealthChange?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantly kills this AI.
|
||||
/// </summary>
|
||||
public void KillAI()
|
||||
{
|
||||
EmeraldAPI.Combat.KillAI(EmeraldComponent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called through the OnExitCombat callback when an AI exits combat. This heals the AI over time according to its HealRate.
|
||||
/// </summary>
|
||||
void StartHealing ()
|
||||
{
|
||||
StartCoroutine(StartHealingInternal());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called through the StartHealing function which increases the AI's health each HealRate. This gets canceled if the target enters combat.
|
||||
/// </summary>
|
||||
IEnumerator StartHealingInternal ()
|
||||
{
|
||||
float t = 0;
|
||||
|
||||
while (CurrentHealth < StartingHealth)
|
||||
{
|
||||
t += Time.deltaTime;
|
||||
|
||||
if (t >= 1)
|
||||
{
|
||||
CurrentHealth = CurrentHealth + HealRate;
|
||||
OnHealRateTick?.Invoke();
|
||||
t = 0;
|
||||
}
|
||||
|
||||
if (EmeraldComponent.CombatComponent.CombatState) yield break;
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
CurrentHealth = StartingHealth;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the AI's Max Health and Current Health.
|
||||
/// </summary>
|
||||
public void UpdateHealth(int MaxHealth, int CurrentHealth)
|
||||
{
|
||||
Health = CurrentHealth;
|
||||
StartHealth = MaxHealth;
|
||||
OnHealthChange?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates (an optional) hit effect (outside of an AI's Ability Objects) when an AI takes damage.
|
||||
/// </summary>
|
||||
void CreateHitEffect()
|
||||
{
|
||||
if (EmeraldComponent.HealthComponent.UseHitEffect == YesOrNo.Yes && !EmeraldComponent.LBDComponent && EmeraldComponent.HealthComponent.HitEffectsList.Count > 0 && !EmeraldComponent.AnimationComponent.IsDead)
|
||||
{
|
||||
GameObject RandomBloodEffect = EmeraldComponent.HealthComponent.HitEffectsList[UnityEngine.Random.Range(0, EmeraldComponent.HealthComponent.HitEffectsList.Count)];
|
||||
if (RandomBloodEffect != null)
|
||||
{
|
||||
GameObject SpawnedBlood = EmeraldObjectPool.SpawnEffect(RandomBloodEffect, Vector3.zero, EmeraldComponent.transform.rotation, EmeraldComponent.HealthComponent.HitEffectTimeoutSeconds) as GameObject;
|
||||
SpawnedBlood.transform.SetParent(EmeraldComponent.transform);
|
||||
SpawnedBlood.transform.position = EmeraldComponent.CombatComponent.DamagePosition() + EmeraldComponent.HealthComponent.HitEffectPosOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4435fe59ad515fd4d94c2dd09006f199
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a91628e3b6f5c0418d73c9a0ccf4df1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,562 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EmeraldAI
|
||||
{
|
||||
[HelpURL("https://black-horizon-studios.gitbook.io/emerald-ai-wiki/emerald-components-required/sounds-component")]
|
||||
public class EmeraldSounds : MonoBehaviour
|
||||
{
|
||||
#region Variables
|
||||
public Utility.EmeraldSoundProfile SoundProfile;
|
||||
public bool SoundProfileFoldout;
|
||||
public bool HideSettingsFoldout;
|
||||
public int IdleSoundsSeconds;
|
||||
public float IdleSoundsTimer;
|
||||
|
||||
public AudioSource m_AudioSource;
|
||||
public AudioSource m_SecondaryAudioSource;
|
||||
public AudioSource m_EventAudioSource;
|
||||
|
||||
EmeraldSystem EmeraldComponent;
|
||||
EmeraldHealth EmeraldHealth;
|
||||
EmeraldItems EmeraldItems;
|
||||
#endregion
|
||||
|
||||
void Awake()
|
||||
{
|
||||
InitializeSounds(); //Initialize the EmeraldSounds script.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the sound settings.
|
||||
/// </summary>
|
||||
public void InitializeSounds()
|
||||
{
|
||||
EmeraldHealth = GetComponent<EmeraldHealth>();
|
||||
EmeraldItems = GetComponent<EmeraldItems>();
|
||||
EmeraldComponent = GetComponent<EmeraldSystem>();
|
||||
|
||||
//Do not subscribe to any delegates if the sound profile is null.
|
||||
if (SoundProfile == null)
|
||||
return;
|
||||
|
||||
EmeraldHealth.OnTakeDamage += PlayInjuredSound; //Subscribe to the OnTakeDamage event for Injured Sounds
|
||||
EmeraldHealth.OnTakeCritDamage += PlayInjuredSound; //Subscribe to the OnTakeCritDamage event for Injured Sounds
|
||||
EmeraldHealth.OnBlock += PlayBlockSound; //Subscribe to the OnTakeDamage event for Block Sounds
|
||||
EmeraldHealth.OnDeath += PlayDeathSound; //Subscribe to the OnDeath event for Death Sounds
|
||||
|
||||
if (EmeraldItems != null)
|
||||
{
|
||||
EmeraldItems.OnEquipWeapon += PlayEquipSound; //Subscribe to the OnEquipWeapon event for Equip Sounds
|
||||
EmeraldItems.OnUnequipWeapon += PlayUnequipSound; //Subscribe to the OnUnequipWeapon event for Unequip Sounds
|
||||
}
|
||||
|
||||
IdleSoundsSeconds = Random.Range(SoundProfile.IdleSoundsSecondsMin, SoundProfile.IdleSoundsSecondsMax + 1);
|
||||
m_AudioSource = GetComponent<AudioSource>();
|
||||
m_SecondaryAudioSource = gameObject.AddComponent<AudioSource>();
|
||||
m_SecondaryAudioSource.priority = m_AudioSource.priority;
|
||||
m_SecondaryAudioSource.spatialBlend = m_AudioSource.spatialBlend;
|
||||
m_SecondaryAudioSource.minDistance = m_AudioSource.minDistance;
|
||||
m_SecondaryAudioSource.maxDistance = m_AudioSource.maxDistance;
|
||||
m_SecondaryAudioSource.rolloffMode = m_AudioSource.rolloffMode;
|
||||
m_EventAudioSource = gameObject.AddComponent<AudioSource>();
|
||||
m_EventAudioSource.priority = m_AudioSource.priority;
|
||||
m_EventAudioSource.spatialBlend = m_AudioSource.spatialBlend;
|
||||
m_EventAudioSource.minDistance = m_AudioSource.minDistance;
|
||||
m_EventAudioSource.maxDistance = m_AudioSource.maxDistance;
|
||||
m_EventAudioSource.rolloffMode = m_AudioSource.rolloffMode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play a random idle sound when the IdleSoundsSeconds have been met.
|
||||
/// </summary>
|
||||
public void IdleSoundsUpdate ()
|
||||
{
|
||||
IdleSoundsTimer += Time.deltaTime;
|
||||
if (IdleSoundsTimer >= IdleSoundsSeconds)
|
||||
{
|
||||
PlayIdleSound();
|
||||
IdleSoundsTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a sound clip according to the Clip parameter.
|
||||
/// </summary>
|
||||
public void PlaySoundClip(AudioClip Clip)
|
||||
{
|
||||
if (!m_AudioSource.isPlaying)
|
||||
{
|
||||
m_AudioSource.volume = 1;
|
||||
m_AudioSource.PlayOneShot(Clip);
|
||||
}
|
||||
else if (!m_SecondaryAudioSource.isPlaying)
|
||||
{
|
||||
m_SecondaryAudioSource.volume = 1;
|
||||
m_SecondaryAudioSource.PlayOneShot(Clip);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_EventAudioSource.volume = 1;
|
||||
m_EventAudioSource.PlayOneShot(Clip);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a sound clip according to the Clip parameter with optional volume control.
|
||||
/// </summary>
|
||||
public void PlayAudioClip(AudioClip Clip, float Volume = 1)
|
||||
{
|
||||
if (!m_AudioSource.isPlaying)
|
||||
{
|
||||
m_AudioSource.volume = Volume;
|
||||
m_AudioSource.PlayOneShot(Clip);
|
||||
}
|
||||
else if (!m_SecondaryAudioSource.isPlaying)
|
||||
{
|
||||
m_SecondaryAudioSource.volume = Volume;
|
||||
m_SecondaryAudioSource.PlayOneShot(Clip);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_EventAudioSource.volume = Volume;
|
||||
m_EventAudioSource.PlayOneShot(Clip);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a sound clip according to the Clip parameter with a customizable volume.
|
||||
/// </summary>
|
||||
public void PlaySoundClipWithVolume(AudioClip Clip, float Volume)
|
||||
{
|
||||
if (!m_AudioSource.isPlaying)
|
||||
{
|
||||
m_AudioSource.volume = Volume;
|
||||
m_AudioSource.PlayOneShot(Clip);
|
||||
}
|
||||
else if (!m_SecondaryAudioSource.isPlaying)
|
||||
{
|
||||
m_SecondaryAudioSource.volume = Volume;
|
||||
m_SecondaryAudioSource.PlayOneShot(Clip);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_EventAudioSource.volume = Volume;
|
||||
m_EventAudioSource.PlayOneShot(Clip);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a random attack sound based on your AI's Attack Sounds list. Can also be called through Animation Events.
|
||||
/// </summary>
|
||||
public void PlayIdleSound()
|
||||
{
|
||||
if (SoundProfile && SoundProfile.IdleSounds.Count > 0)
|
||||
{
|
||||
if (!m_AudioSource.isPlaying)
|
||||
{
|
||||
AudioClip m_RandomIdleSoundClip = SoundProfile.IdleSounds[Random.Range(0, SoundProfile.IdleSounds.Count)];
|
||||
if (m_RandomIdleSoundClip != null)
|
||||
{
|
||||
m_AudioSource.volume = SoundProfile.IdleVolume;
|
||||
m_AudioSource.PlayOneShot(m_RandomIdleSoundClip);
|
||||
IdleSoundsSeconds = Random.Range(SoundProfile.IdleSoundsSecondsMin, SoundProfile.IdleSoundsSecondsMax);
|
||||
IdleSoundsSeconds = (int)m_RandomIdleSoundClip.length + IdleSoundsSeconds;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AudioClip m_RandomIdleSoundClip = SoundProfile.IdleSounds[Random.Range(0, SoundProfile.IdleSounds.Count)];
|
||||
if (m_RandomIdleSoundClip != null)
|
||||
{
|
||||
m_SecondaryAudioSource.volume = SoundProfile.IdleVolume;
|
||||
m_SecondaryAudioSource.PlayOneShot(m_RandomIdleSoundClip);
|
||||
IdleSoundsSeconds = Random.Range(SoundProfile.IdleSoundsSecondsMin, SoundProfile.IdleSoundsSecondsMax);
|
||||
IdleSoundsSeconds = (int)m_RandomIdleSoundClip.length + IdleSoundsSeconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a random attack sound based on your AI's Attack Sounds list. Can also be called through Animation Events.
|
||||
/// </summary>
|
||||
public void PlayAttackSound()
|
||||
{
|
||||
if (SoundProfile.AttackSounds.Count > 0)
|
||||
{
|
||||
if (!m_AudioSource.isPlaying)
|
||||
{
|
||||
m_AudioSource.volume = SoundProfile.AttackVolume;
|
||||
m_AudioSource.pitch = Mathf.Round(Random.Range(0.9f, 1.1f) * 10) / 10;
|
||||
m_AudioSource.PlayOneShot(SoundProfile.AttackSounds[Random.Range(0, SoundProfile.AttackSounds.Count)]);
|
||||
}
|
||||
else if (!m_SecondaryAudioSource.isPlaying)
|
||||
{
|
||||
m_SecondaryAudioSource.volume = SoundProfile.AttackVolume;
|
||||
m_SecondaryAudioSource.PlayOneShot(SoundProfile.AttackSounds[Random.Range(0, SoundProfile.AttackSounds.Count)]);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_EventAudioSource.volume = SoundProfile.AttackVolume;
|
||||
m_EventAudioSource.PlayOneShot(SoundProfile.AttackSounds[Random.Range(0, SoundProfile.AttackSounds.Count)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a equip sound based on your AI's Equip Weapon sounds (is called automatically through the EquipWeapon Animation Event).
|
||||
/// </summary>
|
||||
public void PlayEquipSound (string WeaponType)
|
||||
{
|
||||
if (WeaponType == "Weapon Type 1")
|
||||
{
|
||||
if (SoundProfile.UnsheatheWeapon != null)
|
||||
{
|
||||
m_AudioSource.volume = SoundProfile.EquipVolume;
|
||||
m_SecondaryAudioSource.volume = SoundProfile.EquipVolume;
|
||||
|
||||
if (!m_AudioSource.isPlaying)
|
||||
{
|
||||
m_AudioSource.PlayOneShot(SoundProfile.UnsheatheWeapon);
|
||||
}
|
||||
else if (!m_SecondaryAudioSource.isPlaying)
|
||||
{
|
||||
m_SecondaryAudioSource.PlayOneShot(SoundProfile.UnsheatheWeapon);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_EventAudioSource.PlayOneShot(SoundProfile.UnsheatheWeapon);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (WeaponType == "Weapon Type 2")
|
||||
{
|
||||
if (SoundProfile.RangedUnsheatheWeapon != null)
|
||||
{
|
||||
m_AudioSource.volume = SoundProfile.RangedEquipVolume;
|
||||
m_SecondaryAudioSource.volume = SoundProfile.RangedEquipVolume;
|
||||
|
||||
if (!m_AudioSource.isPlaying)
|
||||
{
|
||||
m_AudioSource.PlayOneShot(SoundProfile.RangedUnsheatheWeapon);
|
||||
}
|
||||
else if (!m_SecondaryAudioSource.isPlaying)
|
||||
{
|
||||
m_SecondaryAudioSource.PlayOneShot(SoundProfile.RangedUnsheatheWeapon);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_EventAudioSource.PlayOneShot(SoundProfile.RangedUnsheatheWeapon);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a unequip sound based on your AI's Unequip Weapon sounds (is called automatically through the UnequipWeapon Animation Event).
|
||||
/// </summary>
|
||||
public void PlayUnequipSound(string WeaponType)
|
||||
{
|
||||
if (WeaponType == "Weapon Type 1")
|
||||
{
|
||||
if (SoundProfile.SheatheWeapon != null)
|
||||
{
|
||||
m_AudioSource.volume = SoundProfile.UnequipVolume;
|
||||
m_SecondaryAudioSource.volume = SoundProfile.UnequipVolume;
|
||||
|
||||
if (!m_AudioSource.isPlaying)
|
||||
{
|
||||
m_AudioSource.PlayOneShot(SoundProfile.SheatheWeapon);
|
||||
}
|
||||
else if (!m_SecondaryAudioSource.isPlaying)
|
||||
{
|
||||
m_SecondaryAudioSource.PlayOneShot(SoundProfile.SheatheWeapon);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_EventAudioSource.PlayOneShot(SoundProfile.SheatheWeapon);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (WeaponType == "Weapon Type 2")
|
||||
{
|
||||
if (SoundProfile.RangedSheatheWeapon != null)
|
||||
{
|
||||
m_AudioSource.volume = SoundProfile.RangedUnequipVolume;
|
||||
m_SecondaryAudioSource.volume = SoundProfile.RangedUnequipVolume;
|
||||
|
||||
if (!m_AudioSource.isPlaying)
|
||||
{
|
||||
m_AudioSource.PlayOneShot(SoundProfile.RangedSheatheWeapon);
|
||||
}
|
||||
else if (!m_SecondaryAudioSource.isPlaying)
|
||||
{
|
||||
m_SecondaryAudioSource.PlayOneShot(SoundProfile.RangedSheatheWeapon);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_EventAudioSource.PlayOneShot(SoundProfile.RangedSheatheWeapon);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a random attack sound based on your AI's Attack Sounds list. Can also be called through Animation Events.
|
||||
/// </summary>
|
||||
public void PlayWarningSound()
|
||||
{
|
||||
if (SoundProfile.WarningSounds.Count > 0)
|
||||
{
|
||||
if (!m_AudioSource.isPlaying)
|
||||
{
|
||||
m_AudioSource.volume = SoundProfile.WarningVolume;
|
||||
m_AudioSource.PlayOneShot(SoundProfile.WarningSounds[Random.Range(0, SoundProfile.WarningSounds.Count)]);
|
||||
}
|
||||
else if (!m_SecondaryAudioSource.isPlaying)
|
||||
{
|
||||
m_SecondaryAudioSource.volume = SoundProfile.WarningVolume;
|
||||
m_SecondaryAudioSource.PlayOneShot(SoundProfile.WarningSounds[Random.Range(0, SoundProfile.WarningSounds.Count)]);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_EventAudioSource.volume = SoundProfile.WarningVolume;
|
||||
m_EventAudioSource.PlayOneShot(SoundProfile.WarningSounds[Random.Range(0, SoundProfile.WarningSounds.Count)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a random block sound based on your AI's Block Sounds list.
|
||||
/// </summary>
|
||||
public void PlayBlockSound()
|
||||
{
|
||||
if (SoundProfile.BlockingSounds.Count > 0)
|
||||
{
|
||||
if (!m_AudioSource.isPlaying)
|
||||
{
|
||||
m_AudioSource.volume = SoundProfile.BlockVolume;
|
||||
m_AudioSource.pitch = Mathf.Round(Random.Range(0.7f, 1.1f) * 10) / 10;
|
||||
m_AudioSource.PlayOneShot(SoundProfile.BlockingSounds[Random.Range(0, SoundProfile.BlockingSounds.Count)]);
|
||||
}
|
||||
else if (!m_SecondaryAudioSource.isPlaying)
|
||||
{
|
||||
m_SecondaryAudioSource.volume = SoundProfile.BlockVolume;
|
||||
m_SecondaryAudioSource.pitch = Mathf.Round(Random.Range(0.7f, 1.1f) * 10) / 10;
|
||||
m_SecondaryAudioSource.PlayOneShot(SoundProfile.BlockingSounds[Random.Range(0, SoundProfile.BlockingSounds.Count)]);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_EventAudioSource.volume = SoundProfile.BlockVolume;
|
||||
m_EventAudioSource.pitch = Mathf.Round(Random.Range(0.7f, 1.1f) * 10) / 10;
|
||||
m_EventAudioSource.PlayOneShot(SoundProfile.BlockingSounds[Random.Range(0, SoundProfile.BlockingSounds.Count)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a random injured sound based on your AI's Injured Sounds list.
|
||||
/// </summary>
|
||||
public void PlayInjuredSound()
|
||||
{
|
||||
int Odds = Random.Range(1, 101);
|
||||
if (Odds > SoundProfile.InjuredSoundOdds) return;
|
||||
|
||||
if (SoundProfile.InjuredSounds.Count > 0 && !EmeraldComponent.AnimationComponent.IsBlocking)
|
||||
{
|
||||
if (!m_AudioSource.isPlaying)
|
||||
{
|
||||
m_AudioSource.volume = SoundProfile.InjuredVolume;
|
||||
m_AudioSource.pitch = Mathf.Round(Random.Range(0.8f, 1.1f) * 10) / 10;
|
||||
m_AudioSource.PlayOneShot(SoundProfile.InjuredSounds[Random.Range(0, SoundProfile.InjuredSounds.Count)]);
|
||||
}
|
||||
else if (!m_SecondaryAudioSource.isPlaying)
|
||||
{
|
||||
m_SecondaryAudioSource.volume = SoundProfile.InjuredVolume;
|
||||
m_SecondaryAudioSource.PlayOneShot(SoundProfile.InjuredSounds[Random.Range(0, SoundProfile.InjuredSounds.Count)]);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_EventAudioSource.volume = SoundProfile.InjuredVolume;
|
||||
m_EventAudioSource.PlayOneShot(SoundProfile.InjuredSounds[Random.Range(0, SoundProfile.InjuredSounds.Count)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a random death sound based on your AI's Death Sounds list. Can also be called through Animation Events.
|
||||
/// </summary>
|
||||
public void PlayDeathSound()
|
||||
{
|
||||
if (SoundProfile.DeathSounds.Count > 0)
|
||||
{
|
||||
if (!m_AudioSource.isPlaying)
|
||||
{
|
||||
m_AudioSource.volume = SoundProfile.DeathVolume;
|
||||
m_AudioSource.PlayOneShot(SoundProfile.DeathSounds[Random.Range(0, SoundProfile.DeathSounds.Count)]);
|
||||
}
|
||||
else if (!m_SecondaryAudioSource.isPlaying)
|
||||
{
|
||||
m_SecondaryAudioSource.volume = SoundProfile.DeathVolume;
|
||||
m_SecondaryAudioSource.PlayOneShot(SoundProfile.DeathSounds[Random.Range(0, SoundProfile.DeathSounds.Count)]);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_EventAudioSource.volume = SoundProfile.DeathVolume;
|
||||
m_EventAudioSource.PlayOneShot(SoundProfile.DeathSounds[Random.Range(0, SoundProfile.DeathSounds.Count)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If using a Footstep Component, this creates a footstep effect and sound based on the detected surface of the footstep (requires a Footstep Component that has been set up).
|
||||
/// If NOT using a Footstep Component, this plays a random footstep sound based off of your AI's Walk Sound List.
|
||||
/// </summary>
|
||||
public void Footstep()
|
||||
{
|
||||
if (GetComponent<EmeraldFootsteps>() != null) return;
|
||||
|
||||
if (EmeraldComponent.MovementComponent.CanPlayWalkFootstepSound() || EmeraldComponent.MovementComponent.CanPlayRunFootstepSound())
|
||||
{
|
||||
float StepVolume = EmeraldComponent.MovementComponent.CanPlayWalkFootstepSound() ? SoundProfile.WalkFootstepVolume : SoundProfile.RunFootstepVolume;
|
||||
|
||||
if (SoundProfile.FootStepSounds.Count > 0)
|
||||
{
|
||||
if (!m_AudioSource.isPlaying)
|
||||
{
|
||||
m_AudioSource.volume = StepVolume;
|
||||
m_AudioSource.PlayOneShot(SoundProfile.FootStepSounds[Random.Range(0, SoundProfile.FootStepSounds.Count)]);
|
||||
}
|
||||
else if (!m_SecondaryAudioSource.isPlaying)
|
||||
{
|
||||
m_SecondaryAudioSource.volume = StepVolume;
|
||||
m_SecondaryAudioSource.PlayOneShot(SoundProfile.FootStepSounds[Random.Range(0, SoundProfile.FootStepSounds.Count)]);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_EventAudioSource.volume = StepVolume;
|
||||
m_EventAudioSource.PlayOneShot(SoundProfile.FootStepSounds[Random.Range(0, SoundProfile.FootStepSounds.Count)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a footstep sound from the AI's Footstep Sounds list to use when the AI is walking. This should be setup through an Animation Event.
|
||||
/// </summary>
|
||||
public void WalkFootstepSound()
|
||||
{
|
||||
if (GetComponent<EmeraldFootsteps>() != null) return;
|
||||
|
||||
if (EmeraldComponent.MovementComponent.CanPlayWalkFootstepSound())
|
||||
{
|
||||
if (SoundProfile.FootStepSounds.Count > 0)
|
||||
{
|
||||
if (!m_AudioSource.isPlaying)
|
||||
{
|
||||
m_AudioSource.volume = SoundProfile.WalkFootstepVolume;
|
||||
m_AudioSource.PlayOneShot(SoundProfile.FootStepSounds[Random.Range(0, SoundProfile.FootStepSounds.Count)]);
|
||||
}
|
||||
else if (!m_SecondaryAudioSource.isPlaying)
|
||||
{
|
||||
m_SecondaryAudioSource.volume = SoundProfile.WalkFootstepVolume;
|
||||
m_SecondaryAudioSource.PlayOneShot(SoundProfile.FootStepSounds[Random.Range(0, SoundProfile.FootStepSounds.Count)]);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_EventAudioSource.volume = SoundProfile.WalkFootstepVolume;
|
||||
m_EventAudioSource.PlayOneShot(SoundProfile.FootStepSounds[Random.Range(0, SoundProfile.FootStepSounds.Count)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a footstep sound from the AI's Footstep Sounds list to use when the AI is running. This should be setup through an Animation Event.
|
||||
/// </summary>
|
||||
public void RunFootstepSound()
|
||||
{
|
||||
if (GetComponent<EmeraldFootsteps>() != null) return;
|
||||
|
||||
if (EmeraldComponent.MovementComponent.CanPlayRunFootstepSound())
|
||||
{
|
||||
if (SoundProfile.FootStepSounds.Count > 0)
|
||||
{
|
||||
if (!m_AudioSource.isPlaying)
|
||||
{
|
||||
m_AudioSource.volume = SoundProfile.RunFootstepVolume;
|
||||
m_AudioSource.PlayOneShot(SoundProfile.FootStepSounds[Random.Range(0, SoundProfile.FootStepSounds.Count)]);
|
||||
}
|
||||
else if (!m_SecondaryAudioSource.isPlaying)
|
||||
{
|
||||
m_SecondaryAudioSource.volume = SoundProfile.RunFootstepVolume;
|
||||
m_SecondaryAudioSource.PlayOneShot(SoundProfile.FootStepSounds[Random.Range(0, SoundProfile.FootStepSounds.Count)]);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_EventAudioSource.volume = SoundProfile.RunFootstepVolume;
|
||||
m_EventAudioSource.PlayOneShot(SoundProfile.FootStepSounds[Random.Range(0, SoundProfile.FootStepSounds.Count)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a random sound effect from the AI's General Sounds list.
|
||||
/// </summary>
|
||||
public void PlayRandomSoundEffect()
|
||||
{
|
||||
if (SoundProfile.InteractSounds.Count > 0)
|
||||
{
|
||||
if (!m_AudioSource.isPlaying)
|
||||
{
|
||||
m_AudioSource.volume = 1;
|
||||
m_AudioSource.PlayOneShot(SoundProfile.InteractSounds[Random.Range(0, SoundProfile.InteractSounds.Count)].SoundEffectClip);
|
||||
}
|
||||
else if (!m_SecondaryAudioSource.isPlaying)
|
||||
{
|
||||
m_SecondaryAudioSource.volume = 1;
|
||||
m_SecondaryAudioSource.PlayOneShot(SoundProfile.InteractSounds[Random.Range(0, SoundProfile.InteractSounds.Count)].SoundEffectClip);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_EventAudioSource.volume = 1;
|
||||
m_EventAudioSource.PlayOneShot(SoundProfile.InteractSounds[Random.Range(0, SoundProfile.InteractSounds.Count)].SoundEffectClip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a sound effect from the AI's General Sounds list using the Sound Effect ID as the parameter.
|
||||
/// </summary>
|
||||
public void PlaySoundEffect(int SoundEffectID)
|
||||
{
|
||||
if (SoundProfile.InteractSounds.Count > 0)
|
||||
{
|
||||
for (int i = 0; i < SoundProfile.InteractSounds.Count; i++)
|
||||
{
|
||||
if (SoundProfile.InteractSounds[i].SoundEffectID == SoundEffectID)
|
||||
{
|
||||
if (!m_AudioSource.isPlaying)
|
||||
{
|
||||
m_AudioSource.volume = 1;
|
||||
m_AudioSource.PlayOneShot(SoundProfile.InteractSounds[i].SoundEffectClip);
|
||||
}
|
||||
else if (!m_SecondaryAudioSource.isPlaying)
|
||||
{
|
||||
m_SecondaryAudioSource.volume = 1;
|
||||
m_SecondaryAudioSource.PlayOneShot(SoundProfile.InteractSounds[i].SoundEffectClip);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_EventAudioSource.volume = 1;
|
||||
m_EventAudioSource.PlayOneShot(SoundProfile.InteractSounds[i].SoundEffectClip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b69db04fa6da5fc498d3d30b4df09c69
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user