update 1.2.5

This commit is contained in:
2025-07-20 10:18:13 +08:00
parent 5396d3e4b8
commit 9d22e1c192
17 changed files with 313 additions and 115 deletions
@@ -11,7 +11,9 @@ namespace EmeraldAI.SoundDetection.Utility
GUIStyle FoldoutStyle;
Texture SoundDetectionEditorIcon;
SerializedProperty CheckIncrementProp;
SerializedProperty TargetVelocityFactorProp;
SerializedProperty FalloffDealyProp;
SerializedProperty DistanceFactorProp;
SerializedProperty MinVelocityThresholdProp;
SerializedProperty AttentionRateProp;
SerializedProperty AttentionFalloffProp;
@@ -36,7 +38,9 @@ namespace EmeraldAI.SoundDetection.Utility
{
if (SoundDetectionEditorIcon == null) SoundDetectionEditorIcon = Resources.Load("Editor Icons/EmeraldSoundDetector") as Texture;
CheckIncrementProp = serializedObject.FindProperty("CheckIncrement");
TargetVelocityFactorProp = serializedObject.FindProperty("TargetVelocityFactor");
FalloffDealyProp = serializedObject.FindProperty("FalloffDealy");
DistanceFactorProp = serializedObject.FindProperty("DistanceFactor");
MinVelocityThresholdProp = serializedObject.FindProperty("MinVelocityThreshold");
AttentionRateProp = serializedObject.FindProperty("AttentionRate");
AttentionFalloffProp = serializedObject.FindProperty("AttentionFalloff");
@@ -98,23 +102,29 @@ namespace EmeraldAI.SoundDetection.Utility
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
EditorGUILayout.HelpBox("AI will only listen for player targets they have an Enemy Relation Type with. 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);
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), MinVelocityThresholdProp, "Min Velocity Threshold", 0f, 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(), TargetVelocityFactorProp, "Target Velocity Factor", 0f, 1f);
CustomHelpLabelField("Controls how much a detected target's velocity influences the Attention Rate.", true);
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), DistanceFactorProp, "Target Distance Factor", 0f, 1f);
CustomHelpLabelField("Controls how much a detected target's distance influences the Attention Rate. The closer the target is, the more it will affect the Attention Rate.", true);
CustomEditorProperties.CustomFloatSlider(new Rect(), new GUIContent(), AttentionFalloffProp, "Attention Falloff", 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(), FalloffDealyProp, "Falloff Delay", 0, 10f);
CustomHelpLabelField("Controls how many seconds must pass before the Attention Falloff happens. This will only happen after 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);
@@ -31,6 +31,7 @@ namespace EmeraldAI.SoundDetection.Utility
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 SetLoudestTargetAsCombatTargetInfo = "Sets the loudest target as the AI's current target and puts the AI into Combat Mode. This bypasses the AI needing to see the target and will move to their position to attack them.";
string NoneInfo = "A None reaction is the default reaction. Nothing will happen when this reaction is triggered.";
private void OnEnable()
@@ -224,6 +225,12 @@ namespace EmeraldAI.SoundDetection.Utility
new GUIContent(" ", FleeFromLoudestTargetInfo));
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.One;
}
else if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.SetLoudestTargetAsCombatTarget)
{
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 11, rect.width, EditorGUIUtility.singleLineHeight),
new GUIContent(" ", SetLoudestTargetAsCombatTargetInfo));
element.FindPropertyRelative("ElementLineHeight").intValue = (int)Reaction.ElementLineHeights.One;
}
//Two line elements
else if ((ReactionTypes)element.FindPropertyRelative("ReactionType").intValue == ReactionTypes.DebugLogMessage)
@@ -15,12 +15,13 @@ namespace EmeraldAI.SoundDetection
#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 MinVelocityThreshold = 0.5f;
public float TargetVelocityFactor = 0.3f;
public float DistanceFactor = 0.1f;
public float AttentionFalloff = 0.05f;
public float FalloffDealy = 3f;
public float DelayUnawareSeconds = 5f;
public float AttractModifierCooldown = 5;
public bool MovingTargetDetected;
@@ -48,15 +49,16 @@ namespace EmeraldAI.SoundDetection
//Private variables
float DelayUnawareTimer = 0;
float CheckIncrementTimer = 0;
EmeraldSystem EmeraldComponent;
EmeraldDetection EmeraldDetection;
EmeraldMovement EmeraldMovement;
bool ArrivedAtDestination;
Coroutine CurrentReactionCoroutine;
Coroutine CalculateMovementCoroutine;
Coroutine RotateTowardsCoroutine;
float TimeSinceLastAttractModifier;
bool SoundDetectorEnabled = true;
float FalloffDelayTimer;
[SerializeField]
public List<TargetDataClass> CurrentTargetData = new List<TargetDataClass>();
@@ -78,6 +80,7 @@ namespace EmeraldAI.SoundDetection
NoiseLevel = m_NoiseLevel;
}
}
#endregion
#region Editor Variables
@@ -89,6 +92,14 @@ namespace EmeraldAI.SoundDetection
#endregion
void Start()
{
InitializeSoundDetector();
}
/// <summary>
/// Initializes the Sound Detector script.
/// </summary>
void InitializeSoundDetector ()
{
CurrentThreatAmount = 0;
TimeSinceLastAttractModifier = AttractModifierCooldown;
@@ -126,24 +137,18 @@ namespace EmeraldAI.SoundDetection
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++)
{
//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 (!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));
}
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;
}
UpdateTargetData();
}
private void Update()
@@ -153,11 +158,32 @@ namespace EmeraldAI.SoundDetection
if (!EmeraldComponent.AnimationComponent.IsDead && SoundDetectorEnabled)
{
TimeSinceLastAttractModifier += Time.deltaTime;
if (EmeraldDetection.LineOfSightTargets.Count > 0 || CurrentThreatLevel != ThreatLevels.Unaware)
if (EmeraldDetection.LineOfSightTargets.Count > 0)
{
CheckForSounds();
CheckEvents();
CalculateThreatLevel();
}
if (CurrentThreatAmount > 0 && EmeraldDetection.LineOfSightTargets.Count == 0)
{
if (CurrentTargetData.Count > 0)
{
SuspiciousTriggered = false;
AwareTriggered = false;
CurrentTargetData.Clear();
}
FalloffDelayTimer += Time.deltaTime; //Allow a brief resting period between when a moving target is detected and when it's not.
if (FalloffDelayTimer > FalloffDealy) CurrentThreatAmount = Mathf.MoveTowards(CurrentThreatAmount, 0, AttentionFalloff * Time.deltaTime);
CurrentThreatAmount = Mathf.Clamp(CurrentThreatAmount, 0f, 1f);
if (CurrentThreatAmount < 0.001f)
{
InvokeReactionList(UnawareReaction);
UnawareEvent.Invoke();
DelayUnawareTimer = 0;
ClearThreats();
}
}
}
}
@@ -169,20 +195,25 @@ namespace EmeraldAI.SoundDetection
{
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 (CurrentTargetData[i].Target != null)
{
//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 / Time.deltaTime;
float MitigationValue = (1f - Mathf.Clamp01(DistanceFromTarget / EmeraldComponent.DetectionComponent.DetectionRadius));
if (TargetVelocity >= MinVelocityThreshold)
{
MovingTargetDetected = true;
}
else if (CurrentThreatAmount > 0 && TargetVelocity < MinVelocityThreshold)
{
MovingTargetDetected = false;
CurrentTargetData[i].LastPosition = CurrentTargetData[i].Target.position;
CurrentTargetData[i].NoiseLevel = TargetVelocity;
CalculateThreatLevel(TargetVelocity, MitigationValue);
if (TargetVelocity > MinVelocityThreshold)
{
MovingTargetDetected = true;
}
else if (CurrentThreatAmount > 0 && TargetVelocity <= MinVelocityThreshold)
{
MovingTargetDetected = false;
}
}
}
}
@@ -190,15 +221,20 @@ namespace EmeraldAI.SoundDetection
/// <summary>
/// Simply increases or descreases the CurrentThreatLevel depending on whether or not detected targets are moving.
/// </summary>
void CalculateThreatLevel ()
void CalculateThreatLevel (float CurrentTargetVelocity, float MitigationValue)
{
if (MovingTargetDetected)
{
CurrentThreatAmount += Time.deltaTime * AttentionRate;
FalloffDelayTimer = 0;
float attentionEffect = AttentionRate * Time.deltaTime;
attentionEffect += (TargetVelocityFactor * CurrentTargetVelocity * 0.01f) * Time.deltaTime;
attentionEffect += (DistanceFactor * MitigationValue) * Time.deltaTime;
CurrentThreatAmount = Mathf.MoveTowards(CurrentThreatAmount, 1, attentionEffect);
}
else
{
CurrentThreatAmount -= Time.deltaTime * AttentionFalloff;
FalloffDelayTimer += Time.deltaTime; //Allow a brief resting period between when a moving target is detected and when it's not.
if (FalloffDelayTimer > FalloffDealy) CurrentThreatAmount = Mathf.MoveTowards(CurrentThreatAmount, 0, AttentionFalloff * Time.deltaTime);
}
CurrentThreatAmount = Mathf.Clamp(CurrentThreatAmount, 0f, 1f);
@@ -209,7 +245,11 @@ namespace EmeraldAI.SoundDetection
/// </summary>
void CancelAll ()
{
StopAllCoroutines();
if (CurrentReactionCoroutine != null) StopCoroutine(CurrentReactionCoroutine);
if (CalculateMovementCoroutine != null) StopCoroutine(CalculateMovementCoroutine);
if (RotateTowardsCoroutine != null) StopCoroutine(RotateTowardsCoroutine);
EmeraldComponent.MovementComponent.RotateTowardsTarget = false;
EmeraldComponent.m_NavMeshAgent.isStopped = false;
CurrentTargetData.Clear();
EmeraldComponent.MovementComponent.ChangeWanderType((EmeraldMovement.WanderTypes)EmeraldMovement.StartingWanderingType);
}
@@ -314,7 +354,7 @@ namespace EmeraldAI.SoundDetection
//Ensure the AI is using its Starting WanderType.
EmeraldComponent.MovementComponent.ChangeWanderType((EmeraldMovement.WanderTypes)EmeraldMovement.StartingWanderingType);
if (CurrentReactionCoroutine != null) { StopAllCoroutines(); }
if (CurrentReactionCoroutine != null) { StopCoroutine(CurrentReactionCoroutine); }
CurrentReactionCoroutine = StartCoroutine(InvokeReactionListInternal(SentReactionObject, SentByAttractModifier));
}
@@ -354,6 +394,11 @@ namespace EmeraldAI.SoundDetection
else if (SentReactionObject.ReactionList[i].ReactionType == ReactionTypes.LookAtLoudestTarget)
{
LookAtLoudestTarget();
yield return new WaitForSeconds(SentReactionObject.ReactionList[i].IntValue1);
}
else if (SentReactionObject.ReactionList[i].ReactionType == ReactionTypes.SetLoudestTargetAsCombatTarget)
{
SetLoudestTargetAsCombatTarget();
}
else if (SentReactionObject.ReactionList[i].ReactionType == ReactionTypes.ReturnToStartingPosition)
{
@@ -486,7 +531,16 @@ namespace EmeraldAI.SoundDetection
if (CurrentTargetData.Count == 0)
return;
EmeraldComponent.LookAtTarget = GetLoudestTarget(); //Assign the loudest detected target as the Look At Target.
if (RotateTowardsCoroutine != null) StopCoroutine(RotateTowardsCoroutine);
RotateTowardsCoroutine = StartCoroutine(RotateTowardsPosition(GetLoudestTarget().position)); //Have the AI rotate towards the loudest position
}
/// <summary>
/// Sets the loudest target as the AI's current target.
/// </summary>
public void SetLoudestTargetAsCombatTarget()
{
EmeraldAPI.Combat.SetCombatTarget(EmeraldComponent, GetLoudestTarget());
}
/// <summary>
@@ -659,6 +713,27 @@ namespace EmeraldAI.SoundDetection
EmeraldComponent.MovementComponent.ChangeWanderType((EmeraldMovement.WanderTypes)EmeraldMovement.StartingWanderingType);
}
IEnumerator RotateTowardsPosition(Vector3 TargetPosition)
{
if (EmeraldComponent.MovementComponent.RotateTowardsTarget) yield break; //Don't allow the AI to rotate towards the TargetPosition if it's already doing so
EmeraldComponent.MovementComponent.RotateTowardsTarget = true; //Stops certain functionality while in this state
EmeraldComponent.MovementComponent.LockTurning = false; //This is used for preventing an AI from getting stuck between two turn animations
EmeraldComponent.m_NavMeshAgent.isStopped = true; //Stop the AI's NavMesh
yield return new WaitForSeconds(0.1f); //Add a delay so the changes above aren't missed
while (!EmeraldComponent.MovementComponent.LockTurning)
{
Vector3 Direction = new Vector3(TargetPosition.x, 0, TargetPosition.z) - new Vector3(EmeraldComponent.transform.position.x, 0, EmeraldComponent.transform.position.z);
EmeraldComponent.MovementComponent.UpdateRotations(Direction);
EmeraldComponent.AnimationComponent.CalculateTurnAnimations(true);
yield return null;
}
EmeraldComponent.MovementComponent.RotateTowardsTarget = false;
EmeraldComponent.m_NavMeshAgent.isStopped = false;
}
/// <summary>
/// Sets the AI's flee target as the loudest detected target. (Cautious Coward AI Only)
/// </summary>
@@ -14,12 +14,13 @@
MoveAroundCurrentPosition = 200,
MoveAroundLoudestTarget = 225,
MoveToLoudestTarget = 250,
SetLoudestTargetAsCombatTarget = 260,
PlayEmoteAnimation = 275,
PlaySound = 300,
ResetAllToDefault = 325,
ResetDetectionDistance = 350,
ResetLookAtPosition = 375,
ReturnToStartingPosition = 400,
SetMovementState = 425,
SetMovementState = 425,
}
}