Files
BABA_YAGA/Assets/All for one/Simple Melee AI/Scripts/vSimpleMeleeAI_Motor.cs
2026-06-04 10:42:23 +07:00

730 lines
27 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Invector.vCharacterController.AI
{
using UnityEngine.AI;
using vMelee;
public class vSimpleMeleeAI_Motor : vCharacter
{
#region Variables
#region Layers
[vEditorToolbar("Layers")]
[Tooltip("Layers that the character can walk on")]
public LayerMask groundLayer = 1 << 0;
[Tooltip("Distance to became not grounded")]
[SerializeField]
protected float groundCheckDistance = 0.5f;
[Tooltip("What objects can make the character auto crouch")]
public LayerMask autoCrouchLayer = 1 << 0;
[Tooltip("[SPHERECAST] ADJUST IN PLAY MODE - White Spherecast put just above the head, this will make the character Auto-Crouch if something hit the sphere.")]
public float headDetect = 0.95f;
#endregion
#region AI variables
[vEditorToolbar("Locomotion")]
[Tooltip("Use to limit your locomotion animation, if you want to patrol walking set this value to 0.5f")]
[Range(0f, 1.5f)]
public float patrolSpeed = 0.5f;
[Tooltip("Use to limit your locomotion animation, if you want to wander walking set this value to 0.5f")]
[Range(0f, 1.5f)]
public float wanderSpeed = 0.5f;
[Tooltip("Use to limit your locomotion animation, if you want to chase the target walking set this value to 0.5f")]
[Range(0f, 1.5f)]
public float chaseSpeed = 1f;
[Tooltip("Use to limit your locomotion animation, if you want to strafe the target walking set this value to 0.5f")]
[Range(0f, 1.5f)]
public float strafeSpeed = 1f;
[Header("--- Strafe ---")]
[Tooltip("Strafe around the target")]
public bool strafeSideways = true;
[Tooltip("Strafe a few steps backwards")]
public bool strafeBackward = true;
[Tooltip("Distance to switch to the strafe locomotion, leave with 0 if you don't want your character to strafe")]
public float strafeDistance = 3f;
[Tooltip("Min time to change the strafe direction")]
public float minStrafeSwape = 2f;
[Tooltip("Max time to change the strafe direction")]
public float maxStrafeSwape = 5f;
[Tooltip("Velocity to rotate the character while strafing")]
public float strafeRotationSpeed = 5f;
[vEditorToolbar("Detection")]
protected AIStates _currentState = AIStates.PatrolWaypoints;
public virtual AIStates currentState { get { return _currentState; } set { _currentState = value; } }
[Header("Who is your Target?")]
public vSimpleMeleeAI_SphereSensor sphereSensor;
public vTagMask tagsToDetect = new vTagMask { "Player" };
public LayerMask layersToDetect = 1 << 0;
public LayerMask obstaclesLayer;
public bool sortTargetFromDistance = false;
[Range(0f, 360f)]
public float fieldOfView = 95f;
[Tooltip("Max Distance to detect the Target with FOV")]
public float maxDetectDistance = 5f;
[Tooltip("Min Distance to noticed the Target without FOV")]
public float minDetectDistance = 2f;
[Tooltip("Distance to lost the Target")]
public float distanceToLostTarget = 5f;
public float lostTargetDistance { get { return maxDetectDistance + distanceToLostTarget; } }
[Tooltip("Distance to stop when chasing the Player")]
public float chaseStopDistance = 1f;
public bool drawAgentPath = false;
public bool displayGizmos;
[vEditorToolbar("Combat")]
[Tooltip("Check if you want the Enemy to be passive even if you attack him")]
public bool passiveToDamage = false;
[Tooltip("Check if you want the Enemy to chase the Target at first sight")]
public bool agressiveAtFirstSight = true;
[Tooltip("Velocity to rotate the character while attacking")]
public float attackRotationSpeed = 0.5f;
[Tooltip("Delay to trigger the first attack when close to the target")]
public float firstAttackDelay = 0f;
[Tooltip("Min frequency to attack")]
public float minTimeToAttack = 4f;
[Tooltip("Max frequency to attack")]
public float maxTimeToAttack = 6f;
[Tooltip("How many attacks the AI will make on a combo")]
public int maxAttackCount = 3;
[Tooltip("Randomly attacks based on the maxAttackCount")]
public bool randomAttackCount = true;
[Range(0f, 1f)]
public float chanceToRoll = .1f;
[Range(0f, 1f)]
public float chanceToBlockInStrafe = .1f;
[Range(0f, 1f)]
public float chanceToBlockAttack = 0f;
[Tooltip("How much time the character will stand up the shield")]
public float raiseShield = 4f;
[Tooltip("How much time the character will lower the shield")]
public float lowerShield = 2f;
[vEditorToolbar("Waypoint")]
[Tooltip("Max Distance to change waypoint")]
[Range(0.5f, 100f)]
public float distanceToChangeWaypoint = 1f;
[Tooltip("Min Distance to stop when Patrolling through waypoints")]
[Range(0.5f, 100f)]
public float patrollingStopDistance = 0.5f;
public AIPatrolWithOutAreaStyle patrolWithoutAreaStyle = AIPatrolWithOutAreaStyle.GoToStartPoint;
public vWaypointArea pathArea;
public bool randomWaypoints;
public vFisherYatesRandom randomWaypoint = new vFisherYatesRandom();
public vFisherYatesRandom randomPatrolPoint = new vFisherYatesRandom();
[HideInInspector]
public CapsuleCollider _capsuleCollider;
// there is a prefab of health hud example that you can drag and drop into the head bone of your character
[HideInInspector]
public v_SpriteHealth healthSlider;
// attach a meleeManager component to create new hitboxs and set up different weapons
[HideInInspector]
public vMeleeManager meleeManager;
// check your MeleeWeapon Inspector, each weapon can set up different distances to attack
public OnSetAgressiveEvent onSetAgressive = new OnSetAgressiveEvent();
public class OnSetAgressiveEvent : UnityEngine.Events.UnityEvent<bool> { }
[HideInInspector]
public bool lockMovement;
public enum AIPatrolWithOutAreaStyle
{
GoToStartPoint,
Wander,
Idle
}
public enum AIStates
{
Idle,
PatrolSubPoints,
PatrolWaypoints,
Wander,
Chase
}
#endregion
#region Protected Variables
public CharacterTarget currentTarget;// { protected set; get; }
[System.Serializable]
public struct CharacterTarget
{
public Transform transform;
public vIHealthController character;
public Collider colliderTarget;
}
protected Vector3 _targetPos;
protected virtual Vector3 targetPos { get { return _targetPos; } set { _targetPos = value; } }
[SerializeField, vReadOnly]
protected bool _canSeeTarget;
protected virtual bool canSeeTarget { get { return _canSeeTarget; } set { _canSeeTarget = value; } }
protected Vector3 _destination;
protected virtual Vector3 destination { get { return _destination; } set { _destination = value; } }
protected Vector3 _fwd;
protected virtual Vector3 fwd { get { return _fwd; } set { _fwd = value; } }
protected virtual bool _isGrounded { get; set; }
protected virtual bool isGrounded { get { return _isGrounded; } set { _isGrounded = value; } }
protected bool _isStrafing;
protected virtual bool isStrafing { get { return _isStrafing; } set { _isStrafing = value; } }
protected virtual bool inResetAttack { get; set; }
protected virtual bool firstAttack { get; set; }
protected virtual int attackCount { get; set; }
protected virtual int _currentWaypoint { get; set; }
protected virtual int currentWaypoint { get { return _currentWaypoint; } set { _currentWaypoint = value; } }
protected virtual int _currentPatrolPoint { get; set; }
protected virtual int currentPatrolPoint { get { return _currentPatrolPoint; } set { _currentPatrolPoint = value; } }
protected float _direction;
protected virtual float direction { get { return _direction; } set { _direction = value; } }
protected float _timer;
protected virtual float timer { get { return _timer; } set { _timer = value; } }
protected float _wait;
protected virtual float wait { get { return _wait; } set { _wait = value; } }
protected float _fovAngle;
protected virtual float fovAngle { get { return _fovAngle; } set { _fovAngle = value; } }
protected virtual float sideMovement { get; set; }
protected virtual float fwdMovement { get; set; }
protected virtual float strafeSwapeFrequency { get; set; }
protected virtual float groundDistance { get; set; }
protected Vector3 _startPosition;
protected virtual Vector3 startPosition { get { return _startPosition; } set { _startPosition = value; } }
protected RaycastHit groundHit;
protected NavMeshAgent agent;
protected NavMeshPath agentPath;
protected Quaternion freeRotation;
protected Quaternion desiredRotation;
protected Vector3 oldPosition;
protected Vector3 combatMovement;
protected Vector3 rollDirection;
protected Rigidbody _rigidbody;
protected PhysicsMaterial frictionPhysics;
protected Transform head;
protected Collider colliderTarget;
protected vWaypoint targetWaypoint;
protected vPoint targetPatrolPoint;
protected List<vPoint> visitedPatrolPoint = new List<vPoint>();
protected List<vWaypoint> visitedWaypoint = new List<vWaypoint>();
#endregion
#region Actions
protected bool _isCrouched;
protected bool _canAttack;
protected bool _tryingBlock;
protected bool _isRolling;
protected virtual bool isCrouched { get { return _isCrouched; } set { _isCrouched = value; } }
protected virtual bool canAttack { get { return _canAttack; } set { _canAttack = value; } }
protected virtual bool tryingBlock { get { return _tryingBlock; } set { _tryingBlock = value; } }
protected virtual bool isRolling { get { return _isRolling; } set { _isRolling = value; } }
protected bool _isBlocking;
public virtual bool isBlocking { get { return _isBlocking; } protected set { _isBlocking = value; } }
protected bool _isAttacking;
public bool isAttacking { get { return _isAttacking; } protected set { _isAttacking = value; } }
public bool isArmed { get { return meleeManager != null && (meleeManager.rightWeapon != null || (meleeManager.leftWeapon != null && meleeManager.leftWeapon.meleeType != vMeleeType.OnlyDefense)); } }
protected bool _actions;
public virtual bool actions { get { return _actions; } set { _actions = value; } }
#endregion
#endregion
public override void Init()
{
base.Init();
fwd = transform.forward;
destination = transform.position;
agent = GetComponent<UnityEngine.AI.NavMeshAgent>();
agentPath = new UnityEngine.AI.NavMeshPath();
sphereSensor = GetComponentInChildren<vSimpleMeleeAI_SphereSensor>();
if (sphereSensor)
{
sphereSensor.root = transform;
}
meleeManager = GetComponent<vMeleeManager>();
canAttack = true;
attackCount = 0;
sideMovement = GetRandonSide();
destination = transform.position;
_rigidbody = GetComponent<Rigidbody>();
_rigidbody.useGravity = true;
_rigidbody.constraints = RigidbodyConstraints.None | RigidbodyConstraints.FreezeRotation;
agent.updatePosition = false;
agent.updateRotation = false;
agent.enabled = false;
_capsuleCollider = GetComponent<CapsuleCollider>();
// avoid collision detection with inside colliders
Collider[] AllColliders = this.GetComponentsInChildren<Collider>();
Collider thisCollider = GetComponent<Collider>();
for (int i = 0; i < AllColliders.Length; i++)
{
Physics.IgnoreCollision(thisCollider, AllColliders[i]);
}
healthSlider = GetComponentInChildren<v_SpriteHealth>();
head = animator.GetBoneTransform(HumanBodyBones.Head);
oldPosition = transform.position;
if (!fillHealthOnStart) currentHealth = _currentHealth;
startPosition = transform.position;
}
#region AI Locomotion
public float distanceToAttack
{
get { if (meleeManager) { return meleeManager.GetAttackDistance(); } return 1f; }
}
public bool OnCombatArea
{
get
{
if (currentTarget.transform == null)
{
return false;
}
var inFloor = Vector3.Distance(new Vector3(0, transform.position.y, 0), new Vector3(0, currentTarget.transform.position.y, 0)) < distanceToAttack;
return (inFloor && agressiveAtFirstSight && TargetDistance <= strafeDistance && !agent.isOnOffMeshLink);
}
}
public bool OnStrafeArea
{
get
{
if (!canSeeTarget)
{
isStrafing = false;
return false;
}
if (currentTarget.transform == null || !agressiveAtFirstSight)
{
return false;
}
var inFloor = Vector3.Distance(new Vector3(0, transform.position.y, 0), new Vector3(0, currentTarget.transform.position.y, 0)) < 1.5f;
// exit strafe
if (isStrafing)
{
isStrafing = TargetDistance < (strafeDistance + 2f);
}
// enter strafe
else
{
isStrafing = OnCombatArea;
}
return inFloor ? isStrafing : false;
}
}
public bool AgentDone()
{
if (!agent.enabled && agent.updatePosition)
{
return true;
}
return !agent.pathPending && AgentStopping() && agent.updatePosition;
}
public bool AgentStopping()
{
if (!agent.enabled || !agent.isOnNavMesh)
{
return true;
}
return agent.remainingDistance <= agent.stoppingDistance;
}
public int GetRandonSide()
{
var side = UnityEngine.Random.Range(-1, 1);
if (side < 0)
{
side = -1;
}
else
{
side = 1;
}
return side;
}
protected void CheckGroundDistance()
{
if (_capsuleCollider != null)
{
var dist = 10f;
Ray ray1 = new Ray(transform.position + new Vector3(0, _capsuleCollider.height / 2, 0), Vector3.down);
if (Physics.Raycast(ray1, out groundHit, _capsuleCollider.height * 0.75f, groundLayer))
{
dist = transform.position.y - groundHit.point.y;
}
groundDistance = dist;
if (!actions && !isRolling && groundDistance < 0.3f)
{
if (currentHealth > 0)
{
UnityEngine.AI.NavMeshHit navHit;
if (UnityEngine.AI.NavMesh.SamplePosition(transform.position, out navHit, 5f, UnityEngine.AI.NavMesh.AllAreas))
{
_rigidbody.constraints = RigidbodyConstraints.None | RigidbodyConstraints.FreezeRotation | RigidbodyConstraints.FreezePositionX | RigidbodyConstraints.FreezePositionZ;
_rigidbody.useGravity = false;
agent.updatePosition = false;
agent.enabled = true;
}
}
if (agent.enabled && agent.isOnNavMesh && Vector3.Distance(agent.nextPosition, transform.position) <= 0.1f)
{
agent.updatePosition = true;
}
else if (agent.enabled && agent.isOnNavMesh && !agent.updatePosition)
{
agent.nextPosition = transform.position;
}
}
if (!agent.isOnNavMesh && groundDistance > 0.3f && !ragdolled)
{
_rigidbody.useGravity = true;
_rigidbody.constraints = RigidbodyConstraints.None | RigidbodyConstraints.FreezeRotation;
agent.enabled = false;
agent.updatePosition = false;
}
}
}
public void CheckAutoCrouch()
{
// radius of SphereCast
float radius = _capsuleCollider.radius * 0.9f;
// Position of SphereCast origin stating in base of capsule
Vector3 pos = transform.position + Vector3.up * ((_capsuleCollider.height * 0.5f) - _capsuleCollider.radius);
// 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, _capsuleCollider.bounds.max.y - (_capsuleCollider.radius * 0.1f), groundLayer))
if (Physics.SphereCast(ray2, radius, out groundHit, headDetect - (_capsuleCollider.radius * 0.1f), autoCrouchLayer))
{
isCrouched = true;
}
else
{
isCrouched = false;
}
}
#endregion
#region Check Target
/// <summary>
/// Calculate Fov Angle
/// </summary>
/// <returns></returns>
public bool onFovAngle()
{
if (currentTarget.transform == null)
{
return false;
}
var freeRotation = Quaternion.LookRotation(currentTarget.transform.position - transform.position, Vector3.up);
var newAngle = freeRotation.eulerAngles - transform.eulerAngles;
fovAngle = newAngle.NormalizeAngle().y;
if (fovAngle < fieldOfView && fovAngle > -fieldOfView)
{
return true;
}
return false;
}
int canSeeTargetIteration;
/// <summary>
/// Target Detection
/// </summary>
/// <param name="_target"></param>
/// <returns></returns>
public void CheckTarget()
{
if (currentTarget.transform == null || !agressiveAtFirstSight)
{
canSeeTarget = false;
canSeeTargetIteration = 0;
return;
}
if (TargetDistance > maxDetectDistance)
{
canSeeTarget = false;
canSeeTargetIteration = 0;
return;
}
if (currentTarget.colliderTarget == null || currentTarget.colliderTarget.transform != currentTarget.transform)
{
currentTarget.colliderTarget = currentTarget.transform.GetComponent<Collider>();
}
if (currentTarget.colliderTarget == null)
{
canSeeTarget = false;
canSeeTargetIteration = 0;
return;
}
var top = new Vector3(currentTarget.colliderTarget.bounds.center.x, currentTarget.colliderTarget.bounds.max.y, currentTarget.colliderTarget.bounds.center.z);
var bottom = new Vector3(currentTarget.colliderTarget.bounds.center.x, currentTarget.colliderTarget.bounds.min.y, currentTarget.colliderTarget.bounds.center.z);
var offset = Vector3.Distance(top, bottom) * 0.15f;
top.y -= offset;
bottom.y += offset;
if (!onFovAngle() && TargetDistance > minDetectDistance)
{
canSeeTarget = false;
canSeeTargetIteration = 0;
return;
}
RaycastHit hit;
if (canSeeTargetIteration == 0 && !Physics.Linecast(head.position, top, out hit, obstaclesLayer))
{
canSeeTarget = true;
canSeeTargetIteration = 0;
return;
}
else if (canSeeTargetIteration == 1 && !Physics.Linecast(head.position, bottom, out hit, obstaclesLayer))
{
canSeeTarget = true;
canSeeTargetIteration = 0;
return;
}
else if (canSeeTargetIteration == 2 && !Physics.Linecast(head.position, currentTarget.colliderTarget.bounds.center, out hit, obstaclesLayer))
{
canSeeTarget = true;
canSeeTargetIteration = 0;
return;
}
else
{
canSeeTargetIteration++;
}
if (canSeeTargetIteration > 1)
{
canSeeTargetIteration = 0;
}
canSeeTarget = false;
}
public float TargetDistance
{
get
{
if (currentTarget.transform != null)
{
return currentTarget.colliderTarget ? Vector3.Distance(_capsuleCollider.bounds.center, currentTarget.colliderTarget.bounds.center) :
Vector3.Distance(transform.position, currentTarget.transform.position);
}
return maxDetectDistance + 1f;
}
}
public Transform headTarget
{
get
{
if (currentTarget.transform != null && currentTarget.transform.GetComponent<Animator>() != null)
{
return currentTarget.transform.GetComponent<Animator>().GetBoneTransform(HumanBodyBones.Head);
}
else
{
return null;
}
}
}
#endregion
#region AI Health
protected void RemoveComponents()
{
if (!removeComponentsAfterDie)
{
return;
}
if (_capsuleCollider != null)
{
Destroy(_capsuleCollider);
}
if (_rigidbody != null)
{
Destroy(_rigidbody);
}
if (animator != null)
{
Destroy(animator);
}
if (agent != null)
{
Destroy(agent);
}
var comps = GetComponents<MonoBehaviour>();
for (int i = 0; i < comps.Length; i++)
{
Destroy(comps[i]);
}
}
/// <summary>
/// TAKE DAMAGE - you can override the take damage method from the vCharacter and add your own calls
/// </summary>
/// <param name="damage"> damage to apply </param>
public override void TakeDamage(vDamage damage)
{
// ignore damage if the character is rolling, dead or the animator is disable
if (isRolling || currentHealth <= 0 || !animator.enabled)
{
return;
}
if (!damage.ignoreDefense && !actions && CheckChanceToRoll())
{
return;
}
base.TakeDamage(damage);
}
protected override void TriggerDamageReaction(vDamage damage)
{
if (!isRolling)
{
base.TriggerDamageReaction(damage);
}
}
#endregion
#region AI Melee Combat Methods
protected bool CheckChanceToRoll()
{
if (isAttacking || actions)
{
return false;
}
float randomRoll = UnityEngine.Random.value;
if (randomRoll < chanceToRoll && randomRoll > 0 && currentTarget.transform != null)
{
animator.SetTrigger("ResetState");
sideMovement = GetRandonSide();
Ray ray = new Ray(currentTarget.transform.position, currentTarget.transform.right * sideMovement);
rollDirection = ray.direction;
animator.CrossFadeInFixedTime("Roll", 0.1f);
return true;
}
return false;
}
protected IEnumerator CheckChanceToBlock(float chance, float timeToEnter)
{
tryingBlock = true;
float randomBlock = UnityEngine.Random.value;
if (randomBlock < chance && randomBlock > 0 && !isBlocking)
{
if (timeToEnter > 0)
{
yield return new WaitForSeconds(timeToEnter);
}
isBlocking = currentTarget.transform == null || (actions) || isAttacking ? false : true;
StartCoroutine(ResetBlock());
tryingBlock = false;
}
else
{
tryingBlock = false;
}
}
protected IEnumerator ResetBlock()
{
yield return new WaitForSeconds(currentTarget.transform == null ? 0 : raiseShield);
isBlocking = false;
}
protected virtual void SetAggressive(bool value)
{
agressiveAtFirstSight = value;//
onSetAgressive.Invoke(value);
}
int GetDamageResult(int damage, float defenseRate)
{
int result = (int)(damage - ((damage * defenseRate) / 100));
return result;
}
#endregion
#region Ragdoll
public override void ResetRagdoll()
{
oldPosition = transform.position;
ragdolled = false;
_capsuleCollider.isTrigger = false;
_rigidbody.isKinematic = false;
agent.updatePosition = false;
agent.enabled = true;
}
public override void EnableRagdoll()
{
agent.enabled = false;
agent.updatePosition = false;
ragdolled = true;
_rigidbody.isKinematic = true;
_capsuleCollider.isTrigger = true;
}
#endregion
}
}