using UnityEngine; namespace Invector.vCharacterController.AI { public class vSimpleMeleeAI_Animator : vSimpleMeleeAI_Motor { #region AI Variables private bool triggerDieBehaviour; private bool resetState; private float strafeInput; // get Layers from the Animator Controller public AnimatorStateInfo baseLayerInfo, rightArmInfo, leftArmInfo, fullBodyInfo, upperBodyInfo, underBodyInfo; int baseLayer { get { return animator.GetLayerIndex("Base Layer"); } } int underBodyLayer { get { return animator.GetLayerIndex("UnderBody"); } } int rightArmLayer { get { return animator.GetLayerIndex("RightArm"); } } int leftArmLayer { get { return animator.GetLayerIndex("LeftArm"); } } int upperBodyLayer { get { return animator.GetLayerIndex("UpperBody"); } } int fullbodyLayer { get { return animator.GetLayerIndex("FullBody"); } } #endregion public void UpdateAnimator(float _speed, float _direction) { if (animator == null || !animator.enabled) { return; } LayerControl(); LocomotionAnimation(_speed, _direction); RollAnimation(); CrouchAnimation(); ResetAndLockAgent(); MoveSetIDControl(); MeleeATK_Animation(); DEF_Animation(); DeadAnimation(); } void LayerControl() { baseLayerInfo = animator.GetCurrentAnimatorStateInfo(baseLayer); underBodyInfo = animator.GetCurrentAnimatorStateInfo(underBodyLayer); rightArmInfo = animator.GetCurrentAnimatorStateInfo(rightArmLayer); leftArmInfo = animator.GetCurrentAnimatorStateInfo(leftArmLayer); upperBodyInfo = animator.GetCurrentAnimatorStateInfo(upperBodyLayer); fullBodyInfo = animator.GetCurrentAnimatorStateInfo(fullbodyLayer); } void OnAnimatorMove() { actions = baseLayerInfo.IsTag("CustomAction") || lockMovement; if (Time.timeScale == 0 || agent == null) { return; } if (agent.enabled && !agent.isOnOffMeshLink && agent.updatePosition) { Vector3 velocity = animator.deltaPosition / Time.deltaTime; if (!velocity.IsVectorNaN()) { agent.velocity = velocity; } else { agent.velocity = Vector3.zero; } } if (!_rigidbody.useGravity && !actions && !agent.isOnOffMeshLink) { _rigidbody.linearVelocity = animator.deltaPosition; } if (!agent.updatePosition && !actions) { var point = agent.enabled ? agent.nextPosition : destination; if (Vector3.Distance(transform.position, point) > 0.5f) { desiredRotation = Quaternion.LookRotation(point - transform.position); var rot = Quaternion.Euler(transform.eulerAngles.x, desiredRotation.eulerAngles.y, transform.eulerAngles.z); transform.rotation = Quaternion.RotateTowards(transform.rotation, rot, agent.angularSpeed * Time.deltaTime); } transform.position = animator.rootPosition; return; } // Strafe Movement if (OnStrafeArea && !actions && currentTarget.transform != null && canSeeTarget && currentHealth > 0f) { Vector3 targetDir = currentTarget.transform.position - transform.position; float step = (meleeManager != null && isAttacking) ? attackRotationSpeed * Time.deltaTime : (strafeRotationSpeed * Time.deltaTime); Vector3 newDir = Vector3.RotateTowards(transform.forward, targetDir, step, 0.0F); var rot = Quaternion.LookRotation(newDir); transform.eulerAngles = new Vector3(transform.eulerAngles.x, rot.eulerAngles.y, transform.eulerAngles.z); } // Rotate the Character to the OffMeshLink End else if (agent.isOnOffMeshLink && !actions) { var pos = agent.nextOffMeshLinkData.endPos; targetPos = pos; UnityEngine.AI.OffMeshLinkData data = agent.currentOffMeshLinkData; desiredRotation = Quaternion.LookRotation(new Vector3(data.endPos.x, transform.position.y, data.endPos.z) - transform.position); transform.rotation = Quaternion.RotateTowards(transform.rotation, desiredRotation, (agent.angularSpeed * 2f) * Time.deltaTime); } // Free Movement else if (agent.desiredVelocity.magnitude > 0.1f && !actions && agent.enabled && currentHealth > 0f) { if (meleeManager != null && isAttacking) { desiredRotation = Quaternion.LookRotation(agent.desiredVelocity); transform.rotation = Quaternion.RotateTowards(transform.rotation, desiredRotation, agent.angularSpeed * attackRotationSpeed * Time.deltaTime); } else { desiredRotation = Quaternion.LookRotation(agent.desiredVelocity); transform.rotation = Quaternion.RotateTowards(transform.rotation, desiredRotation, agent.angularSpeed * Time.deltaTime); } } // Use the Animator rotation while doing an Action else if (actions || currentHealth <= 0f || isAttacking) { if (isRolling) { desiredRotation = Quaternion.LookRotation(rollDirection, Vector3.up); transform.rotation = desiredRotation; } else { transform.rotation = animator.rootRotation; } // Use the Animator position while doing an Action if (!agent.enabled) { destination = transform.position; transform.position = animator.rootPosition; } } } #region AI Locomotion Animations /// /// Control the Locomotion behaviour of the AI /// /// /// void LocomotionAnimation(float _speed, float _direction) { isGrounded = agent.enabled ? agent.isOnNavMesh : isRolling ? true : groundDistance <= groundCheckDistance; animator.SetBool("IsGrounded", isGrounded); _speed = Mathf.Clamp(_speed, -maxSpeed, maxSpeed); if (OnStrafeArea) { _direction = Mathf.Clamp(_direction, -strafeSpeed, strafeSpeed); } var animSpeed = Mathf.Abs(_speed) > 0.1f ? _speed : 0; var animDirection = Mathf.Abs(_direction) > 0.1f ? _direction : 0; var newInput = new Vector2(animSpeed, animDirection); strafeInput = Mathf.Clamp(newInput.magnitude, 0, 1.5f); animator.SetFloat("InputMagnitude", strafeInput, .2f, Time.deltaTime); animator.SetFloat("InputVertical", actions ? 0 : (_speed != 0) ? _speed : 0, 0.2f, Time.fixedDeltaTime); animator.SetFloat("InputHorizontal", _direction, 0.2f, Time.fixedDeltaTime); animator.SetBool("IsStrafing", OnStrafeArea); animator.SetBool("isDead", isDead); } protected virtual float maxSpeed { get { return (OnStrafeArea ? strafeSpeed : chaseSpeed); } } /// /// Trigger a Death by Animation, Animation with Ragdoll or just turn the Ragdoll On /// void DeadAnimation() { if (!isDead) { return; } if (!triggerDieBehaviour) { triggerDieBehaviour = true; DeathBehaviour(); } // death by animation if (deathBy == DeathBy.Animation) { if (fullBodyInfo.IsName("Dead")) { if (fullBodyInfo.normalizedTime >= 0.99f && groundDistance <= 0.15f) { RemoveComponents(); } } } // death by animation & ragdoll after a time else if (deathBy == DeathBy.AnimationWithRagdoll) { if (fullBodyInfo.IsName("Dead")) { // activate the ragdoll after the animation finish played if (fullBodyInfo.normalizedTime >= 0.8f) { onActiveRagdoll.Invoke(null); RemoveComponents(); } } } // death by ragdoll else if (deathBy == DeathBy.Ragdoll) { onActiveRagdoll.Invoke(null); RemoveComponents(); } } private void DeathBehaviour() { // change the culling mode to render the animation until finish animator.cullingMode = AnimatorCullingMode.AlwaysAnimate; // trigger die animation if (deathBy == DeathBy.Animation || deathBy == DeathBy.AnimationWithRagdoll) { animator.SetBool("isDead", isDead); } } void CrouchAnimation() { animator.SetBool("IsCrouching", isCrouched); if (animator != null && animator.enabled) { CheckAutoCrouch(); } } protected void RollAnimation() { if (animator == null || animator.enabled == false) { return; } isRolling = baseLayerInfo.IsName("Roll"); if (isRolling) { _rigidbody.constraints = RigidbodyConstraints.None | RigidbodyConstraints.FreezeRotation; _rigidbody.useGravity = true; agent.enabled = false; agent.updatePosition = false; } } void ResetAIRotation() { transform.eulerAngles = new Vector3(0, transform.eulerAngles.y, 0); } #endregion #region AI Melee Combat Animations /// /// MOVE SET ID - check the Animator to see what MoveSet the character will move, also check your weapon to see if the moveset matches /// ps* Move Set is the way your character will move, ATK_ID is the way your character will attack. You can have different locomotion animations and attacks. /// void MoveSetIDControl() { if (meleeManager == null) { return; } animator.SetFloat("MoveSet_ID", meleeManager.GetMoveSetID()); } /// /// Control Attack Behaviour /// void MeleeATK_Animation() { if (meleeManager == null) { return; } if (actions) { attackCount = 0; } animator.SetInteger("AttackID", meleeManager.GetAttackID()); } /// /// ATTACK MELEE ANIMATION - it's activate by the AttackInput() method at the TPController by a trigger /// void DEF_Animation() { if (meleeManager == null) { return; } if (isBlocking) { animator.SetInteger("DefenseID", meleeManager.GetDefenseID()); } animator.SetBool("IsBlocking", isBlocking); } /// /// Trigger the Attack animation /// public void MeleeAttack() { if (animator != null && animator.enabled && !actions) { animator.SetTrigger("WeakAttack"); } } /// /// Check if is in LockMovement to reset attack and disable agent /// void ResetAndLockAgent() { lockMovement = fullBodyInfo.IsTag("LockMovement") || upperBodyInfo.IsTag("ResetState"); if (lockMovement) { if (attackCount > 0) { canAttack = false; attackCount = 0; } if (baseLayerInfo.normalizedTime > 0.1f) { animator.ResetTrigger("ResetState"); _rigidbody.constraints = RigidbodyConstraints.None | RigidbodyConstraints.FreezeRotation; _rigidbody.useGravity = true; agent.enabled = false; agent.updatePosition = false; } } } /// /// Trigger Recoil Animation - It's Called at the MeleeWeapon /// public void TriggerRecoil(int recoil_id) { if (animator != null && animator.enabled && !isRolling) { animator.SetInteger("RecoilID", recoil_id); animator.SetTrigger("TriggerRecoil"); animator.SetTrigger("ResetState"); } } #endregion } }