Unity object initialization synchronization - c#

Until now, I've been coding thinking that functions in monobehaviour would be executed in this order:
OnEnable > Start > Update > OnDisable
The problem is that I thought that this order would be absolute, meaning that until a function is not completely finished, there's no way that the next one would start being executed.
So first question is: Is that true? Would update NOT be executed until start ends?
Then my problem arises with synchronization between different monobehaviour classes.
I have a class that creates some objects (a Menu basically) in its start function. Then in a different class, I've a similar code, but it also depends on the object created by the first class to exist already. I'm getting an error because the object is not found yet.
NullReferenceException: Object reference not set to an instance of an object ShopHandler.Start () (at Assets/Scripts/Shops/ShopHandler.cs:60)
So now I'm stuck with this. Therefore my second question would be,
How can I synchronize my different classes when they depend on others like this?
Finally, a question mixed from these two also has to be asked:
Would update function be executed in any of these classes, while they're somehow "waiting" for their initialization part, be it in start function, OnEnable function or whatever?
Because of course, update function relies on objects being initialized already, and this could end in new problems.
Thanks in advance

Here is my approach:
Let's say you have two monobehaviours A and B. Assume that B has to initialised after A. Then;
1-) Create a function, i.e "Initialise" and use it in B instead of the Start function:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class B : MonoBehaviour
{
public void Initialise()
{
//Code you run on Start()
}
}
2-) Reference B obj in A obj, use bObj.Initialise after are ready to initialise it:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class A : MonoBehaviour
{
public B bObj;
// Start is called before the first frame update
void Start()
{
//Things to do in Start()
//...
//...
//...
bObj.Initialise();
}
}
Lastly, if you want your Update function run whenever you want, I usually prefer to use something as a flag. So here's my second version of class B for controlling update() behavior:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class B : MonoBehaviour
{
public bool canUpdate;
public void Initialise()
{
//Code you run on Start()
canUpdate = true;
}
private void Update()
{
if (canUpdate)
{
//do the stuff
}
}
}

The problem is that I thought that this order would be absolute, meaning that until a function is not completely finished, there's no way that the next one would start being executed.
So first question is: Is that true? Would update NOT be executed until start ends?
Usually yes, but there is an exception.
If you implement Start as a coroutine, Update can be called before Start ends on the same monobehaviour.
For instance, this code:
IEnumerator Start()
{
Debug.Log("Start beginning");
yield return null;
Debug.Log("Start continuing");
yield return null;
Debug.Log("Start completing");
}
void Update()
{
Debug.Log("Update running");
}
Could produce this output:
Start beginning
Start continuing
Update running
Start completing
Update running
Update running
Update running
...

It is hard to say where your problem is actually coming from, because you have not posted any code. However, I believe your problem might be solved by implementing solution noted at 2
Yes, All Start Functions (and Awake) are called before the update function, if the gameobjects/ monobehaviours are enabled/active! Start/Awake/Update are not called on inactive gameobjects
You should look into Script Execution Order. Here you can make sure that the start/ awake/ update functions of classes are called in a certain order. It should be listed under project settings.
When initializing a scene, update will be called after all awake/ start functions have finished (unless they are defined as Coroutines, but this is probably not your issue).

Related

Unity scripting Ambient light turns off but I can't turn it back on with the same button

I've tried a few ways of doing it. I've asked a few friends but, nothing seems to work so far. I have changed the script several times so I don't have the other ways that I have tried anymore. However, this is the one that gave me the least errors. Others I had to continuously change it to a recommended way by Unity but ended at a dead end and nothing worked.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LightActivator : MonoBehaviour
{
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
gameObject.SetActive(false);
}
else if (Input.GetKeyDown(KeyCode.Space))
{
gameObject.SetActive(true);
}
}
}
I want to be able to turn on and off the ambient/directional light with just 1 button. Is that not possible?
two little flaws here
after you do
gameObject.SetActive(false);
this very same object is now inactive -> this component as well -> Update is no longer being called at all ;)
You always will ever only treat the first case .. the second one is never called at all since the condition will always match already with the first block!
Instead separate the logic from the target object and do e.g.
// This class should go to a different object that is always active in your scene
public class LightActivator : MonoBehaviour
{
// Here you reference the object you want to (de)activate via drag&drop in the Inspector
public GameObject theLight;
void Update()
{
// You only need one condition check
if (Input.GetKeyDown(KeyCode.Space))
{
// INVERT the active state of the light object whatever it currently is
theLight.SetActive(!theLight.activeSelf);
}
}
}
In order to keep things together you could e.g. simply make the according light object a child of the LightActivator object ;)

Use script for GameObject creation / changes outside of runtime

I have a prefab, which is used multiple times in my game (it will not be instatiated during runtime, I only use the prefab by dragging it into scene view to build my level).
Inside this prefab are many locations, where a specific text is written. This text is unique for every prefab.
So, it is like:
InstanceA
TextA (multiple times inside the instance)
InstanceB
TextB (multiple times inside the instance)
and so on ...
At the moment, I see two options to do it:
I change every text manually in every instance. This would work, but is a little annoying, also I can see me making mistakes (like a misspelling or forget to change one of the texts).
I solve it by a script and change the texts in the Awake or Start method. This would work, but it must be done every time, I start the game.
What I would hope for would be a solution like 2) but not every time (because nothing changes, it es every time the same) but only once - so it is generated by a script but after that, it will be always there. Even when I'm not in runtime and just work in the scene view.
Do you know any approach to get something done like this?
Thank you!
If you wish to run a piece of code outside the run-time, you can use one of the following two options.
OnValidate method
OnValidate is triggered when your MonoBehaviour script is loaded or an inspector value has changed.
using UnityEngine;
public class TestScript : MonoBehaviour
{
private void OnValidate()
{
Debug.Log("test");
}
}
ExecuteInEditMode attribute
Adding ExecuteInEditMode to a MonoBehaviour class will make it function both during and outside the runtime. If you do not wish the script to function during the runtime, feel free to also use Application.isPlaying.
using UnityEngine;
[ExecuteInEditMode]
public class TestScript : MonoBehaviour
{
private void Update()
{
if (!Application.isPlaying)
Debug.Log("test");
}
}

I'm studying the C# constructor and destructor with Unity, but I don't know why it works like this

I'm studying C#'s Constructer and destructor with unity component system.
I'm sorry if the English of this question is weird. I used a translator cause I am not good at English.
The log output came out like this.
The Constructor log was displayed without pressing the play button. Why?
When I pressed the play button, I saw a log of something being created and immediately disappearing. I didn't write a code to create an object after the game started, where does this phrase run?
This is my code.
project working structure
Pressing the space bar brings the pre-made pre-fab to the game world,
and Prefab has a component that attached to test the constructor and
destructor.
CubeFactory.cs / when Press the space bar, It creates Prefab.
this component was attached to "GameObject" Gameobject
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CubeFactory : MonoBehaviour
{
public GameObject obj;
private int pos = 1;
void Start()
{
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Instantiate(obj,new Vector3(pos,0,0),Quaternion.identity);
pos++;
}
}
}
ClassTest.cs / Component for Testing Constructor and Destructor. It attached Cube prefab.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ClassTest : MonoBehaviour
{
public ClassTest(){
Debug.Log("I was born!");
}
~ClassTest(){
Debug.Log("I Died x0x");
}
}
This is my project file. (I'm sorry to compress it up. I haven't learned how to use Git yet.)<
https://github.com/Scincy/UnityStudy
Please change your ClassTest.cs script to use simpler functions. Never use constructors and finalizers in Unity. What I understood from your logic is that when you press Spacebar, an object is instantiated. The ClassTest.cs is attached to this object. Better use:
void Start()
{
Debug.Log("I was born!"); //Will be called when the gameObject is active for the first time
}
Now you also want to know if the gameObject is destroyed, you can do this with another script attached to an always-active gameObject such as:
public GameObject cube;
void Update()
{
cube= GameObject.Find("Cube(Clone)"); //Depending on how your object is called
if(cube == null)
{
Debug.Log("Either not created or is destroyed");
}
}
I have used Update() which is called every frame. You could use a different function that you would like to call from another script only once.
Ye olde wisdom says that you should only use C# finalizers if your class contains a handle (IntPtr) to an object in unmanaged memory. If you're unclear what that means, then you probably don't need to be using finalizers; either way, you should never need to use them in a UnityEngine.Object-derived class.
Instead, prefer either of these Unity messages:
void OnDestroy()
{
// (1) called when the Object is about to be fully destroyed.
}
void OnDisable()
{
// (1) called when the Object is disabled in the Scene hierarchy.
// OR
// (2) called right before OnDestroy() would be called.
}
These Unity messages are of course available on MonoBehaviours, but also on all ScriptableObjects too! (For the latter, OnDisable essentially resolves to the same thing as OnDestroy since they are not Scene-bound Objects.)
Similarly, you should also avoid using C# constructors on MonoBehaviours and ScriptableObjects. The messages to prefer instead are:
void Awake()
void Start()
void OnEnable()
Each has its own quirks, so you may want to familiarize yourself with the differences and write some tests.
You can refer to the relevant documentation here: (see section header "Messages")
MonoBehaviour docs
ScriptableObject docs

C# Variable does not exist in current context

(I know this question has been asked, but none of them work for me)
In my script, i have an array which is out of scope (which is strange because i'm following a tutorial which works). Can anyone see why the array is not in scope?
Code:
using UnityEngine;
using System.Collections;
public class Patrol : MonoBehaviour
{
public Transform[] PartolPoints;
void Start ()
{
transform.position = PatrolPoints[0].position;
}
// Update is called once per frame
void Update ()
{
}
}
Are you getting the array at runtime?
If so, make sure you initialize the array in the Inspector by dragging your patrolpoint GameObjects into the array, or via Code by searching for your patrol points in the start() function.
Otherwise, if you have initialized your array correctly then you are getting out of the ordinary error and i recommend reinstalling the most recent release.
That code does work correctly, i confirmed it running the same version of unity on my side.

Unity 3D's component-based design model and race conditions

I'm having a bit of trouble with Unity 3D's Component-based design model.
Here's an example that demonstrates my problem:
class MyComponent : MonoBehaviour
{
MyType entity;
void Start()
{
entity = (MyType)FindObjectsOfType(typeof(MyType)).First();
}
void MyMethod()
{
var x = entity.SomeProperty; // <= NullReference exception
}
}
// ....
var clone = (GameObject)Instantiate(original);
clone.GetComponent<MyComponent>().MyMethod();
Sometimes, not always though, MyMethod executes before Start so what I end up doing is move all the initializations I usually have in Start to MyMethod which is quite an ugly workaround:
void Start() { }
void MyMethod()
{
entity = (MyType)FindObjectsOfType(typeof(MyType)).First();
var x = entity.SomeProperty; // <= now it's fine.
}
My question is, what is the correct way of working with this pattern (without a constructor)?
That probably happens when you call MyMethod from the Awake of another Component, because Awake is called before the game starts. The only solution I see is to make sure that you don't call methods on other components (in this case MyMethod) in the Awake() event, but rather in the Start() event.
The Unity documentation says "Awake is called before the game starts" but this is not precise. A component's Awake method is called when in a scene the gameobject becomes active which carries the component.
Some gameobjects will be active by default when a scene is loaded, but other gameobjects might be activated later. Then there is the possibility of dynamically adding components via AddComponent.
This aspect should be kept in mind when relying on dependencies between Awake and Start methods. Another example for a race condition is to enable a component from the Update method. While enabling a component implies that its Start method will be called, this will not happen in the running update cycle, so that other components cannot rely on the first component's ".enabled" property to determine wheter it has been started or not.

Categories