magic attacks with autotargetting
This commit is contained in:
@@ -6,13 +6,14 @@ using Invector;
|
|||||||
using Invector.vCharacterController;
|
using Invector.vCharacterController;
|
||||||
using Invector.vCharacterController.vActions;
|
using Invector.vCharacterController.vActions;
|
||||||
using Sirenix.OdinInspector;
|
using Sirenix.OdinInspector;
|
||||||
using UnityEditor;
|
// using UnityEditor; // Best to remove if not strictly needed for runtime
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Events;
|
using UnityEngine.Events;
|
||||||
using UnityEngine.VFX;
|
using UnityEngine.VFX;
|
||||||
using static Invector.vObjectDamage;
|
// using static Invector.vObjectDamage; // Not used directly, consider removing
|
||||||
using DG.Tweening;
|
using DG.Tweening;
|
||||||
using Invector.vCharacterController.AI.FSMBehaviour;
|
using Invector.vCharacterController.AI.FSMBehaviour;
|
||||||
|
using Beyond; // For Player, GameStateManager, AutoTargetting (if Player.Instance.AutoTarget is of this type)
|
||||||
|
|
||||||
namespace Beyond
|
namespace Beyond
|
||||||
{
|
{
|
||||||
@@ -50,35 +51,56 @@ namespace Beyond
|
|||||||
private Coroutine lastPushRoutine = null;
|
private Coroutine lastPushRoutine = null;
|
||||||
private ParticleSystem flame;
|
private ParticleSystem flame;
|
||||||
private BoxCollider flameDamager;
|
private BoxCollider flameDamager;
|
||||||
private bLockOn lockOn;
|
private bLockOn lockOn; // Retained for fallback aiming or non-combat interactions if needed
|
||||||
private const float fireballAimerThreshold = -1.0f;
|
private const float fireballAimerThreshold = -1.0f; // Used for fallback aiming if no auto-target
|
||||||
private const float fireballAimerHeightAdjuster = 0.1f;
|
private const float fireballAimerHeightAdjuster = 0.1f;
|
||||||
private const float fireballDamagerDuration = 0.3f;
|
private const float fireballDamagerDuration = 0.3f;
|
||||||
private const float fireballTargetYPositionOffset = 0.75f;
|
private const float fireballTargetYPositionOffset = 0.75f;
|
||||||
private const int spellLayerIndex = 5;
|
private const int spellLayerIndex = 5;
|
||||||
private EffectDesc shield;
|
private EffectDesc shield;
|
||||||
private EffectDesc silentPeek; //called covert gaze now
|
private EffectDesc silentPeek;
|
||||||
private ShieldEffectController shieldEffectController;
|
private ShieldEffectController shieldEffectController;
|
||||||
private ShieldCollisionController shieldCollisionController;
|
private ShieldCollisionController shieldCollisionController;
|
||||||
private bool canPlayNoFaithClip = true;
|
private bool canPlayNoFaithClip = true;
|
||||||
private bool canPlayCantDoClip = true;
|
private bool canPlayCantDoClip = true;
|
||||||
|
|
||||||
|
// NEW: AutoTargetting fields
|
||||||
|
private AutoTargetting _autoTargettingInstance;
|
||||||
|
|
||||||
[BoxGroup("Auto targetting")]
|
[BoxGroup("Auto targetting")]
|
||||||
|
[Tooltip("Enable to use AutoTargetting for player rotation and spell aiming.")]
|
||||||
|
public bool enableAutoTargetIntegration = true;
|
||||||
|
[BoxGroup("Auto targetting")]
|
||||||
|
[Tooltip("Max distance for player to turn towards an auto-target during spell casting.")]
|
||||||
public float maxTurnTowardDistance = 10f;
|
public float maxTurnTowardDistance = 10f;
|
||||||
[BoxGroup("Auto targetting")]
|
[BoxGroup("Auto targetting")]
|
||||||
|
[Tooltip("Rotation speed when turning towards an auto-target.")]
|
||||||
public float rotationSpeed = 500f;
|
public float rotationSpeed = 500f;
|
||||||
[BoxGroup("Auto targetting")]
|
[BoxGroup("Auto targetting")]
|
||||||
public float degreeThreshold = 100;
|
[Tooltip("Angle threshold within which the player will rotate towards an auto-target.")]
|
||||||
|
public float degreeThreshold = 100f;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public UnityAction<Collider> onHitFireball;
|
public UnityAction<Collider> onHitFireball;
|
||||||
|
|
||||||
// Start is called before the first frame update
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
tpInput = GetComponent<bThirdPersonInput>();
|
tpInput = GetComponent<bThirdPersonInput>();
|
||||||
|
|
||||||
|
// NEW: Initialize AutoTargetting instance
|
||||||
|
if (Player.Instance != null)
|
||||||
|
{
|
||||||
|
_autoTargettingInstance = Player.Instance.AutoTarget; // Assuming Player.Instance has an AutoTarget property of type AutoTargetting
|
||||||
|
if (_autoTargettingInstance == null && enableAutoTargetIntegration)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("MagicAttacks: AutoTargetting component not found on Player.Instance.AutoTarget, but enableAutoTargetIntegration is true. Targeting features will be limited.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogError("MagicAttacks: Player.Instance is null in Awake. Cannot get AutoTargetting component.");
|
||||||
|
}
|
||||||
|
lockOn = GetComponent<bLockOn>(); // Keep for potential fallback
|
||||||
|
|
||||||
EffectDesc mpush = m_effects[(int)EffectType.MAGIC_PUSH];
|
EffectDesc mpush = m_effects[(int)EffectType.MAGIC_PUSH];
|
||||||
EffectDesc flameThrowe = m_effects[(int)EffectType.FLAME_THROWER];
|
EffectDesc flameThrowe = m_effects[(int)EffectType.FLAME_THROWER];
|
||||||
EffectDesc scan = m_effects[(int)EffectType.SCAN];
|
EffectDesc scan = m_effects[(int)EffectType.SCAN];
|
||||||
@@ -122,14 +144,12 @@ namespace Beyond
|
|||||||
|
|
||||||
flame = flameThrowe.effectObject.GetComponent<ParticleSystem>();
|
flame = flameThrowe.effectObject.GetComponent<ParticleSystem>();
|
||||||
flameDamager = flameThrowe.effectObject.GetComponentInChildren<BoxCollider>();
|
flameDamager = flameThrowe.effectObject.GetComponentInChildren<BoxCollider>();
|
||||||
lockOn = GetComponent<bLockOn>();
|
|
||||||
|
|
||||||
silentPeek.del = OnSilentPeek;
|
silentPeek.del = OnSilentPeek;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDisable()
|
private void OnDisable()
|
||||||
{
|
{
|
||||||
//for example if you enter cutsene, it should be disabled when we are back to the game
|
|
||||||
if (shieldAnimationIsActive)
|
if (shieldAnimationIsActive)
|
||||||
{
|
{
|
||||||
DisableShield();
|
DisableShield();
|
||||||
@@ -141,6 +161,7 @@ namespace Beyond
|
|||||||
canPlayNoFaithClip = true;
|
canPlayNoFaithClip = true;
|
||||||
canPlayCantDoClip = true;
|
canPlayCantDoClip = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Button]
|
[Button]
|
||||||
private void OnSilentPeek()
|
private void OnSilentPeek()
|
||||||
{
|
{
|
||||||
@@ -166,7 +187,6 @@ namespace Beyond
|
|||||||
}
|
}
|
||||||
SilentPeekController.instance.SetActive(true, powersArea.equipSlots[equipAreaSelectedIndex].item);
|
SilentPeekController.instance.SetActive(true, powersArea.equipSlots[equipAreaSelectedIndex].item);
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return null;
|
yield return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,14 +202,14 @@ namespace Beyond
|
|||||||
private IEnumerator MagicPushCoroutine()
|
private IEnumerator MagicPushCoroutine()
|
||||||
{
|
{
|
||||||
EffectDesc mpush = m_effects[(int)EffectType.MAGIC_PUSH];
|
EffectDesc mpush = m_effects[(int)EffectType.MAGIC_PUSH];
|
||||||
yield return new WaitForSeconds(mpush.startTime);
|
// MODIFIED: Use TurnTowardTargetCoroutine for potential rotation during wind-up
|
||||||
|
yield return TurnTowardTargetCoroutine(mpush.startTime);
|
||||||
mpush.effectObject.SetActive(false);
|
mpush.effectObject.SetActive(false);
|
||||||
mpush.effectObject.SetActive(true);
|
mpush.effectObject.SetActive(true);
|
||||||
yield return new WaitForSeconds(mpush.delay);
|
yield return new WaitForSeconds(mpush.delay);
|
||||||
Debug.Log("Bum!");
|
Debug.Log("Bum!"); // Consider replacing with actual effect logic
|
||||||
yield return new WaitForSeconds(mpush.endTime);
|
yield return new WaitForSeconds(mpush.endTime); // This is endTime after delay, might be confusing.
|
||||||
mpush.effectObject.SetActive(false);
|
mpush.effectObject.SetActive(false);
|
||||||
|
|
||||||
yield return null;
|
yield return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,15 +221,14 @@ namespace Beyond
|
|||||||
private IEnumerator FlameThrowerhCoroutine()
|
private IEnumerator FlameThrowerhCoroutine()
|
||||||
{
|
{
|
||||||
EffectDesc flameThrowe = m_effects[(int)EffectType.FLAME_THROWER];
|
EffectDesc flameThrowe = m_effects[(int)EffectType.FLAME_THROWER];
|
||||||
//yield return new WaitForSeconds(flameThrowe.startTime);
|
// MODIFIED: TurnTowardTargetCoroutine will handle rotation based on AutoTargetting
|
||||||
yield return TurnTowardTargetCoroutine(flameThrowe.startTime);
|
yield return TurnTowardTargetCoroutine(flameThrowe.startTime);
|
||||||
flameDamager.enabled = true;
|
flameDamager.enabled = true;
|
||||||
flame.Play();
|
flame.Play();
|
||||||
yield return new WaitForSeconds(flameThrowe.endTime);
|
yield return new WaitForSeconds(flameThrowe.endTime); // Duration of flame
|
||||||
flame.Stop();
|
flame.Stop();
|
||||||
yield return new WaitForSeconds(flameThrowe.delay);
|
yield return new WaitForSeconds(flameThrowe.delay); // Cooldown/after-effect
|
||||||
flameDamager.enabled = false;
|
flameDamager.enabled = false;
|
||||||
|
|
||||||
yield return null;
|
yield return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,10 +241,11 @@ namespace Beyond
|
|||||||
private IEnumerator ScanCoroutine()
|
private IEnumerator ScanCoroutine()
|
||||||
{
|
{
|
||||||
EffectDesc scan = m_effects[(int)EffectType.SCAN];
|
EffectDesc scan = m_effects[(int)EffectType.SCAN];
|
||||||
|
// Scan might not need rotation, but if it had a wind-up animation, TurnTowardTargetCoroutine could be used.
|
||||||
yield return new WaitForSeconds(scan.startTime);
|
yield return new WaitForSeconds(scan.startTime);
|
||||||
float time = scan.startTime - scan.delay;
|
float time = scan.startTime - scan.delay; // This calculation seems off if delay is for after effect. Assuming startTime is actual start.
|
||||||
float maxRange = 50f;
|
float maxRange = 50f;
|
||||||
float speed = maxRange / (scan.endTime - scan.startTime);
|
float speed = maxRange / (scan.endTime - scan.startTime); // scan.endTime is duration here
|
||||||
int mask = 1 << LayerMask.NameToLayer("Triggers") | 1 << LayerMask.NameToLayer("HiddenObject");
|
int mask = 1 << LayerMask.NameToLayer("Triggers") | 1 << LayerMask.NameToLayer("HiddenObject");
|
||||||
if (scan.effectObject)
|
if (scan.effectObject)
|
||||||
{
|
{
|
||||||
@@ -233,13 +253,16 @@ namespace Beyond
|
|||||||
VisualEffect effect = scan.effectObject.GetComponent<VisualEffect>();
|
VisualEffect effect = scan.effectObject.GetComponent<VisualEffect>();
|
||||||
effect.Play();
|
effect.Play();
|
||||||
}
|
}
|
||||||
while (time < scan.endTime - scan.delay)
|
float waveEffectTimer = 0f;
|
||||||
|
float waveEffectDuration = scan.endTime - scan.startTime;
|
||||||
|
|
||||||
|
while (waveEffectTimer < waveEffectDuration)
|
||||||
{
|
{
|
||||||
Shader.SetGlobalFloat("_WaveTime", speed * (time - scan.startTime));
|
Shader.SetGlobalFloat("_WaveTime", speed * waveEffectTimer); // Use timer relative to effect start
|
||||||
time += Time.deltaTime;
|
waveEffectTimer += Time.deltaTime;
|
||||||
yield return null;
|
yield return null;
|
||||||
}
|
}
|
||||||
Shader.SetGlobalFloat("_WaveTime", 0f);
|
Shader.SetGlobalFloat("_WaveTime", 0f); // Reset shader global
|
||||||
var colliders = Physics.OverlapSphere(transform.position, maxRange, mask);
|
var colliders = Physics.OverlapSphere(transform.position, maxRange, mask);
|
||||||
foreach (var c in colliders)
|
foreach (var c in colliders)
|
||||||
{
|
{
|
||||||
@@ -247,8 +270,7 @@ namespace Beyond
|
|||||||
if (h != null)
|
if (h != null)
|
||||||
h.OnScanned();
|
h.OnScanned();
|
||||||
}
|
}
|
||||||
scan.effectObject.SetActive(false);
|
if (scan.effectObject) scan.effectObject.SetActive(false); // Deactivate after use
|
||||||
|
|
||||||
yield return null;
|
yield return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,89 +279,128 @@ namespace Beyond
|
|||||||
StartCoroutine(FireballCoroutine());
|
StartCoroutine(FireballCoroutine());
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerator TurnTowardTargetCoroutine(float maxTime)
|
// MODIFIED: Centralized coroutine for turning towards target during spell animations
|
||||||
|
private IEnumerator TurnTowardTargetCoroutine(float maxDuration)
|
||||||
{
|
{
|
||||||
float time = 0;
|
if (!enableAutoTargetIntegration || _autoTargettingInstance == null)
|
||||||
while (time < maxTime)
|
|
||||||
{
|
{
|
||||||
time += Time.deltaTime;
|
// If auto-targeting is off or unavailable, just wait for the duration without rotation.
|
||||||
LerpRotation();
|
if (maxDuration > 0) yield return new WaitForSeconds(maxDuration);
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
float timeElapsed = 0;
|
||||||
|
while (timeElapsed < maxDuration)
|
||||||
|
{
|
||||||
|
if (_autoTargettingInstance.CurrentTarget != null)
|
||||||
|
{
|
||||||
|
vFSMBehaviourController currentTarget = _autoTargettingInstance.CurrentTarget;
|
||||||
|
Transform playerTransform = transform; // Character's transform
|
||||||
|
|
||||||
|
float distSqr = (currentTarget.transform.position - playerTransform.position).sqrMagnitude;
|
||||||
|
|
||||||
|
// Check distance using MagicAttacks.maxTurnTowardDistance
|
||||||
|
if (distSqr <= maxTurnTowardDistance * maxTurnTowardDistance)
|
||||||
|
{
|
||||||
|
// Check angle using MagicAttacks.degreeThreshold and AutoTargetting's utility
|
||||||
|
if (_autoTargettingInstance.IsTargetInAngle(playerTransform, currentTarget, degreeThreshold))
|
||||||
|
{
|
||||||
|
Vector3 directionToTarget = currentTarget.transform.position - playerTransform.position;
|
||||||
|
directionToTarget.y = 0f; // Horizontal rotation only
|
||||||
|
|
||||||
|
if (directionToTarget.sqrMagnitude > 0.0001f) // Ensure there's a direction
|
||||||
|
{
|
||||||
|
Quaternion targetRotation = Quaternion.LookRotation(directionToTarget.normalized);
|
||||||
|
// Use MagicAttacks.rotationSpeed for the rotation
|
||||||
|
playerTransform.rotation = Quaternion.RotateTowards(playerTransform.rotation, targetRotation, Time.deltaTime * rotationSpeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timeElapsed += Time.deltaTime;
|
||||||
yield return null;
|
yield return null;
|
||||||
}
|
}
|
||||||
yield return null; }
|
}
|
||||||
|
|
||||||
private IEnumerator FireballCoroutine()
|
private IEnumerator FireballCoroutine()
|
||||||
{
|
{
|
||||||
EffectDesc fireball = m_effects[(int)EffectType.FIREBALL];
|
EffectDesc fireball = m_effects[(int)EffectType.FIREBALL];
|
||||||
//yield return new WaitForSeconds(fireball.startTime);
|
// MODIFIED: Use new TurnTowardTargetCoroutine
|
||||||
yield return TurnTowardTargetCoroutine(fireball.startTime);
|
yield return TurnTowardTargetCoroutine(fireball.startTime);
|
||||||
|
|
||||||
var fireballClone = Instantiate(fireball.effectObject, fireball.effectObject.transform.position, fireball.effectObject.transform.rotation);
|
var fireballClone = Instantiate(fireball.effectObject, fireball.effectObject.transform.position, fireball.effectObject.transform.rotation);
|
||||||
fireballClone.SetActive(true);
|
fireballClone.SetActive(true);
|
||||||
RFX4_PhysicsMotion fireballMotionController = fireballClone.GetComponentInChildren<RFX4_PhysicsMotion>(); //could maybe add some reference container to use get component without children
|
RFX4_PhysicsMotion fireballMotionController = fireballClone.GetComponentInChildren<RFX4_PhysicsMotion>();
|
||||||
fireballMotionController.CollisionEnter += EnableBrieflyFireballDamager; //have to do it on each instantiate, since it cant be serialized
|
if (fireballMotionController != null)
|
||||||
if (onHitFireball != null)
|
|
||||||
{
|
{
|
||||||
fireballClone.GetComponentInChildren<vObjectDamage>().onHit.AddListener(onHitFireball);
|
fireballMotionController.CollisionEnter += EnableBrieflyFireballDamager;
|
||||||
}
|
}
|
||||||
TryToAimToNearbyEnemy(fireballClone, lockOn);
|
vObjectDamage fireballDamageComponent = fireballClone.GetComponentInChildren<vObjectDamage>();
|
||||||
Destroy(fireballClone, 10f);
|
if (fireballDamageComponent != null && onHitFireball != null)
|
||||||
|
{
|
||||||
|
fireballDamageComponent.onHit.AddListener(onHitFireball);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MODIFIED: Use new AimFireball method
|
||||||
|
AimFireball(fireballClone);
|
||||||
|
|
||||||
|
Destroy(fireballClone, 10f); // Self-destruct after time
|
||||||
yield return null;
|
yield return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnableBrieflyFireballDamager(object sender, RFX4_PhysicsMotion.RFX4_CollisionInfo e)
|
private void EnableBrieflyFireballDamager(object sender, RFX4_PhysicsMotion.RFX4_CollisionInfo e)
|
||||||
{
|
{
|
||||||
RFX4_PhysicsMotion rFX4_PhysicsMotion = (RFX4_PhysicsMotion)sender;
|
RFX4_PhysicsMotion rFX4_PhysicsMotion = (RFX4_PhysicsMotion)sender;
|
||||||
CapsuleCollider collider = rFX4_PhysicsMotion.GetComponentInChildren<CapsuleCollider>();
|
CapsuleCollider collider = rFX4_PhysicsMotion.GetComponentInChildren<CapsuleCollider>(); // Assuming damager is a CapsuleCollider
|
||||||
StartCoroutine(EnableBrieflyFireballDamagerCoroutine(collider));
|
if(collider != null) StartCoroutine(EnableBrieflyFireballDamagerCoroutine(collider));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TryToAimToNearbyEnemy(GameObject fireballClone, bLockOn lockOn)
|
// NEW: Refactored fireball aiming logic
|
||||||
|
private void AimFireball(GameObject fireballClone)
|
||||||
{
|
{
|
||||||
if (lockOn.isLockingOn && lockOn.currentTarget != null)
|
Vector3 aimDirection = transform.forward; // Default aim is player's forward
|
||||||
|
|
||||||
|
if (enableAutoTargetIntegration && _autoTargettingInstance != null && _autoTargettingInstance.CurrentTarget != null)
|
||||||
|
{
|
||||||
|
vFSMBehaviourController autoTarget = _autoTargettingInstance.CurrentTarget;
|
||||||
|
Vector3 targetPosition = autoTarget.transform.position;
|
||||||
|
targetPosition.y += fireballTargetYPositionOffset; // Adjust height for aiming
|
||||||
|
aimDirection = (targetPosition - fireballClone.transform.position).normalized;
|
||||||
|
}
|
||||||
|
else if (lockOn != null && lockOn.isLockingOn && lockOn.currentTarget != null) // Fallback to bLockOn target
|
||||||
{
|
{
|
||||||
Vector3 targetPosition = lockOn.currentTarget.position;
|
Vector3 targetPosition = lockOn.currentTarget.position;
|
||||||
targetPosition.y += fireballTargetYPositionOffset; //because it gets point on the ground
|
|
||||||
Vector3 forward = (fireballClone.transform.TransformDirection(Vector3.forward)).normalized;
|
|
||||||
Vector3 toOther = (targetPosition - fireballClone.transform.position).normalized;
|
|
||||||
//rotate player toword
|
|
||||||
transform.rotation = Quaternion.LookRotation(toOther);
|
|
||||||
fireballClone.transform.rotation = Quaternion.LookRotation(new Vector3(toOther.x, toOther.y + fireballAimerHeightAdjuster, toOther.z));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Transform> closeEnemies = lockOn.GetNearbyTargets(); //is already nicely sorted
|
|
||||||
|
|
||||||
for (int i = 0; i < closeEnemies.Count; i++)
|
|
||||||
{
|
|
||||||
Vector3 targetPosition = closeEnemies[i].position;
|
|
||||||
targetPosition.y += fireballTargetYPositionOffset;
|
targetPosition.y += fireballTargetYPositionOffset;
|
||||||
Vector3 forward = transform.forward;//(fireballClone.transform.TransformDirection(Vector3.forward)).normalized;
|
aimDirection = (targetPosition - fireballClone.transform.position).normalized;
|
||||||
Vector3 toOther = (targetPosition - fireballClone.transform.position).normalized;
|
}
|
||||||
if (Vector3.Dot(forward, toOther) > fireballAimerThreshold)
|
else if (lockOn != null) // Fallback to nearest enemy in front (from bLockOn)
|
||||||
{
|
{
|
||||||
fireballClone.transform.rotation = Quaternion.LookRotation(new Vector3(toOther.x, toOther.y + fireballAimerHeightAdjuster, toOther.z));
|
List<Transform> closeEnemies = lockOn.GetNearbyTargets();
|
||||||
//toOther.y = 0f;
|
if (closeEnemies.Count > 0)
|
||||||
//transform.rotation = Quaternion.LookRotation(toOther);
|
{
|
||||||
i = closeEnemies.Count;
|
foreach (var enemyTransform in closeEnemies) // Find first suitable enemy in front
|
||||||
|
{
|
||||||
|
Vector3 targetPosition = enemyTransform.position;
|
||||||
|
targetPosition.y += fireballTargetYPositionOffset;
|
||||||
|
Vector3 directionToEnemy = (targetPosition - fireballClone.transform.position).normalized;
|
||||||
|
|
||||||
|
// Check if enemy is generally in front of the player (fireball origin)
|
||||||
|
if (Vector3.Dot(transform.forward, directionToEnemy) > fireballAimerThreshold)
|
||||||
|
{
|
||||||
|
aimDirection = directionToEnemy;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TryToTurnTowordsEnemy()
|
// Apply calculated aim direction to the fireball, adding vertical adjustment
|
||||||
|
Vector3 finalAimDirection = new Vector3(aimDirection.x, aimDirection.y + fireballAimerHeightAdjuster, aimDirection.z);
|
||||||
|
if (finalAimDirection.sqrMagnitude > 0.001f)
|
||||||
{
|
{
|
||||||
List<Transform> closeEnemies = lockOn.GetNearbyTargets(); //is already nicely sorted
|
fireballClone.transform.rotation = Quaternion.LookRotation(finalAimDirection.normalized);
|
||||||
|
|
||||||
for (int i = 0; i < closeEnemies.Count; i++)
|
|
||||||
{
|
|
||||||
Vector3 targetPosition = closeEnemies[i].position;
|
|
||||||
Vector3 forward = transform.forward;//(fireballClone.transform.TransformDirection(Vector3.forward)).normalized;
|
|
||||||
Vector3 toOther = (targetPosition - transform.position).normalized;
|
|
||||||
if (Vector3.Dot(forward, toOther) > fireballAimerThreshold)
|
|
||||||
{
|
|
||||||
toOther.y = 0f;
|
|
||||||
transform.rotation = Quaternion.LookRotation(toOther);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// If aimDirection is zero (shouldn't happen with defaults), it will use its instantiated rotation.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -352,24 +413,26 @@ namespace Beyond
|
|||||||
|
|
||||||
public void Shield()
|
public void Shield()
|
||||||
{
|
{
|
||||||
StopCoroutine(ShieldCoroutine());
|
StopCoroutine(ShieldCoroutine()); // Ensure only one shield coroutine runs
|
||||||
StartCoroutine(ShieldCoroutine());
|
StartCoroutine(ShieldCoroutine());
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerator ShieldCoroutine()
|
private IEnumerator ShieldCoroutine()
|
||||||
{
|
{
|
||||||
shieldAnimationIsActive = true;
|
shieldAnimationIsActive = true;
|
||||||
|
// Shield typically doesn't need offensive targeting/rotation.
|
||||||
|
// If there was a wind-up animation where player *should* face an enemy, TurnTowardTargetCoroutine could be used.
|
||||||
yield return new WaitForSeconds(shield.startTime);
|
yield return new WaitForSeconds(shield.startTime);
|
||||||
shieldEffectIsActive = true; //this makes the player immune to dmg
|
shieldEffectIsActive = true;
|
||||||
shieldEffectController.InitializeEffect();
|
shieldEffectController.InitializeEffect();
|
||||||
shield.effectObject.SetActive(true);
|
shield.effectObject.SetActive(true);
|
||||||
shieldCollisionController.shieldCollider.enabled = true;
|
shieldCollisionController.shieldCollider.enabled = true;
|
||||||
yield return new WaitForSeconds(shield.endTime);
|
yield return new WaitForSeconds(shield.endTime); // Duration shield is active
|
||||||
shieldEffectController.DisableEffect();
|
shieldEffectController.DisableEffect();
|
||||||
yield return new WaitForSeconds(shield.delay / 2f);
|
yield return new WaitForSeconds(shield.delay / 2f); // Fade out time part 1
|
||||||
shieldEffectIsActive = false;
|
shieldEffectIsActive = false;
|
||||||
shieldCollisionController.shieldCollider.enabled = false;
|
shieldCollisionController.shieldCollider.enabled = false;
|
||||||
yield return new WaitForSeconds(shield.delay / 2f);
|
yield return new WaitForSeconds(shield.delay / 2f); // Fade out time part 2
|
||||||
shield.effectObject.SetActive(false);
|
shield.effectObject.SetActive(false);
|
||||||
shieldAnimationIsActive = false;
|
shieldAnimationIsActive = false;
|
||||||
}
|
}
|
||||||
@@ -377,31 +440,31 @@ namespace Beyond
|
|||||||
private void DisableShield()
|
private void DisableShield()
|
||||||
{
|
{
|
||||||
shieldEffectIsActive = false;
|
shieldEffectIsActive = false;
|
||||||
|
if(shieldCollisionController != null && shieldCollisionController.shieldCollider != null)
|
||||||
shieldCollisionController.shieldCollider.enabled = false;
|
shieldCollisionController.shieldCollider.enabled = false;
|
||||||
shieldAnimationIsActive = false;
|
shieldAnimationIsActive = false;
|
||||||
|
if(shield != null && shield.effectObject != null)
|
||||||
shield.effectObject.SetActive(false);
|
shield.effectObject.SetActive(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Tooltip("Input to trigger the custom animation")]
|
[Tooltip("Input to trigger the custom animation")]
|
||||||
public GenericInput actionInput = new GenericInput("L", "L", "L");
|
public GenericInput actionInput = new GenericInput("L", "L", "L");
|
||||||
|
|
||||||
[Tooltip("Name of the animation clip")]
|
[Tooltip("Name of the animation clip")]
|
||||||
public string animationClip;
|
public string animationClip; // This will be set by selectedEffect.animClipName
|
||||||
|
|
||||||
[Tooltip("Where in the end of the animation will trigger the event OnEndAnimation")]
|
[Tooltip("Where in the end of the animation will trigger the event OnEndAnimation")]
|
||||||
public float animationEnd = 0.8f;
|
public float animationEnd = 0.8f;
|
||||||
|
|
||||||
public UnityEvent OnPlayAnimation;
|
public UnityEvent OnPlayAnimation; // Consider if this is still needed or how it fits
|
||||||
public UnityEvent OnEndAnimation;
|
public UnityEvent OnEndAnimation;
|
||||||
|
|
||||||
public bool isPlaying;
|
public bool isPlaying; // Tracks if the spell animation is currently playing
|
||||||
protected bool triggerOnce;
|
protected bool triggerOnce; // For OnEndAnimation event
|
||||||
protected vThirdPersonInput tpInput;
|
protected vThirdPersonInput tpInput; // Renamed from tpInput to avoid conflict with Invector's tpInput if any confusion
|
||||||
internal bool shieldEffectIsActive;
|
internal bool shieldEffectIsActive;
|
||||||
private bool shieldAnimationIsActive;
|
private bool shieldAnimationIsActive;
|
||||||
private int equipAreaSelectedIndex = 0;
|
private int equipAreaSelectedIndex = 0;
|
||||||
|
|
||||||
protected virtual void LateUpdate()
|
protected virtual void LateUpdate() // LateUpdate for animation state checks is common
|
||||||
{
|
{
|
||||||
TriggerSpellAnimation();
|
TriggerSpellAnimation();
|
||||||
AnimationBehaviour();
|
AnimationBehaviour();
|
||||||
@@ -409,8 +472,8 @@ namespace Beyond
|
|||||||
|
|
||||||
protected virtual void TriggerSpellAnimation()
|
protected virtual void TriggerSpellAnimation()
|
||||||
{
|
{
|
||||||
// condition to trigger the animation
|
bool playConditions = !isPlaying && tpInput != null && tpInput.cc != null &&
|
||||||
bool playConditions = !isPlaying && !(tpInput.cc.customAction || tpInput.cc.IsAnimatorTag("special") || tpInput.cc.IsAnimatorTag("LockMovement"));
|
!(tpInput.cc.customAction || tpInput.cc.IsAnimatorTag("special") || tpInput.cc.IsAnimatorTag("LockMovement"));
|
||||||
|
|
||||||
if (actionInput.GetButtonDown() && playConditions)
|
if (actionInput.GetButtonDown() && playConditions)
|
||||||
TryToPlaySpellAnimation();
|
TryToPlaySpellAnimation();
|
||||||
@@ -418,174 +481,207 @@ namespace Beyond
|
|||||||
|
|
||||||
public void TryToPlaySpellAnimation()
|
public void TryToPlaySpellAnimation()
|
||||||
{
|
{
|
||||||
// EffectDesc selectedEffect = GetCurrentlySelectedPower();
|
selectedEffect = GetCurrentlySelectedPower(); // Ensure current selected effect is fetched
|
||||||
|
|
||||||
if (shieldAnimationIsActive && selectedEffect == shield) //casting shield on shield shouldnt be possible
|
if (selectedEffect == shield && shieldAnimationIsActive)
|
||||||
{
|
{
|
||||||
TryToPlayCantDoThatYetClip();
|
TryToPlayCantDoThatYetClip();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Player.Instance == null) // Safeguard
|
||||||
|
{
|
||||||
|
Debug.LogError("Player.Instance is null. Cannot cast spell.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedEffect != null && currentSpellFaithCost <= Player.Instance.GetCurrentFaithValue())
|
if (selectedEffect != null && currentSpellFaithCost <= Player.Instance.GetCurrentFaithValue())
|
||||||
{
|
{
|
||||||
Player.Instance.UpdateFaithCurrentValue(-currentSpellFaithCost);
|
Player.Instance.UpdateFaithCurrentValue(-currentSpellFaithCost);
|
||||||
animationClip = selectedEffect.animClipName;
|
animationClip = selectedEffect.animClipName; // Set animation clip for AnimationBehaviour
|
||||||
|
|
||||||
|
// NEW: Perform initial snap rotation if auto-targeting is enabled
|
||||||
|
if (enableAutoTargetIntegration)
|
||||||
|
{
|
||||||
|
SnapLookTowardsAutoTarget();
|
||||||
|
}
|
||||||
|
// else // OLD logic for turning - can be removed or kept as fallback
|
||||||
|
// {
|
||||||
|
// TryToTurnTowordsEnemy();
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (tpInput != null && tpInput.cc != null && tpInput.cc.animator != null)
|
||||||
|
{
|
||||||
tpInput.cc.animator.CrossFadeInFixedTime(animationClip, 0.1f);
|
tpInput.cc.animator.CrossFadeInFixedTime(animationClip, 0.1f);
|
||||||
TryToTurnTowordsEnemy();
|
OnPlayAnimation.Invoke(); // Invoke OnPlay event
|
||||||
selectedEffect.del();
|
triggerOnce = true; // Allow OnEndAnimation to be called
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogError("Cannot play spell animation: tpInput or its components are null.");
|
||||||
|
return; // Don't proceed to call delegate if animation components are missing
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedEffect.del?.Invoke(); // Call the spell's primary action delegate
|
||||||
|
|
||||||
if (powersArea.equipSlots[equipAreaSelectedIndex].item.destroyAfterUse)
|
if (powersArea.equipSlots[equipAreaSelectedIndex].item.destroyAfterUse)
|
||||||
{
|
{
|
||||||
if (selectedEffect == silentPeek)
|
if (selectedEffect == silentPeek)
|
||||||
{
|
{
|
||||||
//for silent peek it is handled in it's controller
|
// Special handling for Silent Peek item destruction (likely in its controller)
|
||||||
//if (SilentPeekController.Instance.IsActive())
|
|
||||||
// powersArea.UseEquippedItem();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
powersArea.UseItem(powersArea.equipSlots[equipAreaSelectedIndex]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
powersArea.UseItem(powersArea.equipSlots[equipAreaSelectedIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (selectedEffect != null) // Not enough faith
|
||||||
{
|
{
|
||||||
TryToPlayNoEnoughFaithClip();
|
TryToPlayNoEnoughFaithClip();
|
||||||
}
|
}
|
||||||
|
// If selectedEffect is null, nothing happens (no spell selected/valid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NEW: Method for an immediate snap-look towards the auto-target
|
||||||
|
private void SnapLookTowardsAutoTarget()
|
||||||
|
{
|
||||||
|
if (_autoTargettingInstance == null || _autoTargettingInstance.CurrentTarget == null)
|
||||||
|
{
|
||||||
|
return; // No auto-target system or no current target
|
||||||
|
}
|
||||||
|
|
||||||
|
vFSMBehaviourController currentTarget = _autoTargettingInstance.CurrentTarget;
|
||||||
|
Transform playerTransform = transform;
|
||||||
|
|
||||||
|
// Check distance condition from MagicAttacks settings
|
||||||
|
float distSqr = (currentTarget.transform.position - playerTransform.position).sqrMagnitude;
|
||||||
|
if (distSqr > maxTurnTowardDistance * maxTurnTowardDistance)
|
||||||
|
{
|
||||||
|
return; // Target is too far for this specific snap-look interaction
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check angle condition from MagicAttacks settings
|
||||||
|
if (!_autoTargettingInstance.IsTargetInAngle(playerTransform, currentTarget, degreeThreshold))
|
||||||
|
{
|
||||||
|
return; // Target is not within the desired cone for snap-look
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 directionToTarget = currentTarget.transform.position - playerTransform.position;
|
||||||
|
directionToTarget.y = 0f; // Horizontal rotation only
|
||||||
|
|
||||||
|
if (directionToTarget.sqrMagnitude > 0.0001f)
|
||||||
|
{
|
||||||
|
playerTransform.rotation = Quaternion.LookRotation(directionToTarget.normalized);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TryToPlayCantDoThatYetClip()
|
private void TryToPlayCantDoThatYetClip()
|
||||||
{
|
{
|
||||||
if (!canPlayCantDoClip)
|
if (!canPlayCantDoClip) return;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
canPlayCantDoClip = false;
|
canPlayCantDoClip = false;
|
||||||
DOVirtual.DelayedCall(1, () => canPlayCantDoClip = true);
|
DOVirtual.DelayedCall(1f, () => canPlayCantDoClip = true); // Reset flag
|
||||||
|
|
||||||
var text = $"Spell is already active";
|
var text = "Spell is already active"; // Example message
|
||||||
|
if (bItemCollectionDisplay.Instance != null)
|
||||||
bItemCollectionDisplay.Instance.FadeText(text, 4, 0.25f);
|
bItemCollectionDisplay.Instance.FadeText(text, 4, 0.25f);
|
||||||
Player.Instance.PlayICantDoThatYet();
|
if (Player.Instance != null)
|
||||||
|
Player.Instance.PlayICantDoThatYet(); // Assuming this method exists on Player
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TryToPlayNoEnoughFaithClip()
|
private void TryToPlayNoEnoughFaithClip()
|
||||||
{
|
{
|
||||||
if (!canPlayNoFaithClip)
|
if (!canPlayNoFaithClip) return;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
canPlayNoFaithClip = false;
|
canPlayNoFaithClip = false;
|
||||||
DOVirtual.DelayedCall(1.5f, () => canPlayNoFaithClip = true);
|
DOVirtual.DelayedCall(1.5f, () => canPlayNoFaithClip = true); // Reset flag
|
||||||
|
|
||||||
var text = $"Not enough Faith";
|
var text = "Not enough Faith";
|
||||||
|
if (bItemCollectionDisplay.Instance != null)
|
||||||
bItemCollectionDisplay.Instance.FadeText(text, 4, 0.25f);
|
bItemCollectionDisplay.Instance.FadeText(text, 4, 0.25f);
|
||||||
Player.Instance.PlayNoFaithClip();
|
if (Player.Instance != null)
|
||||||
|
Player.Instance.PlayNoFaithClip(); // Assuming this method exists on Player
|
||||||
}
|
}
|
||||||
|
|
||||||
public EffectDesc GetCurrentlySelectedPower()
|
public EffectDesc GetCurrentlySelectedPower()
|
||||||
{
|
{
|
||||||
// powersArea.equipSlots[index];
|
if (powersArea == null || equipAreaSelectedIndex < 0 || equipAreaSelectedIndex >= powersArea.equipSlots.Count ||
|
||||||
|
powersArea.equipSlots[equipAreaSelectedIndex] == null || powersArea.equipSlots[equipAreaSelectedIndex].item == null)
|
||||||
if (powersArea.equipSlots[equipAreaSelectedIndex] == null)
|
|
||||||
{
|
{
|
||||||
currentSpellFaithCost = int.MaxValue;
|
currentSpellFaithCost = int.MaxValue;
|
||||||
|
currentSelectedSpellName = "";
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
bItem selectedSpell = powersArea.equipSlots[equipAreaSelectedIndex].item;
|
|
||||||
string itemName = selectedSpell.name;
|
bItem selectedSpellItem = powersArea.equipSlots[equipAreaSelectedIndex].item;
|
||||||
currentSpellFaithCost = selectedSpell.GetItemAttribute(bItemAttributes.Faith).value;
|
currentSelectedSpellName = selectedSpellItem.name; // Store for display or debugging
|
||||||
return m_effects.Find(item => item.name == itemName || item.secondaryName == itemName);
|
currentSpellFaithCost = selectedSpellItem.GetItemAttribute(bItemAttributes.Faith).value;
|
||||||
|
|
||||||
|
return m_effects.Find(effect => effect.name == selectedSpellItem.name || effect.secondaryName == selectedSpellItem.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SelectPowerBasedOnArea(int newIndex)
|
public void SelectPowerBasedOnArea(int newIndex)
|
||||||
{
|
{
|
||||||
equipAreaSelectedIndex = newIndex;
|
equipAreaSelectedIndex = newIndex;
|
||||||
selectedEffect = GetCurrentlySelectedPower();
|
selectedEffect = GetCurrentlySelectedPower(); // Update current effect based on selection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OLD rotation methods - can be removed or commented out if new system is preferred
|
||||||
|
/*
|
||||||
private void LerpRotation()
|
private void LerpRotation()
|
||||||
{
|
{
|
||||||
|
// ... original LerpRotation code using GetNearestEnemy ...
|
||||||
float minDist = maxTurnTowardDistance;
|
|
||||||
var enemy = GetNearestEnemy(ref minDist);
|
|
||||||
|
|
||||||
if (!IsEnemyInAngleRange(enemy))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Transform playerTransform = Player.Instance.transform;
|
|
||||||
var toEnemy = enemy.transform.position - playerTransform.position;
|
|
||||||
toEnemy.y = 0f;
|
|
||||||
toEnemy.Normalize();
|
|
||||||
|
|
||||||
Quaternion targetRot =
|
|
||||||
Quaternion.LookRotation(toEnemy);
|
|
||||||
|
|
||||||
var rotation = playerTransform.rotation;
|
|
||||||
rotation = Quaternion.RotateTowards(rotation, targetRot, Time.deltaTime * rotationSpeed);
|
|
||||||
//rotation = Quaternion.Lerp(rotation, targetRot, Time.deltaTime * rotationSpeed);
|
|
||||||
playerTransform.rotation = rotation;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsEnemyInAngleRange(vFSMBehaviourController ai)
|
private bool IsEnemyInAngleRange(vFSMBehaviourController ai)
|
||||||
{
|
{
|
||||||
if (ai == null)
|
// ... original IsEnemyInAngleRange code ...
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector3 playerFwd = Player.Instance.transform.forward;
|
|
||||||
Vector3 target = (ai.transform.forward - playerFwd).normalized;
|
|
||||||
|
|
||||||
float dot = Vector3.Dot(playerFwd, target );
|
|
||||||
float angle = 180f - Mathf.Acos(dot) * Mathf.Rad2Deg;
|
|
||||||
//Debug.Log("IsEnemyInAngleRange: angle: "+angle);
|
|
||||||
|
|
||||||
if (angle > degreeThreshold)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
private vFSMBehaviourController GetNearestEnemy(ref float minDist)
|
private vFSMBehaviourController GetNearestEnemy(ref float minDist)
|
||||||
{
|
{
|
||||||
var controllers = GameStateManager.Instance.GetActiveCombatcontrollers();
|
// ... original GetNearestEnemy code ...
|
||||||
Vector3 playerPos = Player.Instance.transform.position;
|
}
|
||||||
vFSMBehaviourController ctrl = null;
|
private void TryToTurnTowordsEnemy() // Replaced by SnapLookTowardsAutoTarget
|
||||||
foreach (var aic in controllers)
|
|
||||||
{
|
{
|
||||||
var dist2 = aic.transform.position - Player.Instance.transform.position;
|
// ... original TryToTurnTowordsEnemy code ...
|
||||||
dist2.y = 0f;
|
|
||||||
if (dist2.magnitude < minDist)
|
|
||||||
{
|
|
||||||
ctrl = aic;
|
|
||||||
minDist = dist2.magnitude;
|
|
||||||
}
|
}
|
||||||
}
|
*/
|
||||||
|
|
||||||
return ctrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected virtual void AnimationBehaviour()
|
protected virtual void AnimationBehaviour()
|
||||||
|
{
|
||||||
|
if (tpInput == null || tpInput.cc == null || tpInput.cc.animator == null || string.IsNullOrEmpty(animationClip))
|
||||||
|
{
|
||||||
|
isPlaying = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
{ // know if the animation is playing or not
|
// isPlaying should reflect if the *specific spell animation* is active.
|
||||||
// isPlaying = spellLayerInfo.IsName(animationClip);
|
isPlaying = tpInput.cc.animator.GetCurrentAnimatorStateInfo(spellLayerIndex).IsName(animationClip) ||
|
||||||
|
tpInput.cc.animator.GetNextAnimatorStateInfo(spellLayerIndex).IsName(animationClip);
|
||||||
|
|
||||||
isPlaying = tpInput.cc.animator.GetCurrentAnimatorStateInfo(spellLayerIndex).IsName(animationClip);
|
|
||||||
// isPlaying = tpInput.cc.baseLayerInfo.IsName(animationClip);
|
|
||||||
|
|
||||||
if (isPlaying)
|
if (isPlaying)
|
||||||
{
|
{
|
||||||
// detected the end of the animation clip to trigger the OnEndAnimation Event
|
if (tpInput.cc.animator.GetCurrentAnimatorStateInfo(spellLayerIndex).IsName(animationClip) &&
|
||||||
if (tpInput.cc.animator.GetCurrentAnimatorStateInfo(spellLayerIndex).normalizedTime >= animationEnd)
|
tpInput.cc.animator.GetCurrentAnimatorStateInfo(spellLayerIndex).normalizedTime >= animationEnd)
|
||||||
{
|
{
|
||||||
if (triggerOnce)
|
if (triggerOnce)
|
||||||
{
|
{
|
||||||
triggerOnce = false; // reset the bool so we can call the event again
|
triggerOnce = false;
|
||||||
OnEndAnimation.Invoke(); // call the OnEnd Event at the end of the animation
|
OnEndAnimation.Invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If not playing the specific animation clip, ensure triggerOnce is reset
|
||||||
|
// This handles cases where animation might be interrupted before reaching animationEnd
|
||||||
|
if (triggerOnce)
|
||||||
|
{
|
||||||
|
triggerOnce = false;
|
||||||
|
// Optionally, invoke OnEndAnimation if it should always fire on exiting the state,
|
||||||
|
// but current logic only fires it if normalizedTime >= animationEnd.
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user