using System.Collections; using System.Collections.Generic; using UnityEngine; public class AiController : MonoBehaviour { [SerializeField] Strategy strategy; [SerializeField] public BoxCollider2D closeRangeCollider; [SerializeField] public BoxCollider2D focusRangeCollider; [SerializeField] public BoxCollider2D attackRangeCollider; [SerializeField] public BoxCollider2D playerColldier; private CharacterController2D playerCharacterController; [SerializeField] private AudioSource audioSource; [SerializeField] private AudioClip exclaimAudioClip; public GameObject exclamation; public bool isExclaiming = false; public bool canBeStartled = true; public bool shouldExclaim = false; public float exclaimTime = 1.0f; public float exclaimExpiration = 30.0f; public float transposeExclamationY = 0.0f; public bool isCoolingDownAttack = false; public float attackCooldown = 1.5f; public Intent intent = Intent.NONE; public bool isPlayerInRange = false; public bool isPlayerInAttackRange = false; public enum Strategy { // Actively chases the player, attacking when roughly in range. // Should be annoying. RUSHDOWN, // Minds own business, only facing when in range and attacking // from short range. // Probably the easiest to handle. REACTIVE, // Casually tracks the player, fleeing when approached and // rushing down some time later. // Very difficult to defeat. STRATEGIC, // Unpredictable. CRAZY } public enum Intent { NONE, ATTACK, CHASE, FLEE } // Start is called before the first frame update void Awake() { playerColldier = GameObject.FindGameObjectWithTag("Player").GetComponent(); playerCharacterController = GameObject.FindGameObjectWithTag("Player").GetComponent(); } private Intent GetStrategicIntent() { float posX = transform.position.x; float playerPosX = playerCharacterController.transform.position.x; float playerVelX = playerCharacterController.m_Rigidbody2D.velocity.x; // If the player is facing away from me, I'll chase! if ((playerPosX < posX) != (playerCharacterController.m_FacingRight)) { return Intent.CHASE; } else { // If the player's chasing me, I'll face them! if ((playerVelX > 0.01 && playerPosX < posX) || (playerVelX < -0.01 && playerPosX > posX)) { return Intent.CHASE; } return Intent.FLEE; } } private Intent GetOnFocusIntent() { if (isExclaiming) { return Intent.NONE; } switch (strategy) { case Strategy.RUSHDOWN: // TODO: Refactor IsTouching to use OnTriggerEnter/Exit events if (attackRangeCollider.IsTouching(playerColldier) && !isCoolingDownAttack) { return Intent.ATTACK; } else { return Intent.CHASE; } case Strategy.REACTIVE: return Intent.NONE; case Strategy.STRATEGIC: return GetStrategicIntent(); case Strategy.CRAZY: //float choice = Random.Range(0f, 1f); //if (choice < 0.25f) return Intent.ATTACK; //if (choice < 0.5f) return Intent.CHASE; //if (choice < 0.75f) return Intent.FLEE; return Intent.NONE; default: Debug.LogError("Undefined strategy for monster!"); return Intent.NONE; } } private void OnTriggerEnter2D(Collider2D collision) { if (collision.CompareTag("Player")) { isPlayerInRange = true; intent = GetOnFocusIntent(); if (shouldExclaim && canBeStartled && !attackRangeCollider.IsTouching(playerColldier)) { audioSource.PlayOneShot(exclaimAudioClip); GameObject effect = Instantiate(exclamation); effect.transform.position = new Vector2(transform.position.x, transform.position.y + transposeExclamationY); isExclaiming = true; canBeStartled = false; Invoke("StopExclaiming", exclaimTime); Invoke("CanStartle", exclaimExpiration); } } } public void StopExclaiming() { isExclaiming = false; } public void CanStartle() { canBeStartled = true; } private void OnTriggerExit2D(Collider2D collision) { if (collision.gameObject.CompareTag("Player") && !focusRangeCollider.IsTouching(playerColldier)) { isPlayerInRange = false; } } private void FixedUpdate() { if (isPlayerInRange) { intent = GetOnFocusIntent(); } } }