using System.Collections; using System.Collections.Generic; using UnityEngine; using Pathfinding; public class Enemy : MonoBehaviour { [Header("Health and Damage")] // Health and Damage enemy public variables [SerializeField] int enemyHealth = 100; [SerializeField] int playerDamageMult = 25; [SerializeField] GameObject skeletonRemains; [SerializeField] Color damageColor = Color.red; [SerializeField] float damageTime = 0.5f; [SerializeField] float knockbackStrength = 0.25f; [Header("Misc")] // Misc enemy public variables [SerializeField] AudioClip[] footstepFX; [SerializeField] float radius = 5f; // Enemy private variables LayerMask myLayerMask; Vector3 currentPosition; Vector3 lastPosition; bool isRunning; int facing = 1; bool hitWall = false; bool isDead = false; bool IFrames = false; bool isRunningFX = false; // Components Animator myAnimator; AIPath myAIPath; Renderer objectRenderer; AudioSource audioSource; ParticleSystem bloodParticleSystem; ParticleSystem footstepParticleSystem; Player player; Collider2D myCollider; [Header("GameObjects")] // Enemy GameObject public variables [SerializeField] GameObject potionObject; [SerializeField] GameObject playerObject; [SerializeField] GameObject footstepParticle; [SerializeField] GameObject bloodParticle; // Draws the enemy attack radius, just so I set it more accordingly void OnDrawGizmosSelected() { Gizmos.color = Color.yellow; Gizmos.DrawWireSphere(transform.position, radius); } // Method called on the first frame void Start() { // Get components myAnimator = GetComponent(); myCollider = GetComponent(); myAIPath = GetComponent(); objectRenderer = GetComponent(); audioSource = GetComponent(); footstepParticleSystem = footstepParticle.GetComponent(); bloodParticleSystem = bloodParticle.GetComponent(); player = playerObject.GetComponent(); // Get layermask for player layer myLayerMask = LayerMask.GetMask("Player"); // Start coroutines that run in the background StartCoroutine(RunningCheck()); StartCoroutine(FootstepFunc()); } // Method called every frame void Update() { // Run methods that run every frame FlipSprite(); AlertRadius(); DashIFrames(); Death(); } // Wait for animation corutine IEnumerator WaitForAnimation() { // Wait for death animation to play yield return new WaitForSeconds(0.5f); // Instantiate skeleton remains where enemy died, with a 1/3 chance to instantiate a potion, then destroy itself GameObject newObject = Instantiate(skeletonRemains, transform.position, transform.rotation); newObject.transform.localScale = transform.localScale; int potionChance = Random.Range(0, 3); if (potionChance == 0) { Instantiate(potionObject, transform.position, Quaternion.identity); } Destroy(gameObject); } // Footstep sfx coroutine IEnumerator FootstepFunc() { // Runs forever as long as program is running while (true) { // If enemy is running play footstep sfx if (isRunningFX) { audioSource.PlayOneShot(footstepFX[Random.Range(0, 2)]); } yield return new WaitForSeconds(0.653f); } } // Check if enemy is running coroutine IEnumerator RunningCheck() { // Runs as long as the program is running while (true) { // currentPositon is set to the current position of enemy currentPosition = transform.position; // If the current postion is different from the last position the enemy has to be moving, so isRunning true if (currentPosition != lastPosition) { isRunning = true; } else // Otherwise not so false { isRunning = false; } // If the current position x is less than last position x the enemy must be running to the left, so flip sprite to left if (currentPosition.x < lastPosition.x) { transform.localScale = new Vector3(-1, transform.localScale.y, transform.localScale.z); facing = -1; } else if (currentPosition.x > lastPosition.x) // If the current position x is more than last position x the enemy must be running to the right, so flip sprite to right { transform.localScale = new Vector3(1, transform.localScale.y, transform.localScale.z); facing = 1; } // last position is set to the current position after all logic is finished lastPosition = currentPosition; yield return new WaitForSeconds(0.1f); } } // Coroutine for changing tint back to white after damaged IEnumerator DamageTint() { yield return new WaitForSeconds(damageTime); objectRenderer.material.color = Color.white; } // Damage knockback coroutine IEnumerator DamageKnockback() { // If damaged the enemy is invicible until knockback has been given IFrames = true; // If the enemy is facing to the right, give them knockback to the left. Stop knockback if they hit a wall if (facing == 1) { for (int i = 0; i < 15; i++) { if (hitWall == false) { transform.position += new Vector3(-knockbackStrength, 0, 0); } yield return new WaitForSeconds(0.01f); } } // If the enemy is facing to the left, give them knockback to the right. Stop knockback if they hit a wall else if (facing == -1) { for (int i = 0; i < 15; i++) { if (hitWall == false) { transform.position += new Vector3(knockbackStrength, 0, 0); } yield return new WaitForSeconds(0.01f); } } // Enemy is no longer invicible IFrames = false; } // DashIFrames method void DashIFrames() { // Disables enemy collider when players dashing if (player.isDashing && myCollider.enabled) { myCollider.enabled = false; } // Enables enemy collider when player is not dashing if (!player.isDashing && !myCollider.enabled) { myCollider.enabled = true; } } // Death method void Death() { // If enemy is not already dead and enemy health is 0 or below, isDead true, play death animation and start death coroutine if (!isDead && enemyHealth <= 0) { isDead = true; myAnimator.SetTrigger("isDead"); StartCoroutine(WaitForAnimation()); } } // Alert radius method void AlertRadius() { // Get array of colliders marked as layer "Player" in the radius circle around enemy Collider2D[] colliders = Physics2D.OverlapCircleAll(transform.position, radius, myLayerMask); // If there is a collider in the array (aka. the player) then the navigation library I used becomes active if (colliders.Length > 0) { isRunningFX = true; myAIPath.canMove = true; } // If there is no collider in the array then the enemy doesnt move else { isRunningFX = false; myAIPath.canMove = false; } } // Flip sprite method void FlipSprite() { // If enemy is running enable enemy footstep particles and set running animation if (isRunning) { if (!footstepParticleSystem.isPlaying) { footstepParticleSystem.Play(); } myAnimator.SetBool("isRunning", true); } else // If enemy is not running disable enemy footstep particles and set idle animation { if (footstepParticleSystem.isPlaying) { footstepParticleSystem.Stop(); } myAnimator.SetBool("isRunning", false); } } // If enemy enters any triggers void OnTriggerEnter2D(Collider2D col) { // If enemy enters weapon trigger and is not invicible if (col.gameObject.tag == "Weapon" && !IFrames) { // Spray blood particles, subtract health from enemy, tint enemy red and start the damagetint coroutine bloodParticleSystem.Emit(50); enemyHealth -= playerDamageMult; objectRenderer.material.color = damageColor; StartCoroutine(DamageTint()); // Start damageknockback coroutine if enemy is not on wall if (!hitWall) { StartCoroutine(DamageKnockback()); } } } // Enemy collides with anything void OnCollisionEnter2D(Collision2D col) { // If enemy hits wall hitWall true if (col.gameObject.tag == "Wall") { hitWall = true; } } // Enemy stops colliding with anything void OnCollisionExit2D(Collision2D col) { // If not colliding with wall anymore, hitWall false if (col.gameObject.tag == "Wall") { hitWall = false; } } }