Files
pgs/Assets/Scripts/AiController.cs
2026-02-21 16:58:22 -08:00

148 lines
4.9 KiB
C#

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<BoxCollider2D>();
playerCharacterController = GameObject.FindGameObjectWithTag("Player").GetComponent<CharacterController2D>();
}
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();
}
}
}