I'm doing some tests for the states of a game built using Unity, using something like the following:
script 1
public class ControladorEstados : MonoBehaviour {
private static ControladorEstados controladorEstados = null;
//public bool pause { get; set;} = false; //¿ usa c# 4?
private bool pause;
public bool Pause {
get { return pause; }
set { pause = value; }
}
private bool gameOver;
public bool GameOver {
get { return gameOver; }
set { gameOver = value; }
}
//protected ControladorEstados () {}
private ControladorEstados () {}
void Start () {
pause = false;
gameOver = false;
}
void Update () {
Debug.Log("Update");
if (gameOver == true) {
Debug.Log("GameOver Game");
}
if (pause) {
Debug.Log("Pause Game");
}
}
public static ControladorEstados getInstancia () {
if (controladorEstados == null) {
controladorEstados = new ControladorEstados ();
}
return controladorEstados;
}
}
script 2
..//
private ControladorEstados controladorEstados = null;
void Awake() {
rb = GetComponent<Rigidbody>();
controladorEstados = ControladorEstados.getInstancia ();
}
void Start() {
}
void Update () {
// test
gameOver ();
}
private void gameOver (){
controladorEstados.GameOver = true;
Debug.Log("test gameOver () " + controladorEstados.GameOver);
}
}
In thetest gameOver () log I can see that the variable is set to true, but the script ControladorEstados does not work as I expect when doing the update. It does not work as expected.
I have put Debug.Log("Update"); To see if it was running the Update, and it is running, but it does not branch into the if statement when it is true.
I have no experience in C # or Unity, even though it seems embarrassing for me to ask this question, but I have been at this for a while and I do not see the error. Additionally, I am using a Linux version and I do not know if that could be part of the error.
Update:
I just added this code change:
void Update () {
Debug.Log("Update" + gameOver);
if (gameOver == true) {
Debug.Log("GameOver Game");
}
..//
Its output is False so I can see it is not changing. In the script 2 it is true, but in the script 1 it is false. Please give me some ideas as to what I am doing wrong.
I solve it
script 1
void Awake () {
//controladorEstados = new ControladorEstados ();
if (controladorEstados == null) {
controladorEstados = this;
}
}
..//
public static ControladorEstados getInstancia () {
return controladorEstados;
}
With the response of Peter Duniho I create the code above, You can see the changes and I also knew the use of Script Execution Order To use an order among them.
I do not know if it's the best way, but it seems to work, I post it in case someone helps with something similar.
Without a good, Minimal, Complete, and Verifiable code example that reliably reproduces the problem, it's impossible to say for sure what you need to change to fix the problem.
But, it's clear you are dealing with two different instances of ControladorEstados. The instance that is running (and so the one for which Unity3d is calling the ControladorEstados.Update() method) is not the same instance that is being referenced by the controladorEstados variable in whatever class you are describing as "script 2".
In your ControladorEstados.getInstancia() method, you should not be creating a new instance of ControladorEstados. Instead, you need to do two things:
Set ControladorEstados.controladorEstados in the ControladorEstados class when it's created, e.g. in the Start() method.
Make sure the class you describe as "script 2" does not call getInstancia() until after step #1 has occurred.
Note that the above is just the most minimal steps you can take to resolve the issue. In all likelihood, there is a better way to query the Unity3d API to allow "script 2" to retrieve the instance of ControladorEstados that you want (e.g. to return the current, correct controller instance from a scene, or something like that). But without a more complete code example, it's impossible to say for sure what that better approach would look like, exactly.
Your approach is not exactly the Unity way. You're basically mixing a singleton with a MonoBehaviour.
Remember that Unity is component-driven. If you want a single instance of an object that extends MonoBehaviour just create an object that's always in the scene (like a GameManager) and add ControladorEstados to it.
You can then reference it from there in scripts where you need to use it instead of using a singleton.
Related
Is there a listener event to detect if an object is set to inactive from active state or to active from inactive state? I do not want to add it in Update as there will be multiple calls and it would affect my game's performance. So is there an alternative for this?
public GameObject Go_1;
public GameObject Go_2;
void Update () {
if (Go_1.activeSelf) {
} else if (Go_2.activeSelf) {
}
}
You could implement something using the Update method like e.g.
public class ActiveSelfWatcher : MonoBehaviour
{
private Dictionary<ActiveSelfProvider, bool> _lastActiveSelfState = new Dictionary<ActiveSelfProvider, bool>();
private void OnObjectBecameActive(GameObject obj)
{
Debug.Log($"{obj.name} became active!", this);
}
private void OnObjectBecameInactive(GameObject obj)
{
Debug.Log($"{obj.name} became inactive!", this);
}
private void Update()
{
// Iterate through all registered instances of ActiveSelfProvider
foreach(var provider in ActiveSelfProvider.Instances)
{
// pre-cache the GameObject reference
var obj = provider.gameObject;
// pre-cache the current activeSelf state
var currentActive = obj.activeSelf;
if(!_lastActiveSelfState.TryGetValue(provider))
{
// we don't know this provider until now
// TODO here you have to decide whether you want to call the events now once for this provider or not
// TODO otherwise it is only called for providers you already know and changed their state
}
if(currentActive != _lastActiveSelfState[provider])
{
// the state is not the one we stored for this instance
// => it changed its states since the last frame
// Call the according "event"
if(currentActive)
{
OnObjectBecameActive(obj);
}
else
{
OnObjectBecameInactive(obj);
}
}
// store the current value
_lastActiveSelfState[provider] = currentActive;
}
}
}
This is your watcher class you currently already have anyway.
Then on all the objects you want to be able to watch you use
public class ActiveSelfProvider : MonoBehaviour
{
private static readonly HashSet<ActiveSelfProvider> instances = new HashSet<ActiveSelfProvider>();
public static HashSet<ActiveSelfProvider> Instances => new HashSet<ActiveSelfProvider>(instances);
private void Awake()
{
// register your self to the existing instances
instances.Add(this);
}
private void OnDestroy()
{
// remove yourself from the existing instances
instances.Remove(this);
}
}
If this is "efficient enough" for you use case you would have to test ;)
If you want to go super fancy, someone once made a Transform Interceptor .. a quite nasty hack which on compile time overrules parts of the Unity built-in Transform property setters to hook in callbacks.
One probably could create something like this also for SetActive ;)
Note: Typed on smartphone but I hope the idea gets clear
Yes, try with OnEnable and OnDisable.
// Implement OnDisable and OnEnable script functions.
// These functions will be called when the attached GameObject
// is toggled.
// This example also supports the Editor. The Update function
// will be called, for example, when the position of the
// GameObject is changed.
using UnityEngine;
[ExecuteInEditMode]
public class PrintOnOff : MonoBehaviour
{
void OnDisable()
{
Debug.Log("PrintOnDisable: script was disabled");
}
void OnEnable()
{
Debug.Log("PrintOnEnable: script was enabled");
}
void Update()
{
#if UNITY_EDITOR
Debug.Log("Editor causes this Update");
#endif
}
}
This is the script where the touch action, the main game is on it
var addingGoldPerSec = new GameObject();
var buttonInstance = addingGoldPerSec.AddComponent<ItemButton>();
StartCoroutine(buttonInstance.addGoldLoop());
And this is the one where I have the coroutine
public double goldPerSec
{
get
{
if (!PlayerPrefs.HasKey("_goldPerSec"))
{
return 0;
}
string tmpStartGoldPerSec = PlayerPrefs.GetString("_goldPerSec");
return double.Parse(tmpStartGoldPerSec);
}
set
{
PlayerPrefs.SetString("_goldPerSec", value.ToString());
}
}
public void updateUI()
{
priceDisplayer.text = LargeNumber.ToString(currentCostPerSec).ToString();
coinDisplayer.text = "PER SEC\n" + LargeNumber.ToString(goldPerSec).ToString();
levelDisplayer.text = "LEVEL\n" + level + "/150".ToString();
}
public IEnumerator addGoldLoop()
{
while (DataController.Instance.gold <= 1e36)
{
DataController.Instance.gold += goldPerSec;
if (Gameplay.Instance.booster == 1)
{
yield return new WaitForSeconds(0.25f);
}
else if (Gameplay.Instance.booster == 0)
{
yield return new WaitForSeconds(1.0f);
}
}
}
And this is a third Script where I manage the data stored into PlayerPrefs
public void loadItemButton(ItemButton itemButton)
{
itemButton.level = PlayerPrefs.GetInt("_level",1);
PlayerPrefs.GetString("_costPerSec", itemButton.currentCostPerSec.ToString());
PlayerPrefs.GetString("_goldPerSec",itemButton.goldPerSec.ToString());
}
public void saveItemButton(ItemButton itemButton)
{
PlayerPrefs.SetInt("_level", itemButton.level);
PlayerPrefs.SetString("_costPerSec", itemButton.currentCostPerSec.ToString());
PlayerPrefs.SetString("_goldPerSec", itemButton.goldPerSec.ToString());
}
I have the second script which is the coroutine one attached into several gameobjects where exists an Upgrade button, per upgrade increases the coin by second you earn, the main problem here is that the coroutine just stops after I touch the screen, so I made another code into the main script so by that way the coroutine will keep working even after touched the screen, so I made a script where is the GameObject, but it just throws me NullReferenceException, tried a check with TryCatch and throws me that the problem is coming from the GameObject that I have created on the main script, but if is that way I need to attach to the main object where the main script exists, like more than 10 gameobjects where the coroutine exists, I think Singleton is not the way, it deletes me all the information above there by instantiating on Awake, I never thought about making static so I did as you told me and I need to change almost of my code, every text is attached into each gameobjects, to make a non-static member work with a static-member need to delete Monobehaviour but it just makes the game explode, thanks for helping me.
Create two scripts and attach them, for example, at Main Camera. The first script contains your timer with all variables:
using UnityEngine;
using System.Collections;
public class TimerToCall : MonoBehaviour {
//Create your variable
...
//Your function timer
public IEnumerator Counter() {
...
}
}
In the second script, you will call the timer:
public class callingTimer : MonoBehaviour {
void Start() {
//TimerToCall script linked to Main Camera, so
StartCoroutine(Camera.main.GetComponent<TimerToCall>().Counter());
}
}
if you want to have for example the second script not linked to any gameObjet you could use static property:
in first script linked:
void Start()
{
StartCoroutine(CallingTimer.ExampleCoroutine());
}
in second script not linked, you use the static property:
using System.Collections;
using UnityEngine;
public class CallingTimer
{
public static IEnumerator ExampleCoroutine()
{
//Print the time of when the function is first called.
Debug.Log("Started Coroutine at timestamp : " + Time.time);
//yield on a new YieldInstruction that waits for 5 seconds.
yield return new WaitForSeconds(5);
//After we have waited 5 seconds print the time again.
Debug.Log("Finished Coroutine at timestamp : " + Time.time);
}
}
I'm really struggling with creating a weapon/item unlock system via an in-game shop. The player should only be able to select weapons at indexes that have been unlocked or bought.
I've wasted two days on the best way to achieve this and I'm still not anywhere near a solution.
Currently this is what I have:
I have a WeaponUnlock script that handles a dictionary of weapons and another WeaponSwitcher script that access vales from that script to check if it is unlocked or not.
public class WeaponUnlockSystem : MonoBehaviour
{
private Dictionary<string, bool> weaponUnlockState;
void Start()
{
// Seeding with some data for example purposes
weaponUnlockState = new Dictionary<string, bool>();
weaponUnlockState.Add("Shotgun", true);
weaponUnlockState.Add("Rifle", true);
weaponUnlockState.Add("FireballGun", false);
weaponUnlockState.Add("LaserGun", false);
weaponUnlockState.Add("FlamethrowerGun", false);
weaponUnlockState.Add("SuperGun", false);
}
public bool IsWeaponUnlocked(string weapon_)
{
if (weaponUnlockState.ContainsKey(weapon_))
return weaponUnlockState[weapon_]; // this will return either true or false if the weapon is unlocked or not
else
return false;
}
public void UnlockWeapon(string weapon_) //this will be called by the button..
{
if (weaponUnlockState.ContainsKey(weapon_))
weaponUnlockState[weapon_] = true;
}
}
The WeaponSwitchergets the name of the weapon from the nested children every time the nextWeapon button is pressed:
weaponName = this.gameObject.transform.GetChild(weaponIdx).name.ToString(); //get the name of the child at current index
if (weaponUnlock.IsWeaponUnlocked(weaponName)) //ask weaponUnlockSystem if this name weapon is unlocked or not, only enable the transform if true is returned
selectedWeapon = weaponIdx;
This...... works..... but I know is not practical. As the script sits on a game object and at every time it is called the Start() is called that will reset all the unlocked values. Also will I have to make it persistent via DontDestroyOnLOad() through scenes?
I can get use PlayerPrefs and set a value for every weapon, but that is also not ideal and also to avoid it being reset every time someone opens my game I would have to perform a checks of PlayerPrefs.HasKey() for every weapon. Also not practical.
There must be a better way of doing this. Its funny that I cannot find much help online for this and dont know how everyone is getting around this.
Thanks
You create a class that is not a GameObject, just a normal class. There you can initialize the dictionary and have methods to unlock or check if a weapon is unlocked.
As I don't know what your architecture of the rest of the code looks like, I will go ahead and assume that you have some kind of a persistent player model.
You could just add the WeaponUnlockSystem to your player GameObject and manage the unlocks from there.
To me this would make the most sense as the player is probably the one unlocking the weapons so the WeaponUnlockSystem should be attached to him.
Here is a really basic code example:
public class WeaponUnlockSystem {
private Dictionary<string, bool> weaponUnlockState;
public WeaponUnlockSystem()
{
weaponUnlockState = new Dictionary<string, bool>();
weaponUnlockState.Add("Shotgun", true);
weaponUnlockState.Add("Rifle", true);
weaponUnlockState.Add("FireballGun", false);
weaponUnlockState.Add("LaserGun", false);
weaponUnlockState.Add("FlamethrowerGun", false);
weaponUnlockState.Add("SuperGun", false);
}
public bool IsWeaponUnlocked(string weapon)
{
if (weaponUnlockState.ContainsKey(weapon))
return weaponUnlockState[weapon];
else
return false;
}
public void UnlockWeapon(string weapon)
{
if (weaponUnlockState.ContainsKey(weapon))
weaponUnlockState[weapon] = true;
}
}
public class Player : MonoBehaviour {
private WeaponUnlockSystem weaponUnlockSystem;
void Start()
{
weaponUnlockSystem = new WeaponUnlockSystem();
...
}
public void NextWeapon(string weapon)
{
...
}
}
I would also advice against using a static class for convenience here. Static should only be used, if you don't want to change the state of an object or don't need to create an instance of an object.
I had a Unity project recently where we needed to hold and manipulate some simple data between scenes. We just made a static class that doesn't extend monobehaviour.
Also, if you're looking for efficiency, why not just maintain an array of unlocked weapons?
All in all, why not try something like this:
public static class WeaponUnlockSystem
{
private static string[] unlockedWeapons = InitialWeapons();
private static string[] InitialWeapons(){
string w = new string[]{
"Shotgun",
"Rifle",
}
}
public static bool IsWeaponUnlocked(string name)
{
int i = 0;
bool found = false;
while(i < unlockedWeapons.Length && !found){
if (string.Equals(unlockedWeapons[i],name)){
found = true;
}
i++;
}
return found;
}
public static void UnlockWeapon(string name)
{
string[] weapons = new string[unlockedWeapons.Length+1];
int i = 0;
bool found = false;
while(i < unlockedWeapons.Length && !found){
if (string.Equals(unlockedWeapons[i],name)){
found = true;
} else {
weapons[i] = unlockedWeapons[i];
}
}
if(!found){
weapons[unlockedWeapons.Length] = name;
unlockedWeapons = weapons;
}
}
I'm not running my c# IDE, so I apologise if there's any syntax errors. Should be efficient, simple and - as it's static - you shouldn't need to put it on an empty GameObject to have it work.
I'm developing an open source editor tool for Unity3D https://github.com/JAFS6/BoxStairsTool and I'm writting a CustomEditor.
I create a main GameObject and I attach my script BoxStairs to it. This script attachs to the same GameObject a BoxCollider.
On my CustomEditor code, I have a method which is in charge of removing both two components attached before to finalize editing.
This is the code:
private void FinalizeStairs ()
{
Undo.SetCurrentGroupName("Finalize stairs");
BoxStairs script = (BoxStairs)target;
GameObject go = script.gameObject;
BoxCollider bc = go.GetComponent<BoxCollider>();
if (bc != null)
{
Undo.DestroyObjectImmediate(bc);
}
Undo.DestroyObjectImmediate(target);
}
This method is called on the method OnInspectorGUI after a button has been pressed
public override void OnInspectorGUI ()
{
...
if (GUILayout.Button("Finalize stairs"))
{
FinalizeStairs();
}
}
Both two methods are on the class
[CustomEditor(typeof(BoxStairs))]
public sealed class BoxStairsEditor : Editor
It actually removes the two components but, once the BoxCollider has been removed the following error appears:
MissingReferenceException: The object of type 'BoxCollider' has been
destroyed but you are still trying to access it.
I try to locate where the error is ocurring by looking at the trace:
Your script should either check if it is null or you should not destroy the object.
UnityEditor.Editor.IsEnabled () (at C:/buildslave/unity/build/Editor/Mono/Inspector/Editor.cs:590)
UnityEditor.InspectorWindow.DrawEditor (UnityEditor.Editor editor, Int32 editorIndex, Boolean rebuildOptimizedGUIBlock, System.Boolean& showImportedObjectBarNext, UnityEngine.Rect& importedObjectBarRect) (at C:/buildslave/unity/build/Editor/Mono/Inspector/InspectorWindow.cs:1154)
UnityEditor.InspectorWindow.DrawEditors (UnityEditor.Editor[] editors) (at C:/buildslave/unity/build/Editor/Mono/Inspector/InspectorWindow.cs:1030)
UnityEditor.InspectorWindow.OnGUI () (at C:/buildslave/unity/build/Editor/Mono/Inspector/InspectorWindow.cs:352)
System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Reflection/MonoMethod.cs:222)
But none of my scripts appears on it.
I've been looking on the code where I'm referencing the BoxCollider and the only place is where it is created, when the stairs are created which is triggered once a change has happened on the inspector.
It is in the class:
[ExecuteInEditMode]
[SelectionBase]
public sealed class BoxStairs : MonoBehaviour
This is the code:
/*
* This method creates a disabled BoxCollider which marks the volume defined by
* StairsWidth, StairsHeight, StairsDepth.
*/
private void AddSelectionBox ()
{
BoxCollider VolumeBox = Root.GetComponent<BoxCollider>();
if (VolumeBox == null)
{
VolumeBox = Root.AddComponent<BoxCollider>();
}
if (Pivot == PivotType.Downstairs)
{
VolumeBox.center = new Vector3(0, StairsHeight * 0.5f, StairsDepth * 0.5f);
}
else
{
VolumeBox.center = new Vector3(0, -StairsHeight * 0.5f, -StairsDepth * 0.5f);
}
VolumeBox.size = new Vector3(StairsWidth, StairsHeight, StairsDepth);
VolumeBox.enabled = false;
}
I've tried to comment this method's body to allow removing the BoxCollider without this "reference" and the error still appears, so, I guess this method is not the problem.
Also, I've removed the BoxCollider manually, without clicking the Finalize button to trigger this code, via right click on the component on the inspector "Remove Component" option and the error not appears and after that, click over finalize stairs and no problem shows up.
As #JoeBlow mentioned in the comments I've checked that the FinalizeStairs method is called just once.
Also I've checked that the process of creation with the call to AddSelectionBox method it is not happening on the moment of clicking finalize button.
So, please I need a hand on this. This is the link to the development branch https://github.com/JAFS6/BoxStairsTool/tree/feature/BoxStairsTool, here you will find that the above mentioned method FinalizeStairs has the code wich removes the BoxStairs script only and on that moment it throws no errors.
Any idea or advice on this will be very helpful. Thanks in advance.
Edit:
a Minimal, Complete, and Verifiable example:
Asset/BoxStairs.cs
using UnityEngine;
using System.Collections.Generic;
namespace BoxStairsTool
{
[ExecuteInEditMode]
[SelectionBase]
public sealed class BoxStairs : MonoBehaviour
{
private GameObject Root;
private void Start ()
{
Root = this.gameObject;
this.AddSelectionBox();
}
private void AddSelectionBox()
{
BoxCollider VolumeBox = Root.GetComponent<BoxCollider>();
if (VolumeBox == null)
{
VolumeBox = Root.AddComponent<BoxCollider>();
}
VolumeBox.size = new Vector3(20, 20, 20);
VolumeBox.enabled = false;
}
}
}
Asset\Editor\BoxStairsEditor.cs
using UnityEngine;
using UnityEditor;
namespace BoxStairsTool
{
[CustomEditor(typeof(BoxStairs))]
public sealed class BoxStairsEditor : Editor
{
private const string DefaultName = "BoxStairs";
[MenuItem("GameObject/3D Object/BoxStairs")]
private static void CreateBoxStairsGO ()
{
GameObject BoxStairs = new GameObject(DefaultName);
BoxStairs.AddComponent<BoxStairs>();
if (Selection.transforms.Length == 1)
{
BoxStairs.transform.SetParent(Selection.transforms[0]);
BoxStairs.transform.localPosition = new Vector3(0,0,0);
}
Selection.activeGameObject = BoxStairs;
Undo.RegisterCreatedObjectUndo(BoxStairs, "Create BoxStairs");
}
public override void OnInspectorGUI ()
{
if (GUILayout.Button("Finalize stairs"))
{
FinalizeStairs();
}
}
private void FinalizeStairs ()
{
Undo.SetCurrentGroupName("Finalize stairs");
BoxStairs script = (BoxStairs)target;
GameObject go = script.gameObject;
BoxCollider bc = go.GetComponent<BoxCollider>();
if (bc != null)
{
Undo.DestroyObjectImmediate(bc);
}
Undo.DestroyObjectImmediate(target);
}
}
}
Analysis
I'm a programmer, so I just debug to find the problem (in my mind :D).
MissingReferenceException: The object of type 'BoxCollider' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
UnityEditor.Editor.IsEnabled () (at C:/buildslave/unity/build/Editor/Mono/Inspector/Editor.cs:590)
A MissingReferenceException occurs when the code trys to access a Unity3D.Object after it has been destroyed.
Let's look into the decompiled code of UnityEditor.Editor.IsEnabled().
internal virtual bool IsEnabled()
{
UnityEngine.Object[] targets = this.targets;
for (int i = 0; i < targets.Length; i++)
{
UnityEngine.Object #object = targets[i];
if ((#object.hideFlags & HideFlags.NotEditable) != HideFlags.None)
{
return false;
}
if (EditorUtility.IsPersistent(#object) && !AssetDatabase.IsOpenForEdit(#object))
{
return false;
}
}
return true;
}
We won't be able to know which line is the specific line 590. But, we can tell where can a MissingReferenceException happen:
// ↓↓↓↓↓↓
if ((#object.hideFlags & HideFlags.NotEditable) != HideFlags.None)
#object is assigned from Editor.targets which is an array of all the object being inspected. There should be only one target object in this array in your case - the BoxCollider component.
In conclusion, the inspector failed to access the target object (I mean targets[0]) after you calls Undo.DestroyObjectImmediate on the BoxCollider component.
If you dig into the decompiled code of the inspector(UnityEditor.InspectorWindow), you will see that the overridden OnInspectorGUI function is called per Editor in order in UnityEditor.InspectorWindow.DrawEditors, including the internal editor of BoxCollider and your custom editor BoxStairsEditor of BoxStairs .
Solutions
Do not destory a component that is being shown by the Inspector in OnInspectorGUI.
Maybe you can add a delegate instance to EditorApplication.update to do that instead. In this way, the deleting operation will not break the editor/inspector GUI of BoxCollider.
Move the created component BoxCollider upper than your BoxStairs component before you destroy it. This may work but I'm not sure if other internal editor will access the BoxCollider or not. This solution not works when using UnityEditorInternal.ComponentUtility.MoveComponentUp. But, if the user manually move up the BoxCollider component, it works without any code changes.
Code of solution
After using solution 1, the NRE is gone on Unity3D 5.4 on Win10.
using UnityEngine;
using UnityEditor;
namespace BoxStairsTool
{
[CustomEditor(typeof(BoxStairs))]
public sealed class BoxStairsEditor : Editor
{
private const string DefaultName = "BoxStairs";
[MenuItem("GameObject/3D Object/BoxStairs")]
private static void CreateBoxStairsGO ()
{
GameObject BoxStairs = new GameObject(DefaultName);
BoxStairs.AddComponent<BoxStairs>();
if (Selection.transforms.Length == 1)
{
BoxStairs.transform.SetParent(Selection.transforms[0]);
BoxStairs.transform.localPosition = new Vector3(0,0,0);
}
Selection.activeGameObject = BoxStairs;
Undo.RegisterCreatedObjectUndo(BoxStairs, "Create BoxStairs");
}
private void OnEnable ()
{
EditorApplication.update -= Update;
EditorApplication.update += Update;
}
public override void OnInspectorGUI ()
{
if (GUILayout.Button("Finalize stairs"))
{
needFinalize = true;
}
}
private void FinalizeStairs ()
{
Undo.SetCurrentGroupName("Finalize stairs");
BoxStairs script = (BoxStairs)target;
GameObject go = script.gameObject;
BoxCollider bc = go.GetComponent<BoxCollider>();
if (bc != null)
{
Undo.DestroyObjectImmediate(bc);
}
Undo.DestroyObjectImmediate(target);
}
bool needFinalize;
void Update()
{
if(needFinalize)
{
FinalizeStairs();
needFinalize = false;
EditorApplication.update -= Update;
}
}
}
}
Why would I get the following log?
Start OpponentMotionReceiver
motion receiver not found
true
public class ScoreAnimation : MonoBehaviour
{
private OpponentMotionReceiver cachedObject;
private void Start()
{
cachedObject = FindObjectOfType<OpponentMotionReceiver>();
}
private void OnDestroy()
{
var motionReceiver = FindObjectOfType<OpponentMotionReceiver>();
if (motionReceiver == null)
{
Debug.Log("motion receiver not found");
}
if(cachedObject != null)
{
//prints true, another proof that the gameObject is active
Debug.Log(cachedObject.gameObject.activeInHierarchy);
}
}
}
public class OpponentMotionReceiver : MonoBehaviour
{
private void Start()
{
Debug.Log("Start OpponentMotionReceiver");
}
private void OnDisable()
{
Debug.Log("OnDisable OpponentMotionReceiver");
}
private void OnDestroy()
{
Debug.Log("OnDestroy OpponentMotionReceiver");
}
}
P.S. This is extremely simplified version of the code so that the rest brings no confusion. If you need more details, I'd be pleased to answer you!
Changing the scene. Only at that time unity behaves like that. No matter if the object we are looking for is active or not, simply FindObjectOfType doesn't work at this point of the game cycle.
From https://gamedev.stackexchange.com/a/112783/59620
Have you tried to call method by using typeof i.e.FindObjectOfType(typeof(OpponentMotionReceiver)) as stated in official document ?
When OnDestroy is called in your component the GameObject is already deactivated. It's likely already torn out of the normal hierarchy.
Also, if the scene is leaving, it looks like all GameObject instances in the scene that are not set to DontDestroyOnLoad are first deactivated.
When OnDestroy is called all the objects you are looking for are already deactivated.