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);
}
}
}
Related
I know I can inherit from MonoBehaviour and add some handler functions to SceneManager.sceneLoaded, as described in official document https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager-sceneLoaded.html.
My question is, do I need to create an empty GameObject in the scene, and attach a script to that GameObject to do the stuff? Or there is some other better practice?
This is good practice. Create a SceneManager empty GameObject. Attach a script inheriting from MonoBehaviour and handle all scene related actions in there.
Very lucid, straight to the point.
It is one way to go, yes.
You do not necessarily require a GameObject for this.
You can also e.g. use [RuntimeInitializeOnLoadMethod] and attach the listener within there and then further process it.
public static class SomeClass
{
[RuntimeInitializeOnLoadMethod]
private static void Init()
{
SceneManager.sceneLoaded += OnSceneLoaded;
}
private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
Debug.Log("OnSceneLoaded: " + scene.name);
Debug.Log(mode);
}
}
What is "better" depends a lot on your use case and requirements of course
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
I'm implementing a system in which a player can pickup an item from the game world. After picking the item up, I want to destroy the game object. To do this, I figured I'd make a ServerRPC that deletes the object, but the ServerRPC is not getting called. I tried making a ClientRPC as well, but that one isn't being called either. I noticed that it only gets called when the host of the game tries to pick up the item -- any ideas on how to fix this? The documentation is pretty bare but please let me know if I missed anything. Here is my code:
public override void Interact(GameObject player)
{
player.GetComponent<PlayerInventory>().AddItem(this.item);
Debug.Log("before");
TestClientRpc();
TestServerRpc();
}
[ServerRpc]
public void TestServerRpc()
{
Debug.Log("server");
// Destroy(gameObject);
}
[ClientRpc]
public void TestClientRpc()
{
Debug.Log("client");
// Destroy(gameObject);
}
EDIT: image of the components on the weapon
EDIT: This class extends a class that extends the networkbehaviour class. The objects are just sitting in the scene at start -- they are not spawned/instantiated (not sure if that matters or not).
The most plausible solution to this problem , either it is running into a error in some previous lines or as some other person pointed out , requireOwnership is not set to false .
For in depth analysis of MLAPI , https://youtube.com/channel/UCBStHvqSDEF751f0CWd3-Pg
I found the tutorial series on this channel best
I needed to add the following attribute to my ServerRpc:
[ServerRpc(RequireOwnership = false)]
since the player was not the owner of the item.
I have the same issue. Been looking at it for 2 days but I am inclined to think it is a bug to be honest.
I just spent 4+ hours banging my head against the wall because they didn't write this important info in the documentation. When calling ServerRpc from a client that doesn't own the Network Object, RPC attribute should be set to RequireOwnership = false.
For me the issue was that the GameObject(with NetwrokObject component) was sitting in the scene before starting the game, I couldn't use NetworkVariables nor ServerRpc. I think those are only usable on spawned object, but not sure. So I would say : either instantiate the object and add it to the NetworkManager or use normal variable on those objects.
Had the same issue but later realized I just forgot to inherit NetworkBehaviour
For Example:
using Unity.Netcode;
using UnityEngine;
class myObject : NetworkBehaviour {
public override void Interact(GameObject player)
{
player.GetComponent<PlayerInventory>().AddItem(this.item);
Debug.Log("before");
TestClientRpc();
TestServerRpc();
}
[ServerRpc]
public void TestServerRpc()
{
Debug.Log("server");
//Destroy(gameObject);
}
[ClientRpc]
public void TestClientRpc()
{
Debug.Log("client");
// Destroy(gameObject);
}
}
I had the same problem, where the ServerRpc wouldn't even be called. For me it worked after I made the GameObject that my script was attached to a prefab and added it to the NetworkManager's list of NetworkPrefabs.
Note: (I am using Netcode for GameObjects, I don't think it will be the same for other Multiplayer solutions)
Working on getting familiar with C# and unity development. Today I am working on getting a reference to a Text UI object in my script. The following code below produces this error:
NullReferenceException: Object reference not set to an instance of an object
handle.Awake () (at Assets/handle.cs:20)
Script looks like this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using playPORTAL.Profile;
using UnityEngine.UI;
public class handle : MonoBehaviour
{
public Text myText;
// Start is called before the first frame update
void Start()
{
}
void Awake()
{
myText.text = "#organickoala718" ;
}
// Update is called once per frame
void Update()
{
}
}
What needs improving to correctly get a reference to the Text UI element?
In general: The same way as with any other Component.
Either reference it via the Inspector or use GetComponent(Tutorial) or one of its variations.
So if that Text Component is attached to the same GameObject as your script then you could use GetComponent(API) to get the reference on runtime
private void Awake ()
{
if(!myText) myText = GetComponent<Text>();
myText.text = "#organickoala718" ;
}
Also checkout Controlling GameObjects with Components
Btw you should remove the empty methods Start and Update entirely. They are called by the Unity Engine as messages if they exist so the don't need to exist and only cause unnecessary overhead.
You need to either set the myText value of your handle instance from another script, or set it in the Unity Editor's Inspector window, when you've selected a GameObject that has your handle component added.
You need to drag the object you are referencing to from the Unity editor, in the scene, to the script itself.
First attach the script you made to a GameObject in the Unity Scene.
Then drag the "text" component to the Script you recently attached to the GameObject
That would solve the problem you have.
Another approach would be to declare a
public GameObject UITextElement;
Instead of a public text as you did.
Do the same as i wrote before and in the script write:
UITextElement.GetComponent().text = "Write your text here!";
I know this has been covered in the past, but I believe this issue is being caused by the update to Unity 5, thus deprecating the older information. No errors come up, but the debug won't come up. The public variable of 'pushBlockEnter' does become true visibly on the player script, but the pushBlock class will not recognize it no matter what. I just want to be able to check if a variable is true in one script from another script in Unity 5, any way of doing it would be fine. I'm sure it is something simple but I just can't figure it out...I should also mention that both scripts are on a different object. Thanks in advance!:
public class pushBlock : MonoBehaviour {
public Player playerScript;
void Update () {
if (GameObject.Find("Player").GetComponent<Player>().pushBlockEnter)
{
Debug.Log ("do something blahh");
//Do Anything
}
}
}
Figured it out:
GameObject playerReference = GameObject.Find("Player");
void Update(){
if (GameObject.Find("pushBlockCollider").GetComponent<pushBlockCollider>().inPushBlock){
Debug.Log ("got to pushblockCollider True");
}