I’m making a fangame, but there seems to be something wrong with this AI, and I don’t know where these bugs are coming from.
While playing in the editor, I found a few issues with this script:
- The
DefindGrannyDestinations()method can sometimes get messy, specifically because it’s duplicated (probably because ofFixedUpdate(), but I don’t know how to deal with it). - The jump in
boolvalue is also confusing, and I have absolutely no idea what causes this phenomenon.
By the way, when theboolvalue jump and theDefindGrannyDestinations()method is executed in the initial automatic patrol, as long as thePlayerdoes not interfere withGranny, everything will be fine, but once Granny finds thePlayerand chases it, the above “booljump andDefindGrannyDestinations()” problem will become very confusing.
Not only that, but playerCaught() is also very chaotic in execution, and playing GrannyJumpscare may also be due to the intervention of FixedUpdate() so that it is played once per frame…
In addition to that, I’d like to ask for some advice on how to give Granny more flexibility to “discover” the Player, rather than using a bunch of rays to detect the Player. Thank you for your help!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
//This is a script for testing GrannyAI!
[RequireComponent(typeof(AudioSource))]
public class EnemyGrannyAI : MonoBehaviour
{
//Jumpscare Sound
public AudioClip GrannyJumpscare;
//Vocies of Granny
public AudioClip[] GrannySounds;
//Musiken Elements
public GameObject chaseMusik;
//Granny Animator
public Animator GrannyAnim;
//AI Elements
public NavMeshAgent Granny;
private float waypointTime = 7f;
private float lookAroundTime = 6.32f;
private float walkSpeed = 1.4f;
private float followSpeed = 3f;
private float caughtSpeed = 0f;
private float raycastLength = 40f;
private float playerSafeTime= 7f;
public bool GrannyisSearching;
public bool GrannySearchedWaiting = false;
public bool GrannyisChasing;
public bool GrannyChasedWaiting = false;
private bool SeePlayer;
public bool playerGetCaught = false;
private bool canFadeMusik;
public Transform[] patrolPoints;
public Transform granny;
public Transform player;
public GameObject SeePlayerPos;
public GameObject Player;
public GameObject PlayerCam;
private void Start()
{
walkSpeed = Granny.speed;
Granny.stoppingDistance = 2f;
DefindGrannyDestination();
playerGetCaught = false;
GrannyisSearching = true;
GrannySearchedWaiting = false;
GrannyisChasing = false;
GrannyChasedWaiting = false;
}
private void FixedUpdate()
{
//RayForward
CastRay(transform.forward);
//RayLeft
CastRay(Quaternion.Euler(0, -90, 0) * transform.forward);
CastRay(Quaternion.Euler(0, -75, 0) * transform.forward);
CastRay(Quaternion.Euler(0, -60, 0) * transform.forward);
CastRay(Quaternion.Euler(0, -45, 0) * transform.forward);
CastRay(Quaternion.Euler(0, -30, 0) * transform.forward);
CastRay(Quaternion.Euler(0, -15, 0) * transform.forward);
//RayRight
CastRay(Quaternion.Euler(0, 90, 0) * transform.forward);
CastRay(Quaternion.Euler(0, 75, 0) * transform.forward);
CastRay(Quaternion.Euler(0, 60, 0) * transform.forward);
CastRay(Quaternion.Euler(0, 45, 0) * transform.forward);
CastRay(Quaternion.Euler(0, 30, 0) * transform.forward);
CastRay(Quaternion.Euler(0, 15, 0) * transform.forward);
//RayUp
CastRay(Quaternion.Euler(80, 0, 0) * transform.forward);
CastRay(Quaternion.Euler(75, 0, 0) * transform.forward);
CastRay(Quaternion.Euler(60, 0, 0) * transform.forward);
CastRay(Quaternion.Euler(45, 0, 0) * transform.forward);
CastRay(Quaternion.Euler(30, 0, 0) * transform.forward);
CastRay(Quaternion.Euler(15, 0, 0) * transform.forward);
//RayDown
CastRay(Quaternion.Euler(-80, 0, 0) * transform.forward);
CastRay(Quaternion.Euler(-75, 0, 0) * transform.forward);
CastRay(Quaternion.Euler(-60, 0, 0) * transform.forward);
CastRay(Quaternion.Euler(-45, 0, 0) * transform.forward);
CastRay(Quaternion.Euler(-30, 0, 0) * transform.forward);
CastRay(Quaternion.Euler(-15, 0, 0) * transform.forward);
//Function Group
JudgeGrannyStatus();
GrannyAnimationsChecker();
GrannyReachedChecker();
FadeChaseMusik();
}
private void CastRay(Vector3 direction)
{
RaycastHit hit;
if (Physics.Raycast(SeePlayerPos.transform.position, direction, out hit, raycastLength) && hit.collider.gameObject.tag == "Player")
{
SeePlayer = true;
Debug.DrawLine(SeePlayerPos.transform.position, hit.point, Color.red);
}
else
{
SeePlayer = false;
Debug.DrawLine(SeePlayerPos.transform.position, hit.point, Color.red);
}
}
private void GrannyAnimationsChecker()
{
if(GrannyisSearching == true && GrannySearchedWaiting == false && GrannyisChasing == false && GrannyChasedWaiting == false && playerGetCaught == false)
{
GrannyAnim.Play("Walk_1");
walkSpeed = Granny.speed;
}
else if(GrannyisSearching == false && GrannySearchedWaiting == true && GrannyisChasing == false && GrannyChasedWaiting == false && playerGetCaught == false)
{
GrannyAnim.Play("idle_4");
walkSpeed = Granny.speed;
}
else if (GrannyisSearching == false && GrannySearchedWaiting == false && GrannyisChasing == true && GrannyChasedWaiting == false && playerGetCaught == false)
{
GrannyAnim.Play("EasyGranny");
followSpeed = Granny.speed;
}
else if (GrannyisSearching == false && GrannySearchedWaiting == false && GrannyisChasing == false && GrannyChasedWaiting == true && playerGetCaught == false)
{
GrannyAnim.Play("Look");
walkSpeed = Granny.speed;
}
else if (GrannyisSearching == false && GrannySearchedWaiting == false && GrannyisChasing == false && GrannyChasedWaiting == false && playerGetCaught == true)
{
GrannyAnim.Play("Hit_0");
caughtSpeed = Granny.speed;
}
}
private void GrannyReachedChecker()
{
if (GrannyisChasing == true)
{
Granny.stoppingDistance = 3f;
if (Granny.remainingDistance < Granny.stoppingDistance)
{
StartCoroutine(playerCaught());
playerGetCaught = true;
GrannyisSearching = false;
GrannySearchedWaiting = false;
GrannyisChasing = false;
GrannyChasedWaiting = false;
}
}
else
{
Granny.stoppingDistance = 2f;
if (Granny.remainingDistance < Granny.stoppingDistance)
{
StartCoroutine(GrannyWaitedAtRandomDestination());
playerGetCaught = false;
GrannyisSearching = false;
GrannySearchedWaiting = true;
GrannyisChasing = false;
GrannyChasedWaiting = false;
}
else
{
playerGetCaught = false;
GrannyisSearching = true;
GrannySearchedWaiting = false;
GrannyisChasing = false;
GrannyChasedWaiting = false;
}
}
}
private void JudgeGrannyStatus()
{
if (SeePlayer == true)
{
GrannyisSearching = false;
GrannySearchedWaiting = false;
GrannyisChasing = true;
GrannyChasedWaiting = false;
StartCoroutine(followPlayer());
}
}
private void FadeChaseMusik()
{
if(canFadeMusik == true)
{
chaseMusik.SetActive(true);
chaseMusik.GetComponent<AudioSource>().volume = chaseMusik.GetComponent<AudioSource>().volume + 0.3f * Time.deltaTime;
}
else
{
chaseMusik.GetComponent<AudioSource>().volume = chaseMusik.GetComponent<AudioSource>().volume - 0.3f * Time.deltaTime;
if(chaseMusik.GetComponent<AudioSource>().volume <= 0f)
{
chaseMusik.SetActive(false);
}
else
{
chaseMusik.SetActive(true);
}
}
}
IEnumerator followPlayer()
{
Granny.SetDestination(player.position);
canFadeMusik = true;
yield return new WaitForSeconds(playerSafeTime);
if(SeePlayer == true)
{
GrannyisSearching = false;
GrannySearchedWaiting = false;
GrannyisChasing = true;
GrannyChasedWaiting = false;
StartCoroutine(followPlayer());
}
else
{
GrannyisSearching = false;
GrannySearchedWaiting = false;
GrannyisChasing = false;
GrannyChasedWaiting = true;
StartCoroutine(GrannyWaitedAtPlayerDisappearingDestinations());
StopCoroutine(followPlayer());
}
if(playerGetCaught == true)
{
GrannyisSearching = false;
GrannySearchedWaiting = false;
GrannyisChasing = false;
GrannyChasedWaiting = false;
StopCoroutine(GrannyWaitedAtPlayerDisappearingDestinations());
StopCoroutine(followPlayer());
}
}
IEnumerator GrannyWaitedAtPlayerDisappearingDestinations()
{
if (GrannyChasedWaiting == true)
{
canFadeMusik = false;
GrannyisSearching = false;
GrannySearchedWaiting = false;
GrannyisChasing = false;
yield return new WaitForSeconds(lookAroundTime);
DefindGrannyDestination();
GrannyisSearching = true;
GrannySearchedWaiting = false;
GrannyisChasing = false;
UpdateGrannySounds();
GrannyChasedWaiting = false;
StopCoroutine(GrannyWaitedAtPlayerDisappearingDestinations());
}
else
{
yield return null;
}
}
IEnumerator GrannyWaitedAtRandomDestination()
{
if (GrannySearchedWaiting == true)
{
yield return new WaitForSeconds(waypointTime);
DefindGrannyDestination();
GrannyisSearching = true;
GrannySearchedWaiting = false;
StopCoroutine(GrannyWaitedAtRandomDestination());
}
else
{
yield return null;
}
}
IEnumerator playerCaught()
{
if(playerGetCaught == true)
{
canFadeMusik = false;
player.LookAt(granny);
((PlayerController)Player.GetComponent(typeof(PlayerController))).canMove = false;
((CameraController)PlayerCam.GetComponent(typeof(CameraController))).canRotate = false;
GetComponent<AudioSource>().PlayOneShot(GrannyJumpscare);
playerGetCaught = false;
StopCoroutine(playerCaught());
}
yield break;
}
public virtual void DefindGrannyDestination()
{
int randomIndex = Random.Range(0, patrolPoints.Length);
Granny.SetDestination(patrolPoints[randomIndex].position);
}
public virtual void UpdateGrannySounds()
{
GetComponent<AudioSource>().clip = GrannySounds[Random.Range(0, GrannySounds.Length)];
GetComponent<AudioSource>().Play();
}
}

Note that you are right that this doesn't stop any coroutines, but it also doesn't start any. It just creates the IEnumerator object and does nothing with it. This specific IEnumerator object is not registrated by Unity's coroutine scheduler so passing it to StopCoroutine simply does nothing and the created IEnumerator is up for garbage collection. Nothing else would happen. MoveNext of the IEnumerator would not be called even once.
– Bunny83Thanks for letting me know @Bunny83! You're the best :) That makes absolute sense when I think about it now; should have tried it empirically before posting it.
– andrew-lukasik