using UnityEngine; using UnityEngine.AI; public class SkinlessMonsterComponent : MonoBehaviour { [SerializeField] private NavMeshAgent agent; [SerializeField] private Enemy.SkinlessMonsterAnimator animator; [SerializeField] private float atTargetDistance = 2; [SerializeField] private float health = 2f; [SerializeField] [Tooltip("This is the angle of visibility the enemy has")] private float visibilityConeLimit = 90f; [SerializeField] private float bulletSoundRange = 15f; [SerializeField] private float newTargetCooldown = 5f; //private bool prePauseStoppedState = false; [SerializeField] private BoxCollider leftHandDamage; [SerializeField] private BoxCollider rightHandDamage; private bool atTarget; private float distanceToPlayer; private Item.FlareRegister flareRegister; private bool inDamageMargin; private bool inDamageRange; private bool isAlive = true; private Vector3 oppositeVector; private Player.PlayerComponent player; private TargetInformation target; private GameObject targetObject; private float timeSinceTarget; private void Awake() { //Find active player rn. var players = FindObjectsOfType(); foreach (var p in players) if (p.isActiveAndEnabled) player = p; } private void Start() { flareRegister = FindObjectOfType(); if (targetObject == null) targetObject = new GameObject(); targetObject.name = "Enemy Target"; if (player == null) player = FindObjectOfType(); } private void Update() { if (!player.IsAlive) Stop(); if (target != null) HandleTargetOperations(); CheckForAOERanges(); if (isAlive && player.IsAlive) SetLiveTargeting(); timeSinceTarget += Time.deltaTime; if (isAlive) { leftHandDamage.enabled = true; rightHandDamage.enabled = true; } else { leftHandDamage.enabled = false; rightHandDamage.enabled = false; } /* AI Behavior: A. If not targeting player 1. Get distance to player - if distance < player.NoiseDistance then target player. 2. Raycast to player to test line of sight - if in line of sight and angle between LOS vector and forward vector is < LOF margin, then target player. B. If targeting player 1. If out of sound range and no line of sight then lose target. - stop() 2. If beacon placed down and in range - remove agent - kill() 3. If beacon placed down and in margin: - stop() and wait for player to leave AOE (enforced by navigation). */ } private void OnTriggerEnter(Collider other) { if (other.gameObject.GetComponent() != null) health -= other.gameObject.GetComponent().DamageMagnitude; } private void HandleTargetOperations() { //Update booleans for movement var distToTarget = Vector3.Distance(target.target.transform.position, agent.transform.position); atTarget = atTargetDistance >= distToTarget; if (target.hasDamageRange) { inDamageRange = target.damageRange <= distToTarget; inDamageMargin = target.damageMargin + target.damageRange <= distToTarget; } else { inDamageRange = false; inDamageMargin = false; } //Perform actions. if (target.runTowards) { if (inDamageRange) { //Damage is being dealt animator.InLight(); //Deal Damage health -= Time.deltaTime; if (health < 0 && isAlive) Kill(); } else if (inDamageMargin) { //Effective stop agent.SetDestination(transform.position); animator.StopMoving(); } else { if (atTarget) { //Effective stop agent.SetDestination(transform.position); animator.StopMoving(); if (target.isHostile) { animator.Attack(); animator.SetAttackType(Random.Range(0, 0)); } } else { agent.SetDestination(target.target.transform.position); animator.StartMoving(); animator.NotInLight(); } } } else { //Run away logic if (atTarget || distToTarget < 3) { animator.StopMoving(); } else { var r = new Ray(); r.origin = transform.position; r.direction = oppositeVector; RaycastHit hit; if (Physics.Raycast(r, out hit)) { animator.StartMoving(); agent.SetDestination(hit.point); } else { agent.SetDestination(transform.position + oppositeVector * 100); agent.isStopped = false; } } } if (atTarget && target.isHostile) SlowLookAt(target.target.transform.position - transform.position); } private void SlowLookAt(Vector3 targetVector, float animatedRotationSpeed = 1f) { var relativePos = targetVector; var toRotation = Quaternion.LookRotation(relativePos); transform.rotation = Quaternion.Lerp(transform.rotation, toRotation, animatedRotationSpeed * Time.deltaTime); } private void CheckForAOERanges() { foreach (var bullet in flareRegister.bullets) { var dist = Vector3.Distance(bullet.transform.position, transform.position); if (dist <= bullet.DamageRange) { health = 0; Kill(true); } } foreach (var beacon in flareRegister.beacons) { var dist = Vector3.Distance(beacon.transform.position, transform.position); if (dist <= beacon.Range) health = 0f; } if (health <= 0) Kill(); } public bool isPlayerVisible(bool withAngle) { var r = new Ray(); r.origin = transform.position; r.direction = player.transform.position - transform.position; var toPosition = (player.transform.position - transform.position).normalized; var angleToPosition = Vector3.Angle(transform.forward, toPosition); RaycastHit hit; if (Physics.Raycast(r, out hit)) { var hitObject = hit.transform.gameObject; if (hitObject.GetComponent() != null) //hit player return angleToPosition <= visibilityConeLimit || !withAngle; if (hitObject.GetComponentInParent() != null) //also hit player return angleToPosition <= visibilityConeLimit || !withAngle; } return false; } private void SetLiveTargeting() { if (!isAlive) //this.targetObject.transform.position = transform.position; Stop(); distanceToPlayer = Vector3.Distance(transform.position, player.transform.position); //print("Dist Comparison "+distanceToPlayer.ToString()+" Noise:"+player.NoiseManager.NoiseDistance); var isPlayerVisible = this.isPlayerVisible(true); //check if player is heard or player line of sight if (distanceToPlayer <= player.NoiseManager.NoiseDistance || isPlayerVisible) { //check that nothing in between if (this.isPlayerVisible(false)) Target(player.gameObject, true); } else { if (timeSinceTarget < newTargetCooldown) //no further targeting //Stop(); return; Item.BulletComponent closestBullet = null; var closestDistance = Mathf.Infinity; foreach (var bullet in flareRegister.bullets) { var bDist = Vector3.Distance(bullet.transform.position, transform.position); if (closestBullet == null || (bDist < bulletSoundRange && bDist < closestDistance)) { closestBullet = bullet; closestDistance = bDist; } } if (closestBullet != null && closestBullet.DamageRange == 0) { targetObject.transform.position = closestBullet.transform.position; Target(targetObject); } else { targetObject.transform.position = transform.position; Target(targetObject); Stop(); } } } /* STANDARD BEHAVIOR: - OnSeeing/Hearing Player: - Run towards Player - OnHearing Bolt: - Run towards bolt - OnSeeing Flare: - Run towards flare if not within (flareRange + flareMargin). - This acts like if you are far enough away that you don't feel the pain of the flare - OnSeeing FlareBeacon: - Run away if within (flareBeaconRange + flareBeaconMargin). */ //Runs towards target. If its meant to be hostile, it will melee attack once in range. public void Target(GameObject obj, bool hostile = false) { if (target == null || target.target != obj) { //target = new TargetInformation(obj,hostile,false,runTowards:true); target = new TargetInformation(); target.target = obj; target.isHostile = hostile; target.hasDamageRange = false; target.runTowards = true; timeSinceTarget = 0; } } //Runs towards flare such that it stops randomly within margin before range. public void TargetFlare(GameObject obj, float range, float margin) { if (target == null || target.target != obj) { //target = new TargetInformation(obj, false, true, range, margin,runTowards:true); target = new TargetInformation(); target.target = obj; target.isHostile = false; target.hasDamageRange = true; target.damageRange = range; target.damageMargin = margin; target.runTowards = true; timeSinceTarget = 0; } } /// /// Runs away from object. A minimum range is specified where it no longer takes damage, and a minimum margin is /// speicifed where it can stop running. /// /// /// /// public void RunAwayFrom(GameObject obj, float minRange, float minMargin) { if (target == null || target.target != obj) { //target = new TargetInformation(obj, false, true, minRange, minMargin, false); target = new TargetInformation(); target.target = obj; target.isHostile = false; target.hasDamageRange = true; target.damageRange = minRange; target.damageMargin = minMargin; target.runTowards = false; oppositeVector = -(target.target.transform.position - transform.position).normalized; timeSinceTarget = 0; } } public void Stop() { target = new TargetInformation(); target.target = gameObject; target.isHostile = false; target.hasDamageRange = false; target.runTowards = true; } public void Kill(bool fast = false) { if (isAlive) { animator.Kill(fast); isAlive = false; agent.isStopped = true; } } } internal class TargetInformation { public float damageMargin; public float damageRange; public bool hasDamageRange; public bool isHostile; public bool runTowards; public GameObject target; }