Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Question How to Instantiate with modified code before first frame/step?

Discussion in 'Scripting' started by Duck783, Apr 6, 2023.

  1. Duck783

    Duck783

    Joined:
    Apr 6, 2023
    Posts:
    6
    Hi all!

    Question: Can you pass in code to a prefab before it gets instantiated?

    I am writing a simple program that creates a pizza with a specific number of slices. The prefab takes in a given number and then if that number exceeds the max slices (8), it then spawns a new pizza from the remaining slices. This continues until there are no more slices to make. So if you were to put 20 slices on the starting prefab, you should end up with 3 pizzas: 2 with 8 slices and 1 with 4 -- this works in Unreal but I can't seem to get it running in Unity.

    In Unreal, you can pass values into the creation code of a new object. So each new pizza starts with a new number of slices deducted from the previous pizza.

    In Unity, from what I've found, you can only modify values after the object has been instantiated, which means its awake or start functions would have run before those values could be modified. This basically causes a race condition because the new pizza only receives the correct number of slices after its starting frame, which results in the wrong number of slices being spawned.

    Also, despite the prefab in the assets menu having 0 slices, new prefabs spawned from the one in world, inherit the previous value given. So if you put 20 in, you'll get 3 pies of 20, which then later do the math and add on whatever the remaining slices would have been.

    Unreal: (20 slices)
    upload_2023-4-6_8-17-42.png

    Unity: (20 slices)
    upload_2023-4-6_8-20-3.png

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Pizza : MonoBehaviour
    6. {
    7.     public GameObject pizzaSlice; // the pizza mesh, gameObject, or prefab
    8.     public GameObject pizza; // the empty pizza prefab
    9.     public int slices; // the number of slices to make
    10.     private int maxSlices = 8; // the maximun number of slices a pizza can have
    11.  
    12.     // Start is called before the first frame update
    13.     void Start()
    14.     {
    15.         // call the create pizza function
    16.         CreatePizza(slices);
    17.     }
    18.  
    19.     // Creates a pizza based on the number of slices
    20.     void CreatePizza(int slices)
    21.     {
    22.         // spawn pizza in the world based on number of slices
    23.         for(int i = 0; i < slices; i++)
    24.         {
    25.             if(i < maxSlices)
    26.             {
    27.                 Vector3 spawnRotation = new Vector3(0f, i * 45f, 0f);
    28.                
    29.                 GameObject newSlice = Instantiate(pizzaSlice, transform.position,
    30.                     Quaternion.Euler(spawnRotation));
    31.  
    32.                 newSlice.transform.parent = this.transform;
    33.             }
    34.             else
    35.             {
    36.                 float xOffset = 1f;
    37.  
    38.                 Vector3 newPizzaLocation = new Vector3(this.transform.position.x + xOffset,
    39.                     this.transform.position.y, this.transform.position.z);
    40.  
    41.                 GameObject newPizza = Instantiate(pizza, newPizzaLocation, Quaternion.identity);
    42.  
    43.                 newPizza.GetComponent<Pizza>().slices = slices - maxSlices;
    44.  
    45.                 return;
    46.             }
    47.         }
    48.     }
    49. }
     

    Attached Files:

  2. Duck783

    Duck783

    Joined:
    Apr 6, 2023
    Posts:
    6
    I found a solution. Instead of Instantiate, create a new GameObject("name", Typeof(object));

    Credit goes to my students for their help in creative problem solving.

    upload_2023-4-6_11-58-47.png

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Pizza : MonoBehaviour
    6. {
    7.     public GameObject pizzaSlice; // the pizza mesh, gameObject, or prefab
    8.     //public GameObject pizza; // the empty pizza prefab
    9.     public int slices; // the number of slices to make
    10.     private int maxSlices = 8; // the maximun number of slices a pizza can have
    11.  
    12.     // Start is called before the first frame update
    13.     void Start()
    14.     {
    15.         // get slice mesh from resources folder
    16.         pizzaSlice = Resources.Load<GameObject>("Pizza/sm_pizzaSlice");
    17.  
    18.         // call the create pizza function
    19.         CreatePizza(slices);
    20.     }
    21.  
    22.     // Creates a pizza based on the number of slices
    23.     public void CreatePizza(int slices)
    24.     {
    25.         // spawn pizza in the world based on number of slices
    26.         for(int i = 0; i < slices; i++)
    27.         {
    28.             if(i < maxSlices)
    29.             {
    30.                 Vector3 spawnRotation = new Vector3(0f, i * 45f, 0f);
    31.              
    32.                 GameObject newSlice = Instantiate(pizzaSlice, transform.position,
    33.                     Quaternion.Euler(spawnRotation));
    34.  
    35.                 newSlice.transform.parent = this.transform;
    36.             }
    37.             else
    38.             {
    39.                 float xOffset = 1f;
    40.  
    41.                 Vector3 newPizzaLocation = new Vector3(this.transform.position.x + xOffset,
    42.                     this.transform.position.y, this.transform.position.z);
    43.  
    44.                 GameObject newPizza = new GameObject("Pizza", typeof(Pizza));
    45.                 newPizza.transform.position = newPizzaLocation;
    46.                 newPizza.GetComponent<Pizza>().slices = slices - maxSlices;
    47.  
    48.                 return;
    49.             }
    50.         }
    51.     }
    52. }
     
  3. samana1407

    samana1407

    Joined:
    Aug 23, 2015
    Posts:
    76
    Is it convenient to manipulate this "recursion" when the pizza creates itself and then creates another pizza and so on?
    In my opinion, it is easier to create a builder who will need to build a series of pizzas from a given number of pieces. Some pizza builder. This way you will also save links to all created pizzas for additionally control them.
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,947
    Duck783 likes this.
  5. StarBornMoonBeam

    StarBornMoonBeam

    Joined:
    Mar 26, 2023
    Posts:
    209
    alternatively the prefab doesn’t need a automatic start method it can have any void be called after instantiating. So you can set your variables and manually start that object. Instead of using the start function. I personally had never heard of start occurring on the line of instantiate, before the next line mods a variable but perhaps it does. And so if it does, just turn that start into a void and call it after instantiate.

    var O = instantiate(Prefab)
    O.GetComponent<YourComponent>().ManuallyStart();
     
  6. Duck783

    Duck783

    Joined:
    Apr 6, 2023
    Posts:
    6

    A separate builder object is an additional step. For freshmen learning about loops and object creation in Unity, a single object that replicates itself is more convenient. Though, not entirely practical.

    But yes, I did a version with a separate builder. The problem was the same.

    The core issue was using Instantiate which duplicates objects in a scene rather than creating a fresh object. Each copy also came with copies of the children from the object it was copied from. That's just how Instantiate works according to the documentation.
     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,947
    Instantiate<T>()
    is sort of the one-stop-for-all "Make me a new thing!" type of operations.

    If you give it a prefab, it will make you an in-scene GameObject full copy

    If you give it a GameObject, it will make you another full copy

    If you give it a ScriptableObject it will copy it. Same goes for pretty much any Asset type.

    NOTE: it will NOT write the copy to disk! That's on you... see
    AssetDatabase
    Editor-only class.

    Instantiate copies "the public stuff," broadly speaking. Technically it is the "unity serialized stuff."

    If you give it a Component it will clone the same way as if you gave it the GameObject, but it will return you the reference to the cloned Component, which can save some time / hassle.

    In all cases, by the time Instantiate<T>() returns, both Awake() and OnEnable() WILL have been called.

    Here is some timing diagram help:

    https://docs.unity3d.com/Manual/ExecutionOrder.html
     
  8. Duck783

    Duck783

    Joined:
    Apr 6, 2023
    Posts:
    6
    It doesn't need but in this case I wanted it to. It was more of the challenge of replicating the behavior across different game engines. Both Unreal and GameMaker have a very straight-forward way of handling this sort of thing. It took some digging to get it to work just right in Unity.

    That said, I did try this.

    The issue was using Instantiate which also produced copies of the previous object and its children. It may simply have been that this effect gave the illusion of the frame start issue I thought I was having.