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,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: