I'm trying to access a value of a local variable inside a lambda according to this post, but I get this excepton:
MissingReferenceException: The object of type 'Transform' has been destroyed but you are still trying to access it.
Here is my code:
private static void ValidateComponent<T>(Transform transform, Delegate validationMethod, object[] args) where T : Component
{
#if UNITY_EDITOR // EditorApplication is only available when running in the editor
if (EditorApplication.isPlaying) return;
if (transform == null) return; // in case inspector field is not assigned
var t = transform;
EditorApplication.delayCall += () =>
{
var component = t.GetComponent<T>();
if (component == null)
{
LogAddingComponent<T>(t);
var addedComponent = t.gameObject.AddComponent<T>();
validationMethod.DynamicInvoke(args.Prepend(addedComponent));
}
else
{
validationMethod.DynamicInvoke(args.Prepend(component));
}
};
#endif
}
What am I doing wrong?
You are attempting to pin transform via the variable t only to use it later during your delayCall callback. That's dangerous considering many types in Unity contain unmanaged resources hence the error. The effect is the same if you created a Font in WinForms, Disposed it, then attempted to use the font.
OP:
Is there a better way to approach this then?
From what I understand, Transforms are always attached to the GameObject and it is the GameObject that is being destroyed taking the Transform along with it.
The only way I know to prevent game object destruction is viaDontDestroyOnLoad but I'm not sure if its applicable here since the latter is really designed for protection whilst transitioning to a new scene, but you never know perhaps it prevents things from being destroyed in other scenarios.
Try calling DontDestroyOnLoad at the top of ValidateComponent like so:
private static void ValidateComponent<T>(Transform transform,
Delegate validationMethod,
object[] args)
where T : Component
{
#if UNITY_EDITOR // EditorApplication is only available when running in the editor
if (EditorApplication.isPlaying) return;
if (transform == null) return; // in case inspector field is not assigned
DontDestroyOnLoad (transform.gameObject); // <---- new
var t = transform;
.
.
.
Otherwise, there is this Unity Answer, that suggests simply disabling and re-enabling the object rather than whatever is destroying the object in the first place. Following that approach you shouldn't require any changes to your code above.
Editor-time-only scripts
BTW, regarding the #if UNITY_EDITOR bit, if you want to avoid having to decorate your code with #ifs everywhere (as an ex c/c++ programmer I certainly don't miss having to do that all the time), consider simply moving your script into a child folder called Editor. Unity automatically treats anything placed in such folders as Editor-time-only and won't be incorporated into your final game build. Better yet, no need for the #if stuff.
The best reason for it though is to prevent build errors when Unity is producing your final game due to forgetting to either use #if or Editor folders. I typically run into this one with 3rd party scripts obtained from the Unity store. Instead of having to fix the code I simply move it to a child Editor folder.
See also
Special folder names, Unity
Related
In Unity I have a script in which I connect to a socket via TCP and want to use this connection every frame. I need to dispose and clean up after that. My idea was to use Start() to start a connection and OnDestroy()
public class Foo : MonoBehaviour
{
void Start()
{
// start TCP connection
}
void Update()
{
// use connection
}
void OnDestroy()
{
// cleanup
}
}
I need the cleanup to execute whatever happens. Does the OnDestroy() method guarantee to be called before the application stops (in standalone mode and in editor) no matter what happens to the object? If not, how can I guarantee the cleanup?
No it is not!
Even OnApplicationQuit might not get called when your app e.g. crashes for some reason.
And there are other specific cases where neither is called. I know that from my own experience that e.g. on the HoloLens2 apps are not closed but only hibernated. If you then close them via the HoloLens home "menu" then you actually kill them via task manager.
This is pretty dirty and causes neither OnDestroy nor OnApplicationQuit or any other Unity specific messages to be called and we ended up with zomby threads and still occupied TCP ports.
If you really want to go sure (e.g. for giving free the connection, killing threads etc) what I finally did was creating a dedicated class with deconstructor (Finalizer)
The deconstructor is pure c# and does not rely on Unity being shutdown correctly so it is guaranteed to be called even if the app was terminated due to a crash as soon as the Garbage Collector automatically does its job.
A MonoBehaviour itself shouldn't implement any constructor nor destructor but a private "normal" class can:
public class Foo : MonoBehaviour
{
private class FooInternal
{
private bool disposed;
public FooInternal()
{
// create TCP connection
// start thread etc
}
public void Update ()
{
// e.g. forward the Update call in order to handle received messages
// in the Unity main thread
}
public ~FooInternal()
{
Dispose();
}
public void Dispose()
{
if(disposed) return;
disposed = true;
// terminate thread, connection etc
}
}
private FooInternal _internal;
void Start()
{
_internal = new FooInternal ();
}
void Update()
{
_internal.Update();
}
void OnDestroy ()
{
_internal.Dispose();
}
}
if you never pass on the reference to _internal to anything else, the GC should automatically kill it after this instance has been destroyed as well.
You're looking for OnEnable for establishing your connection, and OnDisable for cleaning it up.
The issue with OnDestroy (and OnApplicationQuit fwiw) is it won't be called if the script is disabled, which can happen if:
You call SetActive(false) on the GameObject or any of its parents.
You set enabled = false on the script component.
You disable the GameObject or any of its parents in play mode by unchecking the box in the inspector.
You disable the script component in play mode by unchecking the box in the inspector.
The editor is about to recompile the scripts while play mode is running (an especially irritating event to miss for certain types of cleanup code [cough lookin' at you, Vuforia]).
The issue with Start is it won't be called if the script is re-enabled after having been disabled (it'll be called the first time, but no more), which happens in all of the cases opposite of the above list (as well as when the editor finishes recompilation of scripts in play mode).
On the other hand:
OnDisable will be called when the script transitions to disabled for any of the above reasons, plus all the stuff that OnDestroy and OnApplicationQuit cover.
OnEnable will be called when the script transitions to enabled for any of the above reasons, plus all the stuff that Start covers.
Additionally, you will have a 1:1 correspondence of OnEnable and OnDisable calls by Unity (of course you can call them yourself whenever you want).
You will only almost have a 1:1 correspondence of Start and OnDestroy: The notable exception is if a script component is initially disabled (i.e. you disabled it in edit mode) but its GameObject is enabled, you'll still get OnDestroy called on the script even if Start wasn't called (which may be a bug now that I think about it). Plus, of course, you will miss all the above cases.
OnEnable + OnDisable, however, handle all that cleanly, and generally do exactly what you hope that they do, in a straightforward manner with (theoretically) no weird quirks or gotchas.
For the question itself, yes, OnDestroy is reliable: It is called as documented when the script is destroyed.
Of course it wouldn't be called if the application crashes, but in general you'd have bigger problems and that's not the kind of thing you normally code around. It also probably wouldn't be called if you exploded a grenade next to the machine running the game but, again, that's all in "undefined behavior" territory.
This is a pretty simple question, but it seems something has changed on Unity in the last versions answers I've found on the internet are no longer valid, so here goes nothing:
I got some UI elements, and a "InputController" class which is intended to handle the user input during the game (Input on the controllers are handled through the onclick events).
What I'm looking is for a way to being able to know if the mouse is clicking a UI element to block the execution of my input handling (and avoid the user clicking on "pause" while also the game executes "left button clicked."
Now, most solutions I've fond were a bit messy or used EventSystem.current.IsPointerOverGameObject() (like this one, which was shown when writing this question), which in 2019.4 does not longer appear. So, there's any new way to do this, do I have to make some hacky solution to receive the event from the UI, then block the execution of my code or am I missing something here?
You should look into interfaces like IPointerEnterHandler and IPointerExitHandler. If you implement these interfaces for your UI elements, you can add the necessary code to the OnPointerEnter and OnPointerExit methods that those interfaces require.
It should be as simple as adding a bool to your InputController such as isInputEnabled and only handling input when that is true. Set it to false OnPointerEnter and true OnPointerExit.
I spent a good amount of time trying to figure this out as well. I am on Unity 2022.1 using the Input System and UI Toolkit (UI Elements)
The following should help anyone else who is struggling with this to get the behavior they need.
Examine your UI Documents
You don't want your UI Document to always report clicks. So you need to set the picking mode in your UXML documents accordingly. Have a look at the images below,
In the image on the left, I have a wrapping element that allows me to position the panel at the bottom of the document. By default this element will receive all pointer events. What I actually want is to only receive pointer events inside of the orange area seen in the image on the right.
I can fix this by setting the picking mode of all parent elements to ignore:
Set up the input system
Setting up the new input system is a topic in itself. Refer to the documentation for more information, but in the image you can see I am using a simple button event as a click/tap action:
Set up your script
Next you need to respond to the input action and check if your input is seen by the UI
public class SimpleInput : MonoBehaviour
{
public Camera ViewCamera;
private PlayerControls _controls;
private void OnEnable()
{
Assert.IsNotNull(ViewCamera, "ViewCamera cannot be null");
// This class will vary depending on the name of your Input Action Asset
_controls = new PlayerControls();
_controls.Enable();
_controls.Gameplay.TapAction.performed += OnInputTapAction;
}
private void OnInputTapAction(InputAction.CallbackContext obj)
{
Vector2 position = Pointer.current.position.ReadValue();
Ray ray = ViewCamera.ScreenPointToRay(position);
if (PointerIsUIHit(position))
{
Debug.Log("Ui event received");
}
else
{
// Perform game-world events here
}
}
private bool PointerIsUIHit(Vector2 position)
{
PointerEventData pointer = new PointerEventData(EventSystem.current);
pointer.position = position;
List<RaycastResult> raycastResults = new List<RaycastResult>();
// UI Elements must have `picking mode` set to `position` to be hit
EventSystem.current.RaycastAll(pointer, raycastResults);
if (raycastResults.Count > 0)
{
foreach (RaycastResult result in raycastResults)
{
if (result.distance == 0 && result.isValid)
{
return true;
}
}
}
return false;
}
}
The helper method PointerIsUIHit was inspired by a conversation found in the Unity Forums which had some insight into how to get this done. It also shed some insight into the frustrating experience of being a Unity Dev.
Hopefully this helps other people struggling to find a proper guide.
User can place objects (prefabs) at runtime using hands/gaze.
Voice command ""Remove" should remove the current focussed (looked at) object.
I tried instantiating the objects and adding the Intractable script. But I am stuck add adding OnfocusEnter and OnfocusExit events at runtime.
Hooking up the events on the prefab wont work as the references are in the scene.
I worked this out over on GitHub and posting it here so we can remove it from the other sources.
I didn't tackle the voice input yet as I am not there yet on my own MRTK project.
This submission should cover this answer for MRTK under version RC1. This was a quick job just to show proof of concept - feel free to modify and go on with it but I won't be :)
For run-time placement you would just need to add a method to instantiate an object that contains all of the information I setup in this example. There were some other solutions in the GitHub channel, I've copied the links below (not sure how long they will be active). This example is assuming you have some sort of already default prefab with the MRTK interactable class part of it.
Other discussions on GitHub from Microsoft: https://github.com/microsoft/MixedRealityToolkit-Unity/issues/4456
Example Video is here: https://www.youtube.com/watch?v=47OExTOOuyU&feature=youtu.be
Example Unity Package is here: https://github.com/JShull/MRTKExamples
Based on #jShull answer I came up with a simple solution for what I needed. Since there is no global listener for focus events I basically made my own.
I also added an earlier discussion (before I posted the question here) with two Microsoft Developers of the Mixed Reality Toolkit which could help out of you are looking for more functionality: https://github.com/microsoft/MixedRealityToolkit-Unity/issues/4456
"Object" script that is a component of the object that needs to be removed or interacted with.
using Microsoft.MixedReality.Toolkit.Input;
using UnityEngine;
public class Object: MonoBehaviour, IMixedRealityFocusHandler
{
public GameManager _gameManager;
public void OnFocusEnter(FocusEventData eventData)
{
Debug.Log("Focus ON: " + gameObject);
_gameManager.SetFocussedObject(gameObject);
}
public void OnFocusExit(FocusEventData eventData)
{
Debug.Log("Focus OFF: " + gameObject);
_gameManager.ResetFocussedObject();
}
}
"GameManager" script functions that sets the focussedObject
public void SetFocussedObject(GameObject object)
{
focussedObject = object;
}
public void ResetFocussedObject()
{
focussedObject = null;
}
Remove Object function is connect to the "Remove" global speech command in the "Speech Input Handler" component. It just removes the "focussedObject" inside the GameManager.
In my GameManager script I have a delegate. In my Door script, I subscribe to this delegate. I've tried unsubscribing from it in both the OnDisable and OnDestroy methods in the Door script. In both cases, I get an error when I stop running the game in the editor:
Some objects were not cleaned up when closing the scene. (Did you spawn new GameObjects from OnDestroy?)
Using Debug.Log, I found out that this is because the GameManager will always be destroyed before the Door script. Even if I do a null check inside either the OnDisable or OnDestroy of the Door script to see if the GameManager is null, I get the same error
if (GameManager.Instance)
{
GameManager.Instance.OnAllEnemiesKilled -= OpenDoor;
}
Somebody told me that I don't need to unsubscribe from it, as the delegate will automatically become null when the Door object is destroyed, but that's not true. During runtime, after the Door is destroyed, my update loop inside of the GameManager is still printing that the delegate has one subscriber: the Door.
I suspect you're spawning a new singleton when you call GameManager.Instance after it's been destroyed? If so, do something like this instead:
public class GameManager : MonoBehaviour
{
public static GameManager Instance
{
get
{
if (isDestroyed) return null;
// Your spawn logic...
}
}
static bool isDestroyed;
void OnDestroy()
{
isDestroyed = true;
}
}
As Lece pointed out, I'm spawning a new singleton when I call GameManager.Instance. Rather than creating a static bool to remedy the problem however, I replaced if (_instance == null) with if ((object)_instance == null) inside my Instance getter and it solved the problem. This is because, while the object is destroyed in the native code, it still exists in the managed code. So I'm now referencing it in the managed world. Moreover, Unity makes it so that when an object is destroyed in native code, its value in managed code will still return null.
Is there a way I can prevent the designer from attaching my script to anything except a terrain type?
Normally when I create a script/Component/MonoBehavior, Unity allows a designer to add it to any game object. How do I limit that?
Two methods come to mind, but none are very elegant.
1) Implement OnValidate(). The downside is that's it's called only when modifying component's values, or entering/exiting game mode.
void OnValidate() {
if (GetComponent<Terrain>() == null) {
Debug.LogError("You can't attach this component without terrain!");
DestroyImmediate(this);
}
}
2) Make the script run in the editor, and implement OnAwake(). But be aware that methods like Update() will be called in the editor.
[ExecuteInEditMode]
class MyScript: MonoBehaviour {
void OnAwake() {
if (GetComponent<Terrain>() == null) {
Debug.LogError("You can't attach this component without terrain!");
DestroyImmediate(this);
}
}
}