391 lines
18 KiB
C#
391 lines
18 KiB
C#
using System;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
using VOID.Generic.Dict;
|
|
using VOID.Generic.Objects.Abstract.Events;
|
|
using VOID.Generic.Objects.Unit.Animations;
|
|
using VOID.Generic.Objects.Unit.Events;
|
|
using VOID.Generic.Objects.Weapon;
|
|
using VOID.Generic.Objects.Wearable.Events;
|
|
using VOID.Generic.Settings;
|
|
using VOID.ScriptableObjects.Animations;
|
|
|
|
namespace VOID.Generic.Objects.Unit
|
|
{
|
|
[Serializable]
|
|
public class UnitObjectAnimator : ObjectComponent<UnitObject>
|
|
{
|
|
#region Initialize
|
|
|
|
protected override void SelfInitialize()
|
|
{
|
|
unitAnimatorCallbacks = parent.GetComponentInChildren<UnitAnimatorCallbacks>();
|
|
unitAnimatorCallbacks.OnAnimationEvent += HandleAnimationEvent;
|
|
unitAnimatorCallbacks.OnApplyRootMotion += HandleRootMotion;
|
|
animator = parent.GetComponentInChildren<Animator>();
|
|
animatorOverrideController = new AnimatorOverrideController(animator.runtimeAnimatorController);
|
|
animator.runtimeAnimatorController = animatorOverrideController;
|
|
}
|
|
|
|
protected override void EventInitialize()
|
|
{
|
|
parent.basicEvents.onFixedUpdate.AddListener(UpdateAnimatorVelocity);
|
|
parent.basicEvents.onDeathAfter.AddListener(OnDeath);
|
|
parent.unitEvents.onEquipAfter.AddListener(OnEquip);
|
|
parent.unitEvents.onUnEquipAfter.AddListener(OnUnEquip);
|
|
parent.basicEvents.onTurnQueueEnter.AddListener(OnTurnQueueEnter);
|
|
parent.basicEvents.onTurnQueueLeave.AddListener(OnTurnQueueLeave);
|
|
parent.basicEvents.onRevive.AddListener(OnRevive);
|
|
parent.unitEvents.onMoveStart.AddListener(OnMoveStart);
|
|
}
|
|
|
|
protected override void Initialize()
|
|
{
|
|
var humanoidAnimationSettings = SettingsManager.humanoidAnimation;
|
|
SetLoopAnimation(AnimationType.Traverse, humanoidAnimationSettings.defaultLadderUpAnimation);
|
|
SetLoopAnimation(AnimationType.Death, humanoidAnimationSettings.defaultDeathAnimation);
|
|
UpdateStanceAnimationOnce();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Animation Hashes
|
|
|
|
// Layers
|
|
private static readonly int StanceLayerIndex = 0;
|
|
private static readonly int ActionLayerIndex = 1;
|
|
|
|
// Movement
|
|
private static readonly int VelocityHash = Animator.StringToHash("VELOCITY");
|
|
private static readonly int VelocityVerticalNormalizedHash = Animator.StringToHash("VELOCITY_VERTICAL");
|
|
private static readonly int VelocityHorizontalNormalizedHash = Animator.StringToHash("VELOCITY_HORIZONTAL");
|
|
|
|
// Events inside animations
|
|
private static readonly int TriggerEndLoopHash = Animator.StringToHash("TRIGGER_LOOP_END");
|
|
private static readonly int IsLoopHash = Animator.StringToHash("IS_LOOP");
|
|
private static readonly int HaveLoopStartHash = Animator.StringToHash("HAVE_LOOP_START");
|
|
private static readonly int HaveLoopEndHash = Animator.StringToHash("HAVE_LOOP_END");
|
|
|
|
// Animations
|
|
private static readonly int TriggerCastingHash = Animator.StringToHash("TRIGGER_CASTING");
|
|
private static readonly int TriggerAbilityHash = Animator.StringToHash("TRIGGER_ABILITY");
|
|
private static readonly int TriggerTraverseHash = Animator.StringToHash("TRIGGER_TRAVERSE");
|
|
private static readonly int TriggerDeathHash = Animator.StringToHash("TRIGGER_DEATH");
|
|
|
|
// Stances
|
|
private static readonly int TriggerStanceRelaxHash = Animator.StringToHash("TRIGGER_STANCE_RELAX");
|
|
private static readonly int TriggerStanceUnarmedHash = Animator.StringToHash("TRIGGER_STANCE_UNARMED");
|
|
private static readonly int TriggerStance1Hash = Animator.StringToHash("TRIGGER_STANCE_1");
|
|
private static readonly int TriggerStance1WithShieldHash = Animator.StringToHash("TRIGGER_STANCE_1_WITH_SHIELD");
|
|
private static readonly int TriggerStance2Hash = Animator.StringToHash("TRIGGER_STANCE_2");
|
|
private static readonly int TriggerStance2SwordHash = Animator.StringToHash("TRIGGER_STANCE_2_SWORD");
|
|
private static readonly int TriggerStance2BowHash = Animator.StringToHash("TRIGGER_STANCE_2_BOW");
|
|
private static readonly int TriggerStance2CrossbowHash = Animator.StringToHash("TRIGGER_STANCE_2_CROSSBOW");
|
|
private static readonly int TriggerStance2StaffHash = Animator.StringToHash("TRIGGER_STANCE_2_STAFF");
|
|
|
|
// Actions - States
|
|
private static readonly int CastingStartLoopHash = Animator.StringToHash("CASTING - START");
|
|
private static readonly int CastingLoopHash = Animator.StringToHash("CASTING - LOOP");
|
|
private static readonly int CastingEndLoopHash = Animator.StringToHash("CASTING - END");
|
|
private static readonly int AbilitySimpleHash = Animator.StringToHash("ABILITY - SIMPLE");
|
|
private static readonly int AbilityStartLoopHash = Animator.StringToHash("ABILITY - START");
|
|
private static readonly int AbilityLoopHash = Animator.StringToHash("ABILITY - LOOP");
|
|
private static readonly int AbilityEndLoopHash = Animator.StringToHash("ABILITY - END");
|
|
private static readonly int TraverseStartLoopHash = Animator.StringToHash("TRAVERSE - START");
|
|
private static readonly int TraverseLoopHash = Animator.StringToHash("TRAVERSE - LOOP");
|
|
private static readonly int TraverseEndLoopHash = Animator.StringToHash("TRAVERSE - END");
|
|
private static readonly int DeathStartLoopHash = Animator.StringToHash("DEATH - START");
|
|
private static readonly int DeathLoopHash = Animator.StringToHash("DEATH - LOOP");
|
|
|
|
#endregion
|
|
|
|
protected UnitAnimatorCallbacks unitAnimatorCallbacks;
|
|
protected Animator animator;
|
|
protected AnimatorOverrideController animatorOverrideController;
|
|
|
|
#region RootMotion
|
|
|
|
public bool applyRootMotion;
|
|
|
|
private void HandleRootMotion()
|
|
{
|
|
if (applyRootMotion) parent.transform.position += animator.deltaPosition;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AnimationEvents
|
|
|
|
private AnimationGenericEvent _currentAnimationEvent;
|
|
private int[] _currentExpectedStates;
|
|
|
|
private void HandleAnimationEvent(AnimationEvent animationEvent)
|
|
{
|
|
// LITTLE FIX: Avoid executing events multiple times from different animation states
|
|
var currentState = animationEvent.animatorStateInfo.shortNameHash;
|
|
if (_currentExpectedStates.All(state => state != currentState)) return;
|
|
|
|
switch (animationEvent.functionName)
|
|
{
|
|
case "OnStart": OnStartAnimationEvent(); break;
|
|
case "OnLoopStart": OnLoopStartAnimationEvent(); break;
|
|
case "OnPerform": OnPerformAnimationEvent(); break;
|
|
case "OnLoopEnd": OnLoopEndAnimationEvent(); break;
|
|
case "OnEnd": OnEndAnimationEvent(); break;
|
|
}
|
|
}
|
|
|
|
private AnimationGenericEventSet animSet => parent.unitEvents.onAnimation[_currentAnimationEvent.animationType];
|
|
private void OnStartAnimationEvent() => animSet.onStart.Invoke(_currentAnimationEvent);
|
|
private void OnLoopStartAnimationEvent() => animSet.onLoopStart.Invoke(_currentAnimationEvent);
|
|
private void OnPerformAnimationEvent() => animSet.onPerform.Invoke(_currentAnimationEvent);
|
|
private void OnLoopEndAnimationEvent() => animSet.onLoopEnd.Invoke(_currentAnimationEvent);
|
|
private void OnEndAnimationEvent() => animSet.onEnd.Invoke(_currentAnimationEvent);
|
|
|
|
#endregion
|
|
|
|
#region Events
|
|
|
|
private void OnDeath(DeathEvent deathEvent)
|
|
{
|
|
var animationEvent = new AnimationGenericEvent();
|
|
animationEvent.unit = deathEvent.obj as UnitObject;
|
|
animationEvent.animationType = AnimationType.Death;
|
|
StartLoopAnimation(animationEvent);
|
|
}
|
|
|
|
private void OnEquip(EquipEvent equipEvent)
|
|
{
|
|
if (equipEvent.wearable is WeaponObject)
|
|
{
|
|
parent.basicEvents.onFixedUpdate.RemoveListener(UpdateStanceAnimationOnce);
|
|
parent.basicEvents.onFixedUpdate.AddListener(UpdateStanceAnimationOnce);
|
|
}
|
|
}
|
|
|
|
private void OnUnEquip(UnEquipEvent unEquipEvent)
|
|
{
|
|
if (unEquipEvent.wearable is WeaponObject)
|
|
{
|
|
parent.basicEvents.onFixedUpdate.RemoveListener(UpdateStanceAnimationOnce);
|
|
parent.basicEvents.onFixedUpdate.AddListener(UpdateStanceAnimationOnce);
|
|
}
|
|
}
|
|
|
|
private void OnTurnQueueEnter(TurnEvent turnEvent)
|
|
{
|
|
parent.basicEvents.onFixedUpdate.RemoveListener(UpdateStanceAnimationOnce);
|
|
parent.basicEvents.onFixedUpdate.AddListener(UpdateStanceAnimationOnce);
|
|
}
|
|
|
|
private void OnTurnQueueLeave(TurnEvent turnEvent)
|
|
{
|
|
parent.basicEvents.onFixedUpdate.RemoveListener(UpdateStanceAnimationOnce);
|
|
parent.basicEvents.onFixedUpdate.AddListener(UpdateStanceAnimationOnce);
|
|
}
|
|
|
|
private void OnRevive(ReviveEvent reviveEvent)
|
|
{
|
|
// Forcefully end DEATH animation
|
|
animator.CrossFadeInFixedTime("IDLE", 0.1f, ActionLayerIndex);
|
|
}
|
|
|
|
private void OnMoveStart(MoveEvent moveEvent)
|
|
{
|
|
// Forcefully end (skipping exiting time) current action animation
|
|
animator.CrossFadeInFixedTime("IDLE", 0.1f, ActionLayerIndex);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Animations
|
|
|
|
private void UpdateAnimatorVelocity()
|
|
{
|
|
var localVelocity = parent.unitNavAgent.localVelocity;
|
|
animator.SetFloat(VelocityHash, parent.unitState.isMoving ? parent.unitNavAgent.moveSpeed : 0f);
|
|
animator.SetFloat(VelocityVerticalNormalizedHash, localVelocity.normalized.z);
|
|
animator.SetFloat(VelocityHorizontalNormalizedHash, localVelocity.normalized.x);
|
|
}
|
|
|
|
public void SetAnimation(AnimationType animationType, ActionAnimationList animationList)
|
|
{
|
|
var baseClip = GetBaseAnimation(animationType).clip;
|
|
animatorOverrideController[baseClip] = animationList ? animationList.list.clip : null;
|
|
}
|
|
|
|
public void SetLoopAnimation(AnimationType animationType, ActionLoopAnimationList animationList)
|
|
{
|
|
SetLoopAnimation(animationType, animationList ? animationList.list : null);
|
|
}
|
|
|
|
private void SetLoopAnimation(AnimationType animationType, ActionLoopAnimation animations)
|
|
{
|
|
var baseAnimations = GetBaseLoopAnimation(animationType);
|
|
animatorOverrideController[baseAnimations.startClip] = animations?.startClip;
|
|
animatorOverrideController[baseAnimations.loopClip] = animations?.loopClip;
|
|
animatorOverrideController[baseAnimations.endClip] = animations?.endClip;
|
|
}
|
|
|
|
private void UpdateStanceAnimationOnce()
|
|
{
|
|
// Prevent running this more than once per frame
|
|
parent.basicEvents.onFixedUpdate.RemoveListener(UpdateStanceAnimationOnce);
|
|
|
|
if (!parent.basicState.turnManager)
|
|
{
|
|
// Disable (hide) weapons in hands
|
|
parent.unitData.leftHandPosition.gameObject.SetActive(false);
|
|
parent.unitData.rightHandPosition.gameObject.SetActive(false);
|
|
|
|
// Out of combat idle and move animations
|
|
animator.SetTrigger(TriggerStanceRelaxHash);
|
|
return;
|
|
}
|
|
|
|
// Stance depends on currently equipped weapons
|
|
var mainHand = parent.unitEquipment.GetEquipped(EquipmentSlotType.MainHand) as WeaponObject;
|
|
var offHand = parent.unitEquipment.GetEquipped(EquipmentSlotType.OffHand) as WeaponObject;
|
|
var stanceHash = GetCombatStanceAnimationHash(mainHand, offHand);
|
|
// TODO: pomyśleć o lepszym ogarnięciu animacji postawy poza i w trakcie walki dla każdej broni
|
|
animator.SetTrigger(stanceHash);
|
|
|
|
// Activate (show) weapons in hands
|
|
parent.unitData.leftHandPosition.gameObject.SetActive(true);
|
|
parent.unitData.rightHandPosition.gameObject.SetActive(true);
|
|
}
|
|
|
|
public void StartAnimation(AnimationGenericEvent tEvent)
|
|
{
|
|
// Send event info to callback class so it can be used when calling its events
|
|
_currentAnimationEvent = tEvent;
|
|
_currentExpectedStates = GetExpectedStates(_currentAnimationEvent.animationType);
|
|
|
|
animator.SetBool(IsLoopHash, false);
|
|
animator.SetTrigger(GetAnimationHash(tEvent.animationType));
|
|
}
|
|
|
|
public void StartLoopAnimation(AnimationGenericEvent tEvent)
|
|
{
|
|
// Make sure that loop ending trigger is off (from previous loop)
|
|
animator.ResetTrigger(TriggerEndLoopHash);
|
|
|
|
// Send event info to callback class so it can be used when calling its events
|
|
_currentAnimationEvent = tEvent;
|
|
_currentExpectedStates = GetExpectedStates(_currentAnimationEvent.animationType);
|
|
|
|
// Run start and end loop animation if present, else skip them
|
|
var baseLoopClips = GetBaseLoopAnimation(tEvent.animationType);
|
|
animator.SetBool(HaveLoopStartHash, baseLoopClips.startClip);
|
|
animator.SetBool(HaveLoopEndHash, baseLoopClips.endClip);
|
|
|
|
// There is no animation for loop starting - we need to execute events manually
|
|
if (animator.GetBool(HaveLoopStartHash) == false)
|
|
{
|
|
OnStartAnimationEvent();
|
|
OnLoopStartAnimationEvent();
|
|
OnPerformAnimationEvent();
|
|
}
|
|
|
|
animator.SetBool(IsLoopHash, true);
|
|
animator.SetTrigger(GetAnimationHash(tEvent.animationType));
|
|
}
|
|
|
|
public void EndLoopAnimation()
|
|
{
|
|
// IMPORTANT! This prevent STACKOVERFLOW!
|
|
// Only looped animation should proceed here or else below code can make endless loop,
|
|
// all thanks to AbilityAction that started non-looped animation
|
|
if (animator.GetBool(IsLoopHash) == false) return;
|
|
|
|
// There is no animation for loop ending - we need to execute events manually
|
|
if (animator.GetBool(HaveLoopEndHash) == false)
|
|
{
|
|
OnLoopEndAnimationEvent();
|
|
OnEndAnimationEvent();
|
|
}
|
|
|
|
animator.SetTrigger(TriggerEndLoopHash);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helpers
|
|
|
|
private int[] GetExpectedStates(AnimationType animationType)
|
|
{
|
|
return animationType switch
|
|
{
|
|
AnimationType.Casting => new[] { CastingStartLoopHash, CastingLoopHash, CastingEndLoopHash },
|
|
AnimationType.Ability => new[] { AbilitySimpleHash, AbilityStartLoopHash, AbilityLoopHash, AbilityEndLoopHash },
|
|
AnimationType.Traverse => new[] { TraverseStartLoopHash, TraverseLoopHash, TraverseEndLoopHash },
|
|
AnimationType.Death => new[] { DeathStartLoopHash, DeathLoopHash },
|
|
_ => throw new ArgumentOutOfRangeException()
|
|
};
|
|
}
|
|
|
|
private int GetCombatStanceAnimationHash(WeaponObject mainHand, WeaponObject offHand)
|
|
{
|
|
// UNARMED
|
|
if (!mainHand && !offHand) return TriggerStanceUnarmedHash;
|
|
|
|
// SHIELD
|
|
if (offHand && offHand.weaponData.weaponType is WeaponType.Shield) return TriggerStance1WithShieldHash;
|
|
|
|
// ONE-HANDED
|
|
if (!mainHand || (mainHand && mainHand.weaponData.weaponCarryType is not WeaponCarryType.TwoHand)) return TriggerStance1Hash;
|
|
|
|
// BOW
|
|
if (mainHand.weaponData.weaponType is WeaponType.Bow) return TriggerStance2BowHash;
|
|
|
|
// CROSSBOW
|
|
if (mainHand.weaponData.weaponType is WeaponType.Crossbow) return TriggerStance2CrossbowHash;
|
|
|
|
// STAFF
|
|
if (mainHand.weaponData.weaponType is WeaponType.Staff) return TriggerStance2StaffHash;
|
|
|
|
// SWORD
|
|
if (mainHand.weaponData.weaponType is WeaponType.Sword) return TriggerStance2SwordHash;
|
|
|
|
// TWO-HANDED basic - AXE or MACE
|
|
return TriggerStance2Hash;
|
|
}
|
|
|
|
private ActionAnimation GetBaseAnimation(AnimationType animationType)
|
|
{
|
|
var humanoidAnimationSettings = SettingsManager.humanoidAnimation;
|
|
return animationType switch
|
|
{
|
|
AnimationType.Ability => humanoidAnimationSettings.baseAbility,
|
|
_ => throw new Exception($"Base animation don't exists for: {animationType}")
|
|
};
|
|
}
|
|
|
|
private ActionLoopAnimation GetBaseLoopAnimation(AnimationType animationType)
|
|
{
|
|
var humanoidAnimationSettings = SettingsManager.humanoidAnimation;
|
|
return animationType switch
|
|
{
|
|
AnimationType.Casting => humanoidAnimationSettings.baseCastingLoop,
|
|
AnimationType.Ability => humanoidAnimationSettings.baseAbilityLoop,
|
|
AnimationType.Traverse => humanoidAnimationSettings.baseTraverseLoop,
|
|
AnimationType.Death => humanoidAnimationSettings.baseDeath,
|
|
_ => throw new Exception($"Loop base animation don't exists for: {animationType}")
|
|
};
|
|
}
|
|
|
|
private int GetAnimationHash(AnimationType animationType)
|
|
{
|
|
return animationType switch
|
|
{
|
|
AnimationType.Casting => TriggerCastingHash,
|
|
AnimationType.Ability => TriggerAbilityHash,
|
|
AnimationType.Traverse => TriggerTraverseHash,
|
|
AnimationType.Death => TriggerDeathHash,
|
|
_ => throw new ArgumentOutOfRangeException(nameof(animationType), animationType, null)
|
|
};
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |