0
\$\begingroup\$

Let's say I have a very simple 2D square prefab that has a script like this attached to it:

public class Square : MonoBehaviour
{
    public GameObject GameObject { get; private set; }
    public Transform Transform { get; private set; }
    public SpriteRenderer SpriteRenderer { get; private set; }

    private void Awake() 
    {
        GameObject = gameObject;
        Transform = transform;
        SpriteRenderer = GetComponent<SpriteRenderer>();
    }
}

I want to be able to arrange groups of these squares with different positions and sprite colors, saving them as prefabs to be used by a spawner (with object pooling), sort of like this:

public class SquareSpawner : MonoBehaviour
{
    [SerializeField]
    private Square[] squares;

    private ObjectPool<Square> pool;  

    public void Spawn(Vector2 position)
    {
        foreach (var square in squares)
        {
            /* Get a square prefab from object pool 
               (this will instantiate a Square prefab into the scene 
               if one does not yet exist) */
            Square pooledSquare = pool.Get();
            
            /* Set up the squares we got from the pool 
               to match the squares given to the squares array */
            pooledSquare.Transform.position = position + square.Transform.localPosition;
            pooledSquare.SpriteRenderer.Color = square.SpriteRenderer.Color;

            // etc.
        }          
    }
}

Basically, I'm trying to create a spawning system that is easily visualizable using prefabs. Since object pooling is used, I don't know the configuration of the square I'll be getting back, so I always have to set the given squares' properties to match the desired configuration (this could be size, color, position, etc.).

The issue is that I can't read the properties from the Square prefabs that are not being instantiated (the ones I'm putting into the Square[] squares array) because the Awake() method only gets called upon instantiation into the scene.

Perhaps there's a better way for me to set all of this up, but I'm wondering if there's a way for me to cache the properties in the Square class so that I can read them whether the script is on a prefab and/or an instantiated Square in the scene. It would also be nice if I could use a custom editor to test things in edit mode, which has also been hindered by my inability to cache these fields.

\$\endgroup\$
7
  • \$\begingroup\$ Please do not name your variable the same as the type, so instead of using GameObject GameObject use at least GameObject gameobject \$\endgroup\$
    – Zibelas
    Commented Jan 11, 2022 at 13:01
  • \$\begingroup\$ Did you consider assigning your game object, transform, and sprite renderer references in OnValidate rather than Awake, so the date is already available at edit time and does not need to wait for instantiation? Also, note that MonoBehavíour classes already have a reference to .gameObject and .transform, so there's no particular benefit to be had in making your own aliases/copies of these public properties. \$\endgroup\$
    – DMGregory
    Commented Jan 11, 2022 at 13:08
  • \$\begingroup\$ @DMGregory I've never used OnValidate, but I could try that. Also, I've heard countless times that caching gameObject and transform was a good idea for the minor performance improvements if one is going to be calling those properties all the time (which I will be doing). \$\endgroup\$ Commented Jan 11, 2022 at 13:15
  • 1
    \$\begingroup\$ @Zibelas Do you have a specific reason for this? The feedback here is mixed: stackoverflow.com/questions/1095644/… \$\endgroup\$ Commented Jan 11, 2022 at 13:36
  • 2
    \$\begingroup\$ Regarding capitalization, you'll notice that all of the Unity API is written with member fields and properties starting with a lowercase letter, so that's usually considered a more idiomatic style for Unity scripts interacting with that API. It keeps things consistent so you don't have to flip-flop conventions depending on whether you're accessing something built into Unity or something custom that you made. It also ensures there's never ambiguity about whether you're trying to access an instance member or static member, since those two will look different: myType.foo vs MyType.bar \$\endgroup\$
    – DMGregory
    Commented Jan 11, 2022 at 13:52

1 Answer 1

2
\$\begingroup\$

Simple: ensure the data you want to access is serialized and available in the saved version of the prefab.

[RequireComponent(typeof(SpriteRenderer))]
public class Square : MonoBehaviour
{
    [field:SerializeField, HideInInspector]
    public SpriteRenderer spriteRenderer { get; private set; }

    private void OnValidate() 
    {
        spriteRenderer = GetComponent<SpriteRenderer>();
    }
}

This will...

  1. Ensure the object has a SpriteRenderer component when you add the Square component, if it didn't already, via the RequireComponent attribute.

  2. Immediately call OnValidate when you edit the prefab in the inspector, caching a reference to that SpriteRenderer.

    (Technically here you could also use the Reset method which is called once when the component is added or manually reset to its defaults in the editor, rather than OnValidate which is called after every change or assembly reload in editor, as well as on entering play mode. But OnValidate is a bit more flexible if there's other stuff you want to set up that might not be available from the first moment the component is added. Note Philipp's caution from he comments that neither of these methods is called in the build, so use this only to set up data that you can serialize and load from disk already in the correct state)

  3. Serialize that reference via the field:SerializeField attribute, so that it's saved with the prefab and available to read from the saved version without instantiating it.

I did not do this with the gameObject or transform members because those are already available to us in serialized prefabs through MonoBehaviour without needing to write any extra code of our own.


Alex Hetherington has created a handy editor script that simplifies this common task of linking-up component references. Now you can just add a [Resolve] attribute above the serialized component field you want to populate, and it will add a one-click button to populate the field (even populating an array of references), without you having to write your own initialization in OnValidate() or elsewhere. Here's a link to the GitHub repository with this and other utilities you can add to your project.

\$\endgroup\$
2
  • \$\begingroup\$ The key point here is the OnValidate message method which gets called by the Unity editor every time there is a change in the editor. But remember that it does not get called in the build, so you still need to do the same stuff in Awake. \$\endgroup\$
    – Philipp
    Commented Jan 11, 2022 at 14:28
  • \$\begingroup\$ @Philipp You don't need to do the stuff shown here in Awake because the results of the last editor-time OnValidate call are already serialized and available in the saved version of the prefab read from disk. Now if this code included setup steps that were not reflected in the serialized data, then those would need to be performed after instantiation, or via a custom Initialize() method that you call as your game loads. \$\endgroup\$
    – DMGregory
    Commented Jan 11, 2022 at 14:36

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .