How to access child of a prefab in a serialized array?

I’m working on the classic isometric CRPG “press Tab for info” thing, and trying to make it so that an exclamation mark appears above every interactable NPC when a certain key is pressed.

So for example in the first image the streetnames, the circle around the player character’s feet, and the exclamation marks above the two NPCs should appear when the key is pressed:

The way I’ve handled this so far is to have each element of the info overlay hooked up to the following script:

public class OverlayManager : MonoBehaviour
{
    [SerializeField] GameObject[] infoOverlays;

    void Start()
    {
        foreach(GameObject infoOverlay in infoOverlays){infoOverlay.SetActive(false);}
    }


    void Update()
    {
        if (Input.GetButtonDown("Fire2"))
        {
            foreach(GameObject infoOverlay in infoOverlays){infoOverlay.SetActive(true);}
        }
        else if (Input.GetButtonUp("Fire2"))
        {
            foreach(GameObject infoOverlay in infoOverlays){infoOverlay.SetActive(false);}
        }
    }
}

That script is attached to an empty Game Object (see imgur second pic) which manages the whole overlay. It works fine for the most part.

The kicker is that the exclamation mark (Interaction Marker) is a child of my NPC prefab. I can’t seem to drag and drop it from the prefab itself, so for now I’ve just dragged it from one instance NPC GameObject to test it. However this obviously only works on a case-by-case basis, meaning I’ll have to drag and drop it from every NPC. This seems a bit silly.

Is there a way to make the NPC prefab’s child part of the array? Or do I have to do something else? I reckon I could do something with “Find” but I believe that’s expensive and I’m already struggling with optimisation.

Any help would be much appreciated! Apologies for the weird imgur link, the forum is being fussy about my images.

Hi @dantuomey,

You could do the exact thing in a reverse manner, via events. I’ve created similar scene and it works fine. Here’s the code for the publisher (the object in control):

using System;
using UnityEngine;

public class Publisher : MonoBehaviour
{
    // Event publisher
    public static Action<bool> OnInfoOverlayChange { get; set; }

    // Current state of the exclamation marks
    public bool IsInfoOverlayActive { get; set; }

    [SerializeField] private GameObject _subscriberPrefab;

    private void Start()
    {
        for (int i = 0; i < 10; i++)
            Instantiate(_subscriberPrefab); // Just creating objects for testing
    }

    private void Update()
    {
        if (Input.GetButtonDown("Fire2") || Input.GetButtonUp("Fire2"))
        {
            IsInfoOverlayActive ^= true; // Easy bool reversing
            OnInfoOverlayChange(IsInfoOverlayActive); // Publishing the event
        }
    }
}

And here’s the code assigned to the prefab:

using UnityEngine;

public class Subscriber : MonoBehaviour
{
    // Assign the prefab child in the inspector
    [SerializeField] private GameObject _exclamationMark;

    private void Start()
    {
        Publisher.OnInfoOverlayChange +=
            OnInfoOverlayChanged; // Add subscriber to event
    }

    public void OnInfoOverlayChanged(bool newState)
    {
        _exclamationMark.SetActive(newState);
    }
}

The event is static so you can assign subscribers to it directly.

And well… it’s not an “event” in strict sense, but it does its job.


EDIT. Alternatively, if you really need a serialized Array (although List would be better, thus I use it here) and DO NOT WANT events, you could do something like this:

using System;
using System.Collections.Generic;
using UnityEngine;

public class Publisher : MonoBehaviour
{
    public static List<Subscriber> Subscribers { get; } = new();

    [SerializeField]
    private List<Subscriber> _subscribers = Subscribers;

    [SerializeField]
    private GameObject _subscriberPrefab = null;

    private bool _isInfoOverlayActive = false;

    private void Start()
    {
        for (int i = 0; i < 10; i++)
            Instantiate(_subscriberPrefab); // Just creating objects for testing
    }

    private void Update()
    {
        if (Input.GetButtonDown("Fire2") || Input.GetButtonUp("Fire2"))
        {
            _isInfoOverlayActive ^= true;
            foreach (var item in Subscribers)
                item.ExclamationMark.SetActive(_isInfoOverlayActive);
        }
    }
}

.

using UnityEngine;

public class Subscriber : MonoBehaviour
{
    // Assign the prefab child in the inspector
    public GameObject ExclamationMark;

    private void Start()
    {
        Publisher.Subscribers.Add(this);
    }
}

I’m not sure which option should you use, but the results are perfectly the same, and they’re both very cheap.