init
This commit is contained in:
@@ -0,0 +1,391 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user