init 1.1.2

This commit is contained in:
2025-07-20 10:01:29 +08:00
commit 2afbbf9be4
1327 changed files with 1596159 additions and 0 deletions
@@ -0,0 +1,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();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c6ced538672e8c845aa706ace73da55a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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;
}
}
}
@@ -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;
}
}
}
}
@@ -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:
@@ -0,0 +1,9 @@
namespace EmeraldAI.SoundDetection
{
public enum AttractModifierReactionTypes
{
LookAtAttractSource = 0,
MoveAroundAttractSource = 25,
MoveToAttractSource = 50,
}
}
@@ -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: