Search Unity

Why can't I change a property set in Start()?

Discussion in 'Scripting' started by zioziozio, Jul 2, 2015.

  1. zioziozio

    zioziozio

    Joined:
    Sep 11, 2012
    Posts:
    10
    Hi, I wasn't quite sure how to search for this but really want to ask. Also, I found a solution, but I worry if maybe my misunderstanding of what's actually going on might cause trouble in the future.

    So I have two types of GameObjects:
    1. VarHolder - A variable holding object.
    2. TestManager - A manager object, that has a list of the variable holding objects (VarHolder).
    They have the behaviors that follow attached respectively:

    Code (CSharp):
    1. // Varholder.cs
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class VarHolder : MonoBehaviour {
    6.     public int theNumber;
    7.  
    8.     // Use this for initialization
    9.     void Start () {
    10.         ////// This is the problem area //////
    11.         // theNumber = 37;
    12.     }
    13.  
    14.     // Update is called once per frame
    15.     void Update () {
    16.  
    17.     }
    18. }
    19.  
    Code (CSharp):
    1. // TestManager.cs
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6. public class TestManager : MonoBehaviour {
    7.     public VarHolder varHolderPrefab;
    8.     public List<VarHolder> varHolderList;
    9.  
    10.     // Use this for initialization
    11.     void Start () {
    12.         varHolderList = new List<VarHolder>();
    13.  
    14.         varHolderList.Add (GameObject.Instantiate(varHolderPrefab));
    15.  
    16.         for(int i = 0; i<5; i++){
    17.             makeRandomVarHolder();
    18.         }
    19.     }
    20.  
    21.     // Update is called once per frame
    22.     void Update () {
    23.  
    24.     }
    25.  
    26.     public void makeRandomVarHolder()
    27.     {
    28.         VarHolder temp = GameObject.Instantiate(varHolderPrefab);
    29.  
    30.         temp.theNumber = Random.Range(0, 777);
    31.  
    32.         varHolderList.Add(temp);
    33.     }
    34. }
    35.  
    As is, I get 6 VarHolders. One with the value of "0" as the value, and five with a random value between 0 and 777.

    But what snagged me was that I wanted a default value other than 0. So, if I uncomment line 9 in VarHolder.cs, to set the "default" value to "37" at creation.. ALL of them become "37". Even the ones that have a new number set (what I think is) after the Start() method runs..

    Why is that? What am I misunderstanding? When does Start() run exactly?

    Thanks!
     
    Last edited: Jul 3, 2015
  2. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,536
    Your prefab is a typeof VarHolder (MonoBehavior), not GameObject. What are you trying to instantiate? Maybe you want AddComponent()?
     
    zioziozio likes this.
  3. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    I think Start() will not run until the next frame.

    Would everything work fine if we delay it by a frame?
    Code (CSharp):
    1. public class TestManager : MonoBehaviour
    2. {
    3.     public VarHolder varHolderPrefab;
    4.     public List<VarHolder> varHolderList;
    5.     // Use this for initialization
    6.     void Start()
    7.     {
    8.         varHolderList = new List<VarHolder>();
    9.         varHolderList.Add (GameObject.Instantiate(varHolderPrefab));
    10.         StartCoroutine(MakeRandomHolder());
    11.     }
    12.  
    13.     IEnumerator MakeRandomHolder()
    14.     {
    15.         yield return null; //wait till next frame to run the below code
    16.  
    17.         for(int i = 0; i<5; i++)
    18.         {
    19.             makeRandomVarHolder();
    20.         }
    21.     }
    22.     public void makeRandomVarHolder()
    23.     {
    24.         VarHolder temp = GameObject.Instantiate(varHolderPrefab);
    25.         temp.theNumber = Random.Range(0, 777);
    26.         varHolderList.Add(temp);
    27.     }
    28. }

    What is it you are trying to do?

    Also, cant you just do this...
    Code (CSharp):
    1. public class VarHolder : MonoBehaviour
    2. {
    3.     public int theNumber = "enter desired default number here";
    4. }
     
    Last edited: Jul 2, 2015
  4. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,190
    If VarHolder does not need to be a monobehaviour, make it just a class, then you dont need to instantiate a load of (presumably) empty game Objects, and you can call an Init() function when you create the VarHolder's
     
    zioziozio likes this.
  5. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Code (CSharp):
    1. public int theNumber = 37;
    This will allow your code in start or a value set in the inspector to override the default value.
     
  6. zioziozio

    zioziozio

    Joined:
    Sep 11, 2012
    Posts:
    10
    Thanks for the input everyone, though I'm still a bit lost as to the "why", I learned a lot about the workings of this.

    I tried just setting theNumber to "37" in the declaration, but it still didn't work. Possibly to do with the Prefab having "0" set already.

    So here's what I'm trying to do in the end:
    The VarHolders will be RPG-like character stats. For example, name, strength, HP.. etc. But they will be used in a couple modes of gameplay. In a town simulator game and if the player chooses one to go on an adventure, those stats are used in an adventure mode with another GameObject, with colliders and all that other stuff. I thought making individual GameObjects was necessary to allow me to edit values in the Inspector.

    Like LaneFox said, I tried using addComponent instead of Instantiate and got the desired result. Being a Unity noob, I'm unfamiliar with working with components like this, but it might be an option. Can a GameObject pass a Component to another GameObject?

    Coming from other languages/libraries, I would like to use plain ol' Objects, but I'm not sure how to access the object properties with the Inspector seems like it would be easy to tweak things that way.

    Thanks again!
     
  7. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    yes, you can either type the default value in the prefab, or just right click the component on the prefab and click reset and it will default to what ever default you put in the script.

    If you want plain ol objects but be able to alter them in the inspector, then you can look into [Serializable] and [SerializeField]

    Examples..
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. [Serializable]
    5. public class TestStats
    6. {
    7.     public int health;
    8. }
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. public class UsingTestStats : MonoBehaviour
    5. {
    6.     [SerializeField]
    7.     TestStats testStats = new TestStats();
    8.  
    9.     void Start()
    10.     {
    11.         testStats.health = 100;
    12.     }
    13. }

    However, I think I would go down the path of having a health component, strength component, etc...
    This seems to be the way unity wants us to work.

    I don't think you can easily copy a component, but something like this might work for you.
    Code (CSharp):
    1.     public GameObject targetGameObject;
    2.  
    3.     void Start()
    4.     {
    5.         TestStats testStatsComponent = gameObject.AddComponent<TestStats>();
    6.         testStatsComponent.health = 100;
    7.         PassTestStatsComponent(targetGameObject, testStatsComponent);
    8.     }
    9.  
    10.     void PassTestStatsComponent(GameObject gObj, TestStats testStats)
    11.     {
    12.         TestStats gObjTestStats = gObj.AddComponent<TestStats>();
    13.         gObjTestStats.health = testStats.health;
    14.     }
     
    Last edited: Jul 3, 2015
  8. zioziozio

    zioziozio

    Joined:
    Sep 11, 2012
    Posts:
    10
    Serializable! That does exactly what I want. Though I think it's [System.Serializable] now.

    I'll have 6 stats and a name for each character, so for the data end of things, a component for each stat seems a bit cumbersome. But I think when I go into the adventure mode (platformer style a la ActRaiser or Wonderboy) a HP component might be best as a component on the character's GameObject? Well, I'll not speculate and just try it when the time comes!

    Thanks for the input!

    Just for reference, here's the final code I played with:

    This component was added to an empty GameObject
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class TestManager : MonoBehaviour {
    6.  
    7.     public List<PlainObject> plainObjectList;
    8.  
    9.     // Use this for initialization
    10.     void Start () {
    11.         plainObjectList = new List<PlainObject>();
    12.  
    13.         plainObjectList.Add (new PlainObject ());
    14.  
    15.         for(int i = 0; i<5; i++){
    16.             plainObjectList.Add (new PlainObject ("PlainGuy"+i, Random.Range(0,777)));
    17.         }
    18.     }
    19.  
    20.     // Update is called once per frame
    21.     void Update () {
    22.  
    23.     }
    24. }

    And this is the standalone plainObject class
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. [System.Serializable]
    5. public class PlainObject {
    6.     public string name;
    7.     public int myNumber;
    8.  
    9.     public PlainObject(string inName = "noName", int inNum = 37)
    10.     {
    11.         name = inName;
    12.         myNumber = inNum;
    13.     }
    14. }
    15.  
     
  9. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    Is there a reason you dont want to just have a AllStats component that has all your stats so you dont need to have a separate component for each?
     
  10. zioziozio

    zioziozio

    Joined:
    Sep 11, 2012
    Posts:
    10
    If I were to go the component route, I would put all of the stats for each character in town into one "AllStats" component.

    But when it comes to saving the data, or when I (theoretically) have a town of a million people, I wonder if having each character as an "AllStats" component will cause problems.

    Basically, it's just data, so I feel more comfortable with objects in a List. But maybe I should learn to use the Unity way of things...
     
  11. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    So I feel like nobody really gave you an explanation for the "why" of things, so here it is:

    the Start method is called on the start of the first frame an object exits. That means that if you instantiate something on frame 44, Start will be called in frame 45.

    Since you're setting the random values just as you've created the object (in the example, still in frame 44), the Start method gets called after, and the value gets overwritten.

    Awake, on the other hand, gets called just as the object is instantiated - somewhere inside your Instantiate or AddComponent calls. So in your original code, replacing Start with Awake would have fixed the problem, as the random assignment would have happened after the Awake code.

    Now, the serializeable class is still a much better idea, but I figured this explanation would help you (and others) in the future. :D
     
    zioziozio likes this.
  12. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    And to know all other order of events
    http://docs.unity3d.com/Manual/ExecutionOrder.html

    So your game will have millions on the screen at once? Will you be accessing all their components every frame or for a lot of the time? Will you only access the components of a couple of gameobjects or are you accessing thousands of gameobjects?
    How do you plan on having millions on screen?

    Make sure you are not prematurely optimising. It would probably cause you a lot of pain and waste a lot of time finding ways to gain performance when your game didnt even need it.
     
  13. Timelog

    Timelog

    Joined:
    Nov 22, 2014
    Posts:
    528
    It is the exact same :) "System" is just the namespace where serializable lives under :) If you add "using System;" in you namepspace declaration at the top of your file, you can use Serializable without System in front of it.
     
    zioziozio likes this.