How do I replace GameObject.Find().GetComponent<>?

Hello,

I am trying to create a simple zombie AI that uses State Machines for future scalability purposes. I currently only have two scripts, the State script and the IdleState script.
It all works well, but currently, I rely on GameObject.Find(“Zombie”).GetComponent to make it all work.

I know this will break the second I put more than one zombie on the screen, but I haven’t figured out a way to solve this, because the State class is abstract and I am quite stuck on best practices.

Here are the two classes:

public abstract class State : MonoBehaviour
{
    private Enemy enemy;
    public abstract State RunCurrentState();

    public void Awake()
    {
        enemy = GameObject.Find("Zombie").GetComponent<Enemy>();
    }

    public Enemy GetEnemy()
    {
        return enemy;
    }

And the second class, the IdleState class.

public class IdleState : State
{
    [SerializeField] private ChaseState chaseState;

    public override State RunCurrentState()
    {
        Collider[] colliders = Physics.OverlapSphere(transform.position, GetEnemy().GetDetectionRadius(), GetEnemy().GetDetectPlayerLayer());
        for (int i = 0; i < colliders.Length; i++)
        {
            Player player = colliders[i].transform.GetComponent<Player>();
            if (player != null)
            {
                Vector3 targetDirection = player.transform.position - transform.position;
                float viewableAngle = Vector3.Angle(targetDirection, transform.forward);
                if(viewableAngle > GetEnemy().GetMinimumDetectionAngle() && viewableAngle < GetEnemy().GetMaximumDetectionAngle())
                {
                    return chaseState;
                }
            }
            else
            {
                return this;
            }
        }
        return this;
    }
}

I think I can use constructors, but I don’t exactly know how. I am fairly new to Unity, so any advice is golden.

Is your state script not on the zombie object?
If it is, then you can just use GetComponent() without a target. If it’s not, then might be helpful to know your scene setup, but you’d have to initialize your values probably when you instantiate the zombie, or if the zombies are already in the scene, there may be other ways to hook it up.

No, State is an abstract class so it can’t be attached to the zombie object.

But IdleState is what attaches to your GameObject, which inherits from State, which has your Awake. State is your base class (I guess I should have been more clear). So, your various “States” are attached to something, aren’t they?

I wrote an article a while back with the most common techniques to replace GameObject.Find. This looks like the perfect opportunity for the technique under the heading “Multiton” - a maintained static list of all enemies that you use OnEnable and OnDisable to keep updated.

Ah. Well, your State class inherits from Monobehaviour. That’s the special signal to Unity that a class goes on a gameObject (your zombies) which also means Unity will run its Start() and Update() and also that the can’t be created the normal way using “new” or have a constructor. Put another way, your current State class never needs to use GameObject.Find(“zombie”) because it will always be on a zombie and have a direct link to that zombie: “transform” or “GetComponent()” (for the script).

The alternative is to make a normal class (which can be abstract or inherit from anything except Monobehaviour). Then add a link such as “public Enemy myZombie;” to it. As you create a zombie you’d also “new” an instance of this State class and give it the link to that zombie. As you replace one State subclass with a different one (as part of a state transition), copy myZombie from the old into the new.

In Unity we often put permanent classes onto things as Monobehaviours. For temporary classes it’s possible to also add them, flipping them in and out with, ummm, AddComponent and Destroy, but I feel like it’s simpler to use the normalClass+linkToGameObject method.