1280 lines
44 KiB
C#
1280 lines
44 KiB
C#
using System.Collections;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
|
|
namespace Invector.vCharacterController.AI
|
|
{
|
|
public enum vAIMovementSpeed
|
|
{
|
|
Idle, Walking, Running, Sprinting
|
|
}
|
|
|
|
[vClassHeader("AI Controller")]
|
|
public abstract class vAIMotor : vCharacter, vEventSystems.vIAnimatorStateInfoController
|
|
{
|
|
#region AI VARIABLES
|
|
|
|
#region Inspector Variables
|
|
[vEditorToolbar("Start")]
|
|
public bool disableControllerOnStart;
|
|
|
|
[vEditorToolbar("Movement", order = 1)]
|
|
[SerializeField] protected vAIMovementSpeed currentSpeed;
|
|
[Tooltip("Change the velocity of all animations")]
|
|
public float animatorSpeed = 1;
|
|
[Tooltip("Smooth the InputMagniture Animator parameter Update")]
|
|
public float inputMagnitudeSmooth = 0.2f;
|
|
[vHelpBox("When checked, make sure to reset the speed values to 1 to use the root motion original speed, increase or decrease this value to have extraSpeed", vHelpBoxAttribute.MessageType.Info)]
|
|
[Tooltip("Turn off if you have 'in place' animations and use this values above to move the character, or use with root motion as extra speed")]
|
|
public bool useRootMotion = false;
|
|
public vMovementSpeed freeSpeed, strafeSpeed;
|
|
|
|
|
|
[vHelpBox("Check this options only if the Agent needs to walk on complex meshes.", vHelpBoxAttribute.MessageType.Info)]
|
|
[vEditorToolbar("Step Offset", order = 2)]
|
|
[SerializeField]
|
|
protected bool _useStepOffSet = true;
|
|
[vHideInInspector("useStepOffSet")]
|
|
[Tooltip("ADJUST IN PLAY MODE - Offset height limit for sters - GREY Raycast in front of the legs")]
|
|
[SerializeField]
|
|
protected float stepOffsetEnd = 0.45f;
|
|
[vHideInInspector("useStepOffSet")]
|
|
[Tooltip("ADJUST IN PLAY MODE - Offset height origin for sters, make sure to keep slight above the floor - GREY Raycast in front of the legs")]
|
|
[SerializeField]
|
|
protected float stepOffsetStart = 0.05f;
|
|
[vHideInInspector("useStepOffSet")]
|
|
[Tooltip("Higher value will result jittering on ramps, lower values will have difficulty on steps")]
|
|
[SerializeField]
|
|
protected float stepSmooth = 4f;
|
|
|
|
|
|
[vEditorToolbar("Ground & Jump", order = 3)]
|
|
[Tooltip("Make sure to bake the navmesh with the jump distance value higher then 0")]
|
|
[SerializeField]
|
|
protected float jumpSpeedPerMeter = 0.15f;
|
|
[SerializeField] protected float jumpHeight = 0.75f;
|
|
public float checkGroundDistance = 0.3f;
|
|
[vHelpBox("Make sure to bake the navmesh and use the correct layer on your ground mesh.", vHelpBoxAttribute.MessageType.Info)]
|
|
public LayerMask groundLayer = 1 << 0;
|
|
|
|
|
|
[vEditorToolbar("Auto Crouch", order = 4)]
|
|
public bool useAutoCrouch;
|
|
[SerializeField]
|
|
[Range(0, 1)]
|
|
protected float headDetectStart = 0.4f;
|
|
[SerializeField]
|
|
protected float headDetectHeight = 0.4f;
|
|
[SerializeField]
|
|
protected float headDetectMargin = 0.02f;
|
|
[vHideInInspector("useAutoCrouch")]
|
|
public LayerMask autoCrouchLayer = 1 << 0;
|
|
[SerializeField]
|
|
protected bool debugAutoCrouch;
|
|
|
|
|
|
[vEditorToolbar("Events")]
|
|
public UnityEngine.Events.UnityEvent onEnableController;
|
|
public UnityEngine.Events.UnityEvent onDisableController;
|
|
#endregion
|
|
|
|
#region Hide Inspector Variables
|
|
|
|
[HideInInspector]
|
|
public bool isStrafing { get; protected set; }
|
|
public bool isGrounded { get; protected set; }
|
|
|
|
[HideInInspector]
|
|
public Rigidbody _rigidbody;
|
|
[HideInInspector]
|
|
public PhysicsMaterial frictionPhysics, maxFrictionPhysics, slippyPhysics;
|
|
[HideInInspector]
|
|
public CapsuleCollider _capsuleCollider;
|
|
[HideInInspector]
|
|
public Vector3 targetDirection;
|
|
[HideInInspector]
|
|
public Vector3 input;
|
|
[HideInInspector]
|
|
public bool lockMovement, lockRotation, stopMove, isJumping, doingCustomAction;
|
|
|
|
public vEventSystems.vAnimatorStateInfos animatorStateInfos { get; protected set; }
|
|
|
|
public override bool isCrouching
|
|
{
|
|
get
|
|
{
|
|
return _isCrouching || _isCrouchingFromCast;
|
|
}
|
|
set
|
|
{
|
|
_isCrouching = value;
|
|
}
|
|
}
|
|
|
|
public bool isRolling { get; protected set; }
|
|
public vAIMovementSpeed movementSpeed { get { return currentSpeed; } protected set { currentSpeed = value; } }
|
|
public bool useCustomRotationSpeed { get; set; }
|
|
public float customRotationSpeed { get; set; }
|
|
protected UnityEvent onUpdateAI = new UnityEvent();
|
|
public UnityEvent OnUpdateAI { get { return onUpdateAI; } }
|
|
protected bool _isStrafingRef;
|
|
protected bool _isGroundedRef;
|
|
protected float _verticalVelocityRef;
|
|
protected float _groundDistanceRef;
|
|
protected bool _isCrouchingRef;
|
|
protected bool _isCrouchingFromCast;
|
|
|
|
public virtual bool actions
|
|
{
|
|
get
|
|
{
|
|
return customAction || isRolling || isJumping;
|
|
}
|
|
}
|
|
[HideInInspector]
|
|
public bool customAction;
|
|
#endregion
|
|
|
|
#region Protected Variables
|
|
protected float direction;
|
|
protected float speed;
|
|
protected float velocity;
|
|
protected float strafeMagnitude;
|
|
protected float rotationMagnitude;
|
|
protected float colliderHeight;
|
|
protected float verticalVelocity;
|
|
protected RaycastHit groundHit;
|
|
protected Quaternion freeRotation;
|
|
protected Vector3 colliderCenter;
|
|
protected Vector3 lastCharacterAngle;
|
|
protected Vector3 temporaryDirection;
|
|
protected float temporaryDirectionTime;
|
|
#endregion
|
|
|
|
#region Animator Variables
|
|
public AnimatorStateInfo baseLayerInfo, rightArmInfo, leftArmInfo, fullBodyInfo, upperBodyInfo, underBodyInfo;
|
|
public virtual int baseLayer { get { return animator.GetLayerIndex("Base Layer"); } }
|
|
public virtual int underBodyLayer { get { return animator.GetLayerIndex("UnderBody"); } }
|
|
public virtual int rightArmLayer { get { return animator.GetLayerIndex("RightArm"); } }
|
|
public virtual int leftArmLayer { get { return animator.GetLayerIndex("LeftArm"); } }
|
|
public virtual int upperBodyLayer { get { return animator.GetLayerIndex("UpperBody"); } }
|
|
public virtual int fullbodyLayer { get { return animator.GetLayerIndex("FullBody"); } }
|
|
//[HideInInspector]
|
|
//public bool triggerDieBehaviour;
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
#region PROTECTED VIRTUAL METHODS.UNITY
|
|
|
|
protected virtual void OnDrawGizmos()
|
|
{
|
|
if (!Application.isPlaying)
|
|
{
|
|
_capsuleCollider = GetComponent<CapsuleCollider>();
|
|
colliderHeight = _capsuleCollider.height;
|
|
}
|
|
if (!debugAutoCrouch)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var color = Color.green;
|
|
// radius of SphereCast
|
|
float radius = _capsuleCollider.radius + headDetectMargin;
|
|
// Position of SphereCast origin stating in base of capsule
|
|
Vector3 pos = transform.position + Vector3.up * colliderHeight * headDetectStart;
|
|
// ray for SphereCast
|
|
Ray ray2 = new Ray(pos, Vector3.up);
|
|
|
|
if (!Application.isPlaying && Physics.SphereCast(ray2, radius, (headDetectHeight + (_capsuleCollider.radius)), autoCrouchLayer) || isCrouching)
|
|
{
|
|
color = Color.red;
|
|
}
|
|
else
|
|
{
|
|
color = Color.green;
|
|
}
|
|
|
|
color.a = 0.4f;
|
|
Gizmos.color = color;
|
|
Gizmos.DrawWireSphere(pos + Vector3.up * ((headDetectHeight + (_capsuleCollider.radius))), radius);
|
|
}
|
|
|
|
protected override void Start()
|
|
{
|
|
base.Start();
|
|
animator = GetComponent<Animator>();
|
|
|
|
if (animator)
|
|
{
|
|
animatorStateInfos = new vEventSystems.vAnimatorStateInfos(animator);
|
|
animatorStateInfos.RegisterListener();
|
|
|
|
hitDirectionHash = new vAnimatorParameter(animator, "HitDirection");
|
|
reactionIDHash = new vAnimatorParameter(animator, "ReactionID");
|
|
triggerReactionHash = new vAnimatorParameter(animator, "TriggerReaction");
|
|
triggerResetStateHash = new vAnimatorParameter(animator, "ResetState");
|
|
recoilIDHash = new vAnimatorParameter(animator, "RecoilID");
|
|
triggerRecoilHash = new vAnimatorParameter(animator, "TriggerRecoil");
|
|
}
|
|
// slides the character through walls and edges
|
|
frictionPhysics = new PhysicsMaterial();
|
|
frictionPhysics.name = "frictionPhysics";
|
|
frictionPhysics.staticFriction = .25f;
|
|
frictionPhysics.dynamicFriction = .25f;
|
|
frictionPhysics.frictionCombine = PhysicsMaterialCombine.Multiply;
|
|
|
|
// prevents the collider from slipping on ramps
|
|
maxFrictionPhysics = new PhysicsMaterial();
|
|
maxFrictionPhysics.name = "maxFrictionPhysics";
|
|
maxFrictionPhysics.staticFriction = 1f;
|
|
maxFrictionPhysics.dynamicFriction = 1f;
|
|
maxFrictionPhysics.frictionCombine = PhysicsMaterialCombine.Maximum;
|
|
|
|
// air physics
|
|
slippyPhysics = new PhysicsMaterial();
|
|
slippyPhysics.name = "slippyPhysics";
|
|
slippyPhysics.staticFriction = 0f;
|
|
slippyPhysics.dynamicFriction = 0f;
|
|
slippyPhysics.frictionCombine = PhysicsMaterialCombine.Minimum;
|
|
|
|
targetDirection = transform.forward;
|
|
_rigidbody = GetComponent<Rigidbody>();
|
|
_capsuleCollider = GetComponent<CapsuleCollider>();
|
|
colliderCenter = _capsuleCollider.center;
|
|
colliderHeight = _capsuleCollider.height;
|
|
currentHealth = maxHealth;
|
|
Collider[] AllColliders = this.GetComponentsInChildren<Collider>();
|
|
for (int i = 0; i < AllColliders.Length; i++)
|
|
{
|
|
Physics.IgnoreCollision(_capsuleCollider, AllColliders[i]);
|
|
}
|
|
IsGroundedAnim = isGrounded = true;
|
|
targetDirection = transform.forward;
|
|
if (disableControllerOnStart)
|
|
{
|
|
DisableAIController();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
protected virtual void OnEnable()
|
|
{
|
|
if (animatorStateInfos != null && animator)
|
|
{
|
|
animatorStateInfos.RegisterListener();
|
|
}
|
|
}
|
|
|
|
protected virtual void OnDisable()
|
|
{
|
|
if (animatorStateInfos != null && animator)
|
|
{
|
|
animatorStateInfos.RemoveListener();
|
|
}
|
|
}
|
|
|
|
protected void Update()
|
|
{
|
|
UpdateAI();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region PROTECTED VIRTUAL METHODS.Update AI
|
|
|
|
protected virtual void UpdateAI()
|
|
{
|
|
UpdateLocomotion();
|
|
UpdateAnimator();
|
|
onUpdateAI.Invoke();
|
|
}
|
|
|
|
protected virtual void SetMovementInput(Vector3 input)
|
|
{
|
|
targetDirection = transform.TransformDirection(input).normalized;
|
|
this.input = input;
|
|
}
|
|
|
|
protected virtual void SetMovementInput(Vector3 input, float smooth)
|
|
{
|
|
|
|
targetDirection = transform.TransformDirection(input).normalized;
|
|
this.input = Vector3.Lerp(this.input, input, smooth * Time.deltaTime);
|
|
}
|
|
|
|
protected virtual void SetMovementInput(Vector3 input, Vector3 targetDirection, float smooth)
|
|
{
|
|
this.targetDirection = targetDirection.normalized;
|
|
this.input = Vector3.Lerp(this.input, input, smooth * Time.deltaTime);
|
|
}
|
|
|
|
protected virtual void UpdateLocomotion()
|
|
{
|
|
StepOffset();
|
|
ControlLocomotion();
|
|
PhysicsBehaviour();
|
|
CheckGroundDistance();
|
|
CheckAutoCrouch();
|
|
}
|
|
|
|
protected virtual void ControlLocomotion()
|
|
{
|
|
if (isDead || isJumping || !isGrounded)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CalculateRotationMagnitude();
|
|
|
|
if (isStrafing)
|
|
{
|
|
StrafeMovement();
|
|
}
|
|
else
|
|
{
|
|
FreeMovement();
|
|
}
|
|
if (temporaryDirectionTime > 0) temporaryDirectionTime -= Time.deltaTime;
|
|
|
|
}
|
|
|
|
protected virtual void StrafeMovement()
|
|
{
|
|
StrafeLimitSpeed(maxSpeed);
|
|
if (stopMove)
|
|
{
|
|
strafeMagnitude = 0f;
|
|
}
|
|
|
|
var rotDir = targetDirection.normalized;
|
|
|
|
rotDir.y = 0;
|
|
if (rotDir.magnitude > 0.1f && input.magnitude > 0.1f && !isRolling && !ragdolled)
|
|
{
|
|
if (!lockRotation)
|
|
{
|
|
Rotate(rotDir);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (temporaryDirectionTime > 0)
|
|
{
|
|
Rotate(temporaryDirection);
|
|
}
|
|
}
|
|
|
|
animator.SetFloat("InputMagnitude", isJumping ? 0 : strafeMagnitude, inputMagnitudeSmooth, Time.deltaTime);
|
|
}
|
|
|
|
protected virtual void CalculateRotationMagnitude()
|
|
{
|
|
var eulerDifference = this.transform.eulerAngles - lastCharacterAngle;
|
|
var magnitude = (eulerDifference.NormalizeAngle().y / (isStrafing ? strafeSpeed.rotationSpeed : freeSpeed.rotationSpeed));
|
|
rotationMagnitude = magnitude;
|
|
lastCharacterAngle = transform.eulerAngles;
|
|
}
|
|
|
|
protected virtual void StrafeLimitSpeed(float value)
|
|
{
|
|
var _speed = Mathf.Clamp(input.z, -maxSpeed, maxSpeed);
|
|
var _direction = Mathf.Clamp(input.x, -maxSpeed, maxSpeed);
|
|
speed = _speed;
|
|
direction = _direction;
|
|
var newInput = new Vector2(speed, direction);
|
|
strafeMagnitude = Mathf.Clamp(newInput.magnitude, 0, maxSpeed);
|
|
}
|
|
|
|
protected virtual float rotationSpeed
|
|
{
|
|
get
|
|
{
|
|
if (lockRotation)
|
|
{
|
|
return 0f;
|
|
}
|
|
else
|
|
{
|
|
return useCustomRotationSpeed ? customRotationSpeed : isStrafing ? strafeSpeed.rotationSpeed : freeSpeed.rotationSpeed;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected virtual void FreeMovement()
|
|
{
|
|
if (!animator)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// set speed to both vertical and horizontal inputs
|
|
speed = Mathf.Abs(input.x) + Mathf.Abs(input.z);
|
|
//Limit speed by movementSpeedType
|
|
speed = Mathf.Clamp(speed, 0, maxSpeed);
|
|
if (stopMove)
|
|
{
|
|
speed = 0f;
|
|
}
|
|
|
|
animator.SetFloat("InputMagnitude", isJumping ? 0 : speed, inputMagnitudeSmooth, Time.deltaTime);
|
|
|
|
var conditions = (!actions);
|
|
|
|
if (input.magnitude > 0.1f && targetDirection.magnitude > 0.2f && conditions)
|
|
{
|
|
Vector3 lookDirection = targetDirection.normalized;
|
|
freeRotation = Quaternion.LookRotation(lookDirection, transform.up);
|
|
var eulerY = freeRotation.eulerAngles.y;
|
|
var euler = new Vector3(transform.eulerAngles.x, eulerY, transform.eulerAngles.z);
|
|
if (!lockRotation && !isRolling && speed > 0.1f && !ragdolled)
|
|
{
|
|
Rotate(Quaternion.Euler(euler));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (temporaryDirectionTime > 0)
|
|
{
|
|
Rotate(temporaryDirection);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
protected virtual void Rotate(Vector3 targetDirection)
|
|
{
|
|
|
|
targetDirection.y = 0f;
|
|
if (targetDirection.magnitude > 0.1f)
|
|
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(targetDirection, Vector3.up), rotationSpeed * Time.deltaTime);
|
|
}
|
|
|
|
protected virtual void Rotate(Quaternion targetRotation)
|
|
{
|
|
transform.rotation = Quaternion.Lerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
|
|
}
|
|
|
|
protected virtual bool isSprinting
|
|
{
|
|
get
|
|
{
|
|
return movementSpeed == vAIMovementSpeed.Sprinting;
|
|
}
|
|
}
|
|
|
|
protected virtual float maxSpeed
|
|
{
|
|
get
|
|
{
|
|
switch (movementSpeed)
|
|
{
|
|
case vAIMovementSpeed.Idle:
|
|
return 0;
|
|
case vAIMovementSpeed.Walking:
|
|
return 0.5f;
|
|
case vAIMovementSpeed.Running:
|
|
return 1;
|
|
case vAIMovementSpeed.Sprinting:
|
|
return 1.5f;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected virtual void StepOffset()
|
|
{
|
|
if (input.sqrMagnitude < 0.1 || !isGrounded || !_useStepOffSet || isJumping)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var _hit = new RaycastHit();
|
|
var _movementDirection = isStrafing && input.magnitude > 0 ? (transform.right * input.x + transform.forward * input.z).normalized : transform.forward;
|
|
Ray rayStep = new Ray((transform.position + new Vector3(0, stepOffsetEnd, 0) + _movementDirection * ((_capsuleCollider).radius + 0.05f)), Vector3.down);
|
|
|
|
if (Physics.Raycast(rayStep, out _hit, stepOffsetEnd - stepOffsetStart, groundLayer))
|
|
{
|
|
if (_hit.point.y >= (transform.position.y) && _hit.point.y <= (transform.position.y + stepOffsetEnd))
|
|
{
|
|
var _speed = isStrafing ? Mathf.Clamp(input.magnitude, 0, 1f) : speed;
|
|
var velocityDirection = (_hit.point - transform.position);
|
|
var vel = _rigidbody.linearVelocity;
|
|
vel.y = (velocityDirection * stepSmooth * (_speed * (velocity > 1 ? velocity : 1))).y;
|
|
|
|
_rigidbody.linearVelocity = vel;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected virtual void CheckGroundDistance()
|
|
{
|
|
if (_capsuleCollider != null && (_rigidbody.linearVelocity.y > 0.1f || _rigidbody.linearVelocity.y < -0.1f) || isJumping)
|
|
{
|
|
var dist = 10f;
|
|
|
|
if (Physics.Raycast(transform.position + transform.up * _capsuleCollider.height * 0.5f, Vector3.down, out groundHit, _capsuleCollider.height, groundLayer))
|
|
{
|
|
dist = transform.position.y - groundHit.point.y;
|
|
}
|
|
else if (Physics.SphereCast(transform.position + transform.up * _capsuleCollider.radius, _capsuleCollider.radius * 0.5f, Vector3.down, out groundHit, checkGroundDistance, groundLayer))
|
|
{
|
|
dist = transform.position.y - groundHit.point.y;
|
|
}
|
|
|
|
GroundDistanceAnim = dist;
|
|
|
|
if (dist >= checkGroundDistance)
|
|
{
|
|
isGrounded = false;
|
|
verticalVelocity = _rigidbody.linearVelocity.y;
|
|
}
|
|
if ((!actions || isJumping) && !isRolling && dist < checkGroundDistance * 0.9f)
|
|
{
|
|
isGrounded = true;
|
|
}
|
|
|
|
}
|
|
else if (!isJumping)
|
|
{
|
|
GroundDistanceAnim = 0f;
|
|
isGrounded = true;
|
|
}
|
|
}
|
|
|
|
protected virtual void PhysicsBehaviour()
|
|
{
|
|
if (isGrounded && input == Vector3.zero)
|
|
{
|
|
_capsuleCollider.material = maxFrictionPhysics;
|
|
}
|
|
else if (isGrounded && input != Vector3.zero)
|
|
{
|
|
_capsuleCollider.material = frictionPhysics;
|
|
}
|
|
else
|
|
{
|
|
_capsuleCollider.material = slippyPhysics;
|
|
}
|
|
}
|
|
|
|
protected virtual void CheckAutoCrouch()
|
|
{
|
|
if (!useAutoCrouch)
|
|
{
|
|
return;
|
|
}
|
|
// radius of SphereCast
|
|
float radius = _capsuleCollider.radius + headDetectMargin;
|
|
// Position of SphereCast origin stating in base of capsule
|
|
Vector3 pos = transform.position + Vector3.up * colliderHeight * headDetectStart;
|
|
// ray for SphereCast
|
|
Ray ray2 = new Ray(pos, Vector3.up);
|
|
RaycastHit groundHit;
|
|
// sphere cast around the base of capsule for check ground distance
|
|
if (Physics.SphereCast(ray2, radius, out groundHit, (headDetectHeight + (_capsuleCollider.radius)), autoCrouchLayer))
|
|
{
|
|
if (!_isCrouchingFromCast)
|
|
{
|
|
_isCrouchingFromCast = true;
|
|
_capsuleCollider.center = colliderCenter / 1.8f;
|
|
_capsuleCollider.height = colliderHeight / 1.8f;
|
|
}
|
|
}
|
|
else if (_isCrouchingFromCast)
|
|
{
|
|
_isCrouchingFromCast = false;
|
|
// back to the original values
|
|
_capsuleCollider.center = colliderCenter;
|
|
_capsuleCollider.height = colliderHeight;
|
|
}
|
|
}
|
|
public override bool isDead
|
|
{
|
|
get => base.isDead;
|
|
set
|
|
{
|
|
base.isDead = value;
|
|
if (value)
|
|
{
|
|
if (isGrounded)
|
|
{
|
|
if (_rigidbody) _rigidbody.isKinematic = true;
|
|
if (_capsuleCollider) _capsuleCollider.enabled = false;
|
|
}
|
|
}
|
|
else if (!ragdolled)
|
|
{
|
|
if (_rigidbody) _rigidbody.isKinematic = false;
|
|
if (_capsuleCollider) _capsuleCollider.enabled = true;
|
|
}
|
|
if (animator) animator.SetBool("isDead", value);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region PROTECTED VIRTUAL METHODS.Update Animator
|
|
|
|
protected virtual void UpdateAnimator()
|
|
{
|
|
if (animator == null || !animator.isActiveAndEnabled)
|
|
{
|
|
return;
|
|
}
|
|
|
|
animator.speed = animatorSpeed;
|
|
AnimatorLayerControl();
|
|
AnimatorLocomotion();
|
|
AnimatorDeath();
|
|
ActionsControl();
|
|
}
|
|
|
|
protected virtual bool IsStrafingAnim
|
|
{
|
|
get { return _isStrafingRef; }
|
|
set
|
|
{
|
|
if (_isStrafingRef != value || animator.GetBool("IsStrafing") != value)
|
|
{
|
|
_isStrafingRef = value;
|
|
animator.SetBool("IsStrafing", value);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected virtual bool IsGroundedAnim
|
|
{
|
|
get { return _isGroundedRef; }
|
|
set
|
|
{
|
|
if (_isGroundedRef != value)
|
|
{
|
|
_isGroundedRef = value;
|
|
animator.SetBool("IsGrounded", value);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected virtual bool IsCrouchingAnim
|
|
{
|
|
get { return _isCrouchingRef; }
|
|
set
|
|
{
|
|
if (_isCrouchingRef != value)
|
|
{
|
|
_isCrouchingRef = value;
|
|
animator.SetBool("IsCrouching", value);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected virtual float GroundDistanceAnim
|
|
{
|
|
get { return _groundDistanceRef; }
|
|
set
|
|
{
|
|
if (_groundDistanceRef != value)
|
|
{
|
|
_groundDistanceRef = value;
|
|
animator.SetFloat("GroundDistance", value);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected virtual float VerticalVelocityAnim
|
|
{
|
|
get { return _verticalVelocityRef; }
|
|
set
|
|
{
|
|
if (_verticalVelocityRef != value)
|
|
{
|
|
_verticalVelocityRef = value;
|
|
animator.SetFloat("VerticalVelocity", value);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected virtual void AnimatorLocomotion()
|
|
{
|
|
var canMove = !stopMove && !lockMovement && !animatorStateInfos.HasTag("LockMovement");
|
|
animator.SetFloat("InputHorizontal", canMove && isStrafing && !isSprinting ? direction : 0f, .2f, Time.deltaTime);
|
|
animator.SetFloat("InputVertical", canMove ? speed : 0f, .2f, Time.deltaTime);
|
|
|
|
IsStrafingAnim = isStrafing;
|
|
IsGroundedAnim = isGrounded;
|
|
IsCrouchingAnim = isCrouching;
|
|
VerticalVelocityAnim = verticalVelocity;
|
|
}
|
|
|
|
protected virtual void AnimatorLayerControl()
|
|
{
|
|
if (baseLayer != -1)
|
|
{
|
|
baseLayerInfo = animator.GetCurrentAnimatorStateInfo(baseLayer);
|
|
}
|
|
|
|
if (underBodyLayer != -1)
|
|
{
|
|
underBodyInfo = animator.GetCurrentAnimatorStateInfo(underBodyLayer);
|
|
}
|
|
|
|
if (rightArmLayer != -1)
|
|
{
|
|
rightArmInfo = animator.GetCurrentAnimatorStateInfo(rightArmLayer);
|
|
}
|
|
|
|
if (leftArmLayer != -1)
|
|
{
|
|
leftArmInfo = animator.GetCurrentAnimatorStateInfo(leftArmLayer);
|
|
}
|
|
|
|
if (upperBodyLayer != -1)
|
|
{
|
|
upperBodyInfo = animator.GetCurrentAnimatorStateInfo(upperBodyLayer);
|
|
}
|
|
|
|
if (fullbodyLayer != -1)
|
|
{
|
|
fullBodyInfo = animator.GetCurrentAnimatorStateInfo(fullbodyLayer);
|
|
}
|
|
}
|
|
|
|
protected virtual void AnimatorDeath()
|
|
{
|
|
if (!isDead) return;
|
|
|
|
// death by animation
|
|
if (deathBy == DeathBy.Animation)
|
|
{
|
|
int deadLayer = 0;
|
|
var info = animatorStateInfos.GetStateInfoUsingTag("Dead");
|
|
if (info != null)
|
|
{
|
|
if (!animator.IsInTransition(deadLayer) && info.normalizedTime >= 0.99f && GroundDistanceAnim <= 0.15f)
|
|
{
|
|
RemoveComponents();
|
|
}
|
|
}
|
|
}
|
|
// death by animation & ragdoll after a time
|
|
else if (deathBy == DeathBy.AnimationWithRagdoll)
|
|
{
|
|
int deadLayer = 0;
|
|
var info = animatorStateInfos.GetStateInfoUsingTag("Dead");
|
|
if (info != null)
|
|
{
|
|
if (!animator.IsInTransition(deadLayer) && info.normalizedTime >= 0.8f && GroundDistanceAnim <= 0.15f)
|
|
{
|
|
onActiveRagdoll.Invoke(null);
|
|
}
|
|
}
|
|
}
|
|
// death by ragdoll
|
|
else if (deathBy == DeathBy.Ragdoll)
|
|
{
|
|
onActiveRagdoll.Invoke(null);
|
|
}
|
|
}
|
|
public virtual void RemoveComponents()
|
|
{
|
|
if (!removeComponentsAfterDie)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_capsuleCollider != null)
|
|
{
|
|
Destroy(_capsuleCollider);
|
|
}
|
|
|
|
if (_rigidbody != null)
|
|
{
|
|
Destroy(_rigidbody);
|
|
}
|
|
|
|
if (animator != null)
|
|
{
|
|
Destroy(animator);
|
|
}
|
|
|
|
var comps = GetComponents<MonoBehaviour>();
|
|
for (int i = 0; i < comps.Length; i++)
|
|
{
|
|
Destroy(comps[i]);
|
|
}
|
|
}
|
|
|
|
protected virtual void ControlSpeed(float velocity)
|
|
{
|
|
if (Time.deltaTime == 0 || isJumping || _rigidbody.isKinematic)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var canMove = !stopMove && !lockMovement && !animatorStateInfos.HasTag("LockMovement");
|
|
if (!canMove)
|
|
{
|
|
velocity = 0;
|
|
}
|
|
|
|
if (useRootMotion && !actions && !customAction && canMove)
|
|
{
|
|
this.velocity = velocity;
|
|
var deltaPosition = new Vector3(animator.deltaPosition.x, transform.position.y, animator.deltaPosition.z);
|
|
Vector3 v = (deltaPosition * (velocity > 0 ? velocity : 1f)) / Time.deltaTime;
|
|
v.y = _rigidbody.linearVelocity.y;
|
|
_rigidbody.linearVelocity = Vector3.Lerp(_rigidbody.linearVelocity, v, 20f * Time.deltaTime);
|
|
}
|
|
else if (actions || isDead || !canMove || customAction)
|
|
{
|
|
this.velocity = velocity;
|
|
Vector3 v = Vector3.zero;
|
|
v.y = _rigidbody.linearVelocity.y;
|
|
_rigidbody.linearVelocity = v;
|
|
transform.position = animator.rootPosition;
|
|
}
|
|
else
|
|
{
|
|
if (isStrafing)
|
|
{
|
|
Vector3 v = (transform.TransformDirection(new Vector3(input.x, 0, input.z)) * (velocity > 0 ? velocity : 1f));
|
|
v.y = _rigidbody.linearVelocity.y;
|
|
_rigidbody.linearVelocity = Vector3.Lerp(_rigidbody.linearVelocity, v, 20f * Time.deltaTime);
|
|
}
|
|
else
|
|
{
|
|
var _targetVelocity = transform.forward * velocity * speed;
|
|
_targetVelocity.y = _rigidbody.linearVelocity.y;
|
|
_rigidbody.linearVelocity = _targetVelocity;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected virtual void ActionsControl()
|
|
{
|
|
// to have better control of your actions, you can filter the animations state using bools
|
|
// this way you can know exactly what animation state the character is playing
|
|
|
|
isRolling = baseLayerInfo.IsName("Roll");
|
|
|
|
// locks player movement while a animation with tag 'LockMovement' is playing
|
|
UpdateLockMovement();
|
|
UpdateLockRotation();
|
|
// ! -- you can add the Tag "CustomAction" into a AnimatonState and the character will not perform any Melee action -- !
|
|
UpdateCustomAction();
|
|
}
|
|
|
|
protected virtual void UpdateLockMovement()
|
|
{
|
|
lockMovement = IsAnimatorTag("LockMovement");
|
|
}
|
|
|
|
protected virtual void UpdateLockRotation()
|
|
{
|
|
lockRotation = IsAnimatorTag("LockRotation");
|
|
}
|
|
|
|
public virtual void UpdateCustomAction()
|
|
{
|
|
customAction = IsAnimatorTag("CustomAction");
|
|
}
|
|
|
|
protected virtual void OnAnimatorMove()
|
|
{
|
|
// we implement this function to override the default root motion.
|
|
// this allows us to modify the positional speed before it's applied.
|
|
if (animator && isGrounded && !ragdolled)
|
|
{
|
|
// use root rotation for custom actions or
|
|
if (customAction || input.magnitude < 0.1f)
|
|
{
|
|
//_rigidbody.velocity = Vector3.zero;
|
|
_rigidbody.position = animator.rootPosition;
|
|
transform.rotation = animator.rootRotation;
|
|
|
|
return;
|
|
}
|
|
if (lockMovement)
|
|
{
|
|
//_rigidbody.velocity = Vector3.zero;
|
|
_rigidbody.position = animator.rootPosition;
|
|
if (lockRotation)
|
|
{
|
|
transform.rotation = animator.rootRotation;
|
|
}
|
|
|
|
return;
|
|
}
|
|
if (lockRotation)
|
|
{
|
|
_rigidbody.rotation = animator.rootRotation;
|
|
}
|
|
|
|
var a_strafeSpeed = Mathf.Abs(strafeMagnitude);
|
|
|
|
// strafe extra speed
|
|
if (isStrafing)
|
|
{
|
|
if (a_strafeSpeed <= 0.5f)
|
|
{
|
|
ControlSpeed(strafeSpeed.walkSpeed);
|
|
}
|
|
else if (a_strafeSpeed > 0.5f && a_strafeSpeed <= 1f)
|
|
{
|
|
ControlSpeed(strafeSpeed.runningSpeed);
|
|
}
|
|
else
|
|
{
|
|
ControlSpeed(strafeSpeed.sprintSpeed);
|
|
}
|
|
|
|
if (isCrouching)
|
|
{
|
|
ControlSpeed(strafeSpeed.crouchSpeed);
|
|
}
|
|
}
|
|
else if (!isStrafing)
|
|
{
|
|
// free extra speed
|
|
if (speed <= 0.5f)
|
|
{
|
|
ControlSpeed(freeSpeed.walkSpeed);
|
|
}
|
|
else if (speed > 0.5 && speed <= 1f)
|
|
{
|
|
ControlSpeed(freeSpeed.runningSpeed);
|
|
}
|
|
else
|
|
{
|
|
ControlSpeed(freeSpeed.sprintSpeed);
|
|
}
|
|
|
|
if (isCrouching)
|
|
{
|
|
ControlSpeed(freeSpeed.crouchSpeed);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region PUBLIC VIRTUAL METHODS. AI
|
|
|
|
/// <summary>
|
|
/// Check Current Animator State tag
|
|
/// </summary>
|
|
/// <param name="tag"></param>
|
|
/// <returns></returns>
|
|
public virtual bool IsAnimatorTag(string tag)
|
|
{
|
|
if (animator == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (animatorStateInfos.HasTag(tag))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (baseLayerInfo.IsTag(tag))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (underBodyInfo.IsTag(tag))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (rightArmInfo.IsTag(tag))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (leftArmInfo.IsTag(tag))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (upperBodyInfo.IsTag(tag))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (fullBodyInfo.IsTag(tag))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set Movement speed to 0 (zero)
|
|
/// </summary>
|
|
public virtual void Stop()
|
|
{
|
|
//targetDirection = transform.forward;
|
|
if (input != Vector3.zero)
|
|
{
|
|
// _turnOnSpotDirection = transform.forward;
|
|
input = Vector3.zero;
|
|
}
|
|
|
|
movementSpeed = vAIMovementSpeed.Idle;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set Movement speed to WalkSpeed
|
|
/// </summary>
|
|
public virtual void Walk()
|
|
{
|
|
movementSpeed = vAIMovementSpeed.Walking;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set Movement speed to RunSpeed
|
|
/// </summary>
|
|
public virtual void Run()
|
|
{
|
|
movementSpeed = vAIMovementSpeed.Running;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set Movement speed to SprintSpeed
|
|
/// </summary>
|
|
public virtual void Sprint()
|
|
{
|
|
movementSpeed = vAIMovementSpeed.Sprinting;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Jump To target point
|
|
/// </summary>
|
|
/// <param name="jumpTarget">target point</param>
|
|
public virtual void JumpTo(Vector3 jumpTarget)
|
|
{
|
|
// if (animator.IsInTransition(0)) return;
|
|
if (isGrounded && !lockMovement && !actions && !isJumping)
|
|
{
|
|
animator.CrossFadeInFixedTime("JumpMove", .1f);
|
|
StartCoroutine(JumpParabole(jumpTarget, jumpHeight, jumpSpeedPerMeter));
|
|
//// isStrafing = false;
|
|
//targetDirection = jumpTarget - transform.position;
|
|
//_rigidbody.velocity = Vector3.zero;
|
|
//var jumpDistance = Vector3.Distance(transform.position, jumpTarget) + (_capsuleCollider.radius * 2f);
|
|
//var dir = targetDirection;
|
|
//dir.y = 0;
|
|
//transform.rotation = Quaternion.LookRotation(dir);
|
|
//var force = GetJumpForce(jumpTarget + targetDirection.normalized * _capsuleCollider.radius, jumpDistance * jumpSpeedPerMeter);
|
|
//_rigidbody.AddForce(force, ForceMode.VelocityChange);
|
|
//canExitJump = false;
|
|
//isJumping = true;
|
|
//StartCoroutine(ResetJump());
|
|
}
|
|
}
|
|
|
|
protected virtual IEnumerator JumpParabole(Vector3 targetPos, float height, float duration)
|
|
{
|
|
animator.CrossFadeInFixedTime("JumpMove", .1f);
|
|
isJumping = true;
|
|
Vector3 startPos = transform.position;
|
|
Vector3 endPos = targetPos;
|
|
float normalizedTime = 0.0f;
|
|
Vector3 jumpDir = targetPos - transform.position;
|
|
jumpDir.y = 0;
|
|
while (normalizedTime < 1.0f)
|
|
{
|
|
float yOffset = height * 4.0f * (normalizedTime - normalizedTime * normalizedTime);
|
|
transform.transform.position = Vector3.Lerp(startPos, endPos, normalizedTime) + yOffset * Vector3.up;
|
|
normalizedTime += Time.deltaTime / duration;
|
|
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(jumpDir), freeSpeed.rotationSpeed * Time.deltaTime);
|
|
yield return null;
|
|
}
|
|
isJumping = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Roll to target point
|
|
/// </summary>
|
|
/// <param name="direction">target point</param>
|
|
public virtual void RollTo(Vector3 direction)
|
|
{
|
|
if (!isRolling && !isJumping && isGrounded && !lockMovement && !customAction)
|
|
{
|
|
targetDirection = direction.normalized;
|
|
targetDirection.y = 0;
|
|
animator.SetTrigger("ResetState");
|
|
animator.CrossFadeInFixedTime("Roll", 0.01f);
|
|
transform.rotation = Quaternion.LookRotation(targetDirection);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set Strafe Locomotion type
|
|
/// </summary>
|
|
public virtual void SetStrafeLocomotion()
|
|
{
|
|
isStrafing = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set Free Locomotion type
|
|
/// </summary>
|
|
public virtual void SetFreeLocomotion()
|
|
{
|
|
isStrafing = false;
|
|
}
|
|
|
|
public virtual void EnableAIController()
|
|
{
|
|
if (!gameObject.activeInHierarchy)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_rigidbody.isKinematic = false;
|
|
_capsuleCollider.isTrigger = false;
|
|
enabled = true;
|
|
onEnableController.Invoke();
|
|
}
|
|
|
|
public virtual void DisableAIController()
|
|
{
|
|
if (!gameObject.activeInHierarchy)
|
|
{
|
|
return;
|
|
}
|
|
|
|
targetDirection = transform.forward;
|
|
input = Vector3.zero;
|
|
movementSpeed = vAIMovementSpeed.Idle;
|
|
if (animator.isActiveAndEnabled)
|
|
{
|
|
animator.SetFloat("InputHorizontal", 0f);
|
|
animator.SetFloat("InputVertical", 0f);
|
|
animator.SetFloat("InputMagnitude", 0f);
|
|
}
|
|
//_rigidbody.velocity = Vector3.zero;
|
|
_rigidbody.isKinematic = true;
|
|
//_capsuleCollider.isTrigger = true;
|
|
enabled = false;
|
|
onDisableController.Invoke();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region OVERRIDE METHODS. HealthController/ICharacter interface
|
|
|
|
public override void TakeDamage(vDamage damage)
|
|
{
|
|
base.TakeDamage(damage);
|
|
if (damage.damageValue > 0)
|
|
{
|
|
TriggerDamageRection(damage);
|
|
}
|
|
}
|
|
|
|
protected virtual void TriggerDamageRection(vDamage damage)
|
|
{
|
|
if (!isRolling)
|
|
{
|
|
if (animator != null && animator.enabled && !damage.activeRagdoll && currentHealth > 0)
|
|
{
|
|
if (hitDirectionHash.isValid)
|
|
{
|
|
animator.SetInteger(hitDirectionHash, (int)transform.HitAngle(damage.sender.position));
|
|
}
|
|
// trigger hitReaction animation
|
|
if (damage.hitReaction)
|
|
{
|
|
// set the ID of the reaction based on the attack animation state of the attacker - Check the MeleeAttackBehaviour script
|
|
if (reactionIDHash.isValid)
|
|
{
|
|
animator.SetInteger(reactionIDHash, damage.reaction_id);
|
|
}
|
|
|
|
if (triggerReactionHash.isValid)
|
|
{
|
|
animator.SetTrigger(triggerReactionHash);
|
|
}
|
|
|
|
if (triggerResetStateHash.isValid)
|
|
{
|
|
animator.SetTrigger(triggerResetStateHash);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (recoilIDHash.isValid)
|
|
{
|
|
animator.SetInteger(recoilIDHash, damage.recoil_id);
|
|
}
|
|
|
|
if (triggerRecoilHash.isValid)
|
|
{
|
|
animator.SetTrigger(triggerRecoilHash);
|
|
}
|
|
|
|
if (triggerResetStateHash.isValid)
|
|
{
|
|
animator.SetTrigger(triggerResetStateHash);
|
|
}
|
|
}
|
|
}
|
|
if (damage.activeRagdoll)
|
|
{
|
|
onActiveRagdoll.Invoke(damage);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void ResetRagdoll()
|
|
{
|
|
lockMovement = false;
|
|
verticalVelocity = 0f;
|
|
ragdolled = false;
|
|
_rigidbody.WakeUp();
|
|
_rigidbody.useGravity = true;
|
|
_rigidbody.isKinematic = false;
|
|
_capsuleCollider.enabled = true;
|
|
}
|
|
|
|
public override void EnableRagdoll()
|
|
{
|
|
animator.SetFloat("InputHorizontal", 0f);
|
|
animator.SetFloat("InputVertical", 0f);
|
|
animator.SetFloat("VerticalVelocity", 0f);
|
|
ragdolled = true;
|
|
_capsuleCollider.enabled = false;
|
|
_rigidbody.useGravity = false;
|
|
_rigidbody.isKinematic = true;
|
|
lockMovement = true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
[System.Serializable]
|
|
public class vMovementSpeed
|
|
{
|
|
[Tooltip("Rotation speed of the character")]
|
|
public float rotationSpeed = 10f;
|
|
[Tooltip("Speed to Walk using rigibody force or extra speed if you're using RootMotion")]
|
|
public float walkSpeed = 2f;
|
|
[Tooltip("Speed to Run using rigibody force or extra speed if you're using RootMotion")]
|
|
public float runningSpeed = 3f;
|
|
[Tooltip("Speed to Sprint using rigibody force or extra speed if you're using RootMotion")]
|
|
public float sprintSpeed = 4f;
|
|
[Tooltip("Speed to Crouch using rigibody force or extra speed if you're using RootMotion")]
|
|
public float crouchSpeed = 2f;
|
|
}
|
|
}
|
|
}
|