Best practice to to access components using scripts in Unity? - unity3d

I am new to Unity so go easy on me. :)
I added an game object with a text field component (via TextMeshProUGUI) to my heads up display in my scene. I want to use this to display various statistics on it for debugging purposes during game play.
I then created a script which I added as a component to the same game object that holds my text component. Is this the best practice? Not sure how else I would get the script to execute.
Once I had my script created, I needed to find the text component as well as some other components in my scene so I could display the debug information. Below you can see how I did it... it feels a little dirty to be searching the entire scene to find these things. Would love some insight on how long-time Unity programmers go about this!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class PlayerDebugStatistics:MonoBehaviour {
TextMeshProUGUI playerDebugStatisticsText;
PlayerCharacterController playerCharacterController;
Health playerHealth;
private void Start() {
// First find the object in the scene named "PlayerDebugStatisticsText" and then get it's TextMeshProUGUI component
this.playerDebugStatisticsText = GameObject.Find("PlayerDebugStatisticsText").GetComponent<TextMeshProUGUI>();
// Get the player character controller
this.playerCharacterController = GameObject.FindObjectOfType<PlayerCharacterController>();
// Get the player health from the player character controller
this.playerHealth = playerCharacterController.GetComponent<Health>();
}
void Update() {
// Update the text every frame
this.playerDebugStatisticsText.SetText(string.Format("{0:N2}", this.playerHealth.currentHealth));
}
}

3 ways
Create an inspector reference to the other object and access it from your script - a public or private (with [SerializeField]) attribute in your script - and drag the component in like in this video: https://youtu.be/dMgQOP7kdxg?t=425
Use the singleton pattern: https://www.youtube.com/watch?v=5p2JlI7PV1w
Use dependency injection - https://github.com/modesttree/Zenject
Your way isn't terrible if you don't do it in the Update() loop, and cache your objects (like it appears you are doing), but if you need that performance in Start(), the above options can help.

Related

How to disable the Tracked Pose Driver in a Unity VR Application?

I need to write a VR application that disables HMD positional tracking for a specific situation and then reenable it again.
In the UI it is a simple as ticking and unticking the TrackedPoseDriver shown in the picture below.
How can I do that via Scripting?
I assume I need to use the enabled property of a game object. But I don't know how to grab a hold of this GameObject (or Component).
EDIT: In case this was clear this is GameObject/Component associated to the main camera.
Ok. So I was able to do It.
What I did is I search MonoBehaviour Based Components on the camera and then cast it to a MonoBehaviour once I found the right one. And then used the enabled property. Like this:
Component[] components = Camera.main.GetComponents(typeof(MonoBehaviour));
bool found = false;
foreach(Component component in components) {
string name = component.ToString();
MonoBehaviour mb = (MonoBehaviour) component;
if (name.Contains(TRACK_POSE_DRIVER_NAME)){
mbTrackingPose = mb;
found = true;
break;
}
}
The unique string that the toString() method provided was somethign like (I don't have it on hand) "Main Camera (Unity.XR.TrackedPoseDriver)". So I just made sure that the string had the "TrackedPoseDriver" and stored that as a MonoBehaviour.
I hope this helps someone else.

Unity script stops working when scene is reloaded

I am trying to create an infinite runner.
My scene have a "road creator" object that is just a trigger and a script attached to my road prefab.
Every time a piece of road leaves the road creator trigger, a new piece of road is created.
Here's the script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class roadGenerator : MonoBehaviour
{
public GameObject road;
private void OnTriggerExit(Collider other) {
if(other.gameObject.tag == "roadGenerator"){
Instantiate(road, new Vector3(200, 0, 0), Quaternion.identity);
}
}
}
My issue is that once the player loses and the level reloads the script stops working.
Everything gets loaded properly (the trigger object and the first piece of road with the script attached) but for some reason the script doesn't get triggered...
Hey I think I'll need some more context to understand your problem:
You have a "roadGenerator" with a trigger and the Script attached + a "Road" Prefab with the Tag "roadGenerator" right?
I suppose by "realoading the level" you mean reloading the Scene with something like SceneManager.LoadScene(...)
It would also be good to know what you already tried out to fix it. :)

Unity3D Text not changing after being set in Start()

I have a Canvas (World Space Render mode) with a Text and a Button component displayed in a tridimensional space (it's a VR app). The canvas instantiated at runtime using a prefab.
I get a reference to the Text object using:
_codeTextLabel = canvasPrefab.transform.Find("CodeTextLabel").gameObject.GetComponent<Text>();
I want to update the text at run-time using:
void Update()
{
_codeTextLabel.text = _codeText;
}
where _codeText is just a variable I update based on specific events.
The problem is that the Text gets updated only the first time, but if I try to change the variable nothing happens. I have tried several combinations and also the method _codeTextLabel.SetAllDirty() but it doesn't work.
The only way to update the text is to re-instantiate the prefab.
Are you instantiating your prefab before setting the values. If you are storing the _codeTextLabel reference before instantiating then your reference will point to the prefab not the runtime object. I can't see the rest of your code, so I can't say for sure. (I would have asked as a comment, but as I'm new I don't have the reputation to do so)
edit: I did a test to try and recreate your problem. I made the following script and it appears to work as expected. CanvasPrefab is a worldspace canvas with a UnityEngine.UI.Text component attached. (The script is attached on an empty game object in the scene btw)
public class ChangeText : MonoBehaviour
{
public GameObject CanvasPrefab;
private GameObject runtimeCanvas;
public string runtimeText = "something";
private Text textRef;
// Start is called before the first frame update
void Start()
{
runtimeCanvas = GameObject.Instantiate(CanvasPrefab);
textRef = runtimeCanvas.GetComponentInChildren<Text>();
}
// Update is called once per frame
void Update()
{
textRef.text = runtimeText;
}
}
as long as you did something wrong, It works absolutely so I guess there are several cases
Failed to do "_codeTextLabel = canvasPrefab.transform.Find("CodeTextLabel").gameObject.GetComponent();"
'_codeTextLabel' lost reference from 'GameObject.
Doesn't change runtimeText' change at all
Subscription of events failed I mean, your updating scripts doesn't get proper event to update that text.
Without codes, this is only thing I can guess for yours so please check above I hope there is case among above.

Can I programatically load scenes in the Unity editor?

I'm using the A* pathfinding algorithm for my 2D game (from my understanding, Unity Nav Meshes don't work in 2D). I would like to be able to pre-calculate navigation grids for all of my scenes, and save them in resource files that can be loaded whenever the player enters a new scene. Rather than having to remember to click "calculate" for every scene -- and remember to recalculate all of my scenes if I make a change to my navigation grids -- I want to be able to programatically have the Unity Editor iterate though each scene and calculate the grids.
Is there a way to create a command in the Unity editor that will iteratively open each scene in the editor and run a method on a MonoBehaviour that's in the scene? Alternatively, is there another way to accomplish what I'm trying to do?
Yes you can!
In editmode you can't use SceneManager but have to use the EditorSceneManager.
First of all you need the scenes you want to iterate.
Could be e.g. a public static field with a list of SceneAsset in the Inspector where you simply reference the scenes
public static List<SceneAsset> Scenes = new List<SceneAsset>();
or you could get them by script e.g. for only use the scenes added to the build settings using EditorBuildSettings.scenes
List<EditorBuildSettingsScene> Scenes = EditorBuildSettings.scenes;
For both you can get a list of the scene paths e.g. using LinQ Select (this is basically a kind of shortcut for a foreach loop) and AssetDatabase.GetAssetPath like
List<string> scenePaths = Scenes.Select(scene => AssetDatabase.GetAssetPath(scene)).ToList();
for the EditorBuildSettingsScene from EditorBuildSettings.scenes you can also simply use
List<string> scenePaths = Scenes.Select(scene => scene.path).ToList();
Now you can iterate over them all and do your stuff by using EditorSceneManager.OpenScene, EditorSceneManager.SaveScene and EditorSceneManager.CloseScene (and if you need it AssetDatabase.SaveAssets)
foreach(string scenePath in scenePaths)
{
// Open a scene e.g. in single mode
var currentScene = EditorSceneManager.OpenScene(scenePath);
/* Do your calculation for currentScene */
// Don't know if it makes changes to your scenes .. if not you can probably skip this
EditorSceneManager.SaveScene(currentScene);
// Finally Close and remove the scene
EditorSceneManager.CloseScene(currentScene, true);
}
// you might be doing changes to an asset you want to save when done
AssetDatabase.SaveAssets();
Before starting you should probably ask to save the current open scene(s) using EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo
if(EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
{
// Saved => the foreach loop here
}
else
{
// aborted => do nothing
}
Than in order to finally start that method the simplest would be to add a [MenuItem]
public static class Calculation
{
[MenuItem("YourMenu/RunCalculation")]
public static void RunCalculation()
{
// all the before mentioned snippets here
// depending how exactly you want to do it
}
}
This will add a new menu YourMenu with one entry RunCalculation to the top menubar of the Unity Editor.
Note:
Since this uses a lot of types (EditorSceneManager etc) that only exist in the UnityEditor namespace you should either place the whole script in an Editor folder (so it is ignored in the final build) or use pre-processors like
#if UNITY_EDITOR
// ... code here
#endif
so in the build the code is also ignored.
Note that I'm assuming so far you also did your calculation in editmode. The thing is if this calculation relies somewhere aon any Start or Awake method you have to call it manually from that editorscript before running the calculation.

SceneManager.LoadScene(string) doesn't recognize scene name

I've seen similar questions all over the internet, but none seem to apply to my situation.
I have a very simple project with two scenes: MainMenuScene and OtherScene. OtherScene is nothing but a blank canvas, and MainMenuScene just has a button which is meant to be used to navigate to OtherScene. The Main Camera has the following script attached to it:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class LaunchScene : MonoBehaviour {
public void LaunchSceneOnButtonClick(int index)
{
SceneManager.LoadScene(index);
}
public void LaunchSceneOnButtonClick(string sceneName)
{
SceneManager.LoadScene(sceneName);
}
}
and in the OnClick() of the button, this script is called. I have made sure to add both levels to the Build Settings.
If I call the version of the LanchSceneOnButtonClick which takes an integer argument (and specify the appropriate value in the OnClick() method) then the desired scene loads.
However, if I call the version which takes a string argument and use the exact name of the scene I want to load (and I've quadruple checked that it is indeed the exact string, that the string name has no spaces or hidden characters, and have copy pasted the scene name into the OnClick() argument field) the scene does not load and instead I get the error message
Scene "OtherScene" couldn't be loaded because it has not been added to
the build settings or the AssetBundle has not been loaded.
Since using the scene index works, we know it was added to the Build Settings properly, so I reason that there is some issue with the scene name. But I've copied it exactly. I want to avoid using the full path of the scene in case I later decide to move scenes around to different folders, so what are my options here?
Thanks in advance.
EDIT:
Adding screenshots.
Scene in folder structure:
Scenes in build settings:
OnClick() input:
Keep in mind I've also tried the input to OnClick() with and without quotation marks.
The only possible problems are these:
The string passed is different from the scene name; bear in mind, the string used in LoadScene is not case sensitive, i.e. you can use OtherScene, OTHERscene, etc.
The scene in the build settings is not active (i.e. with the checkmark on the left disabled).
The scene isn't present at all in the build settings.
The full path of the scene is irrelevant btw, you can have OtherScene inside any dir/subdir in the Assets, and you'll need only the name of the scene. You need the full path of the scene only if you have more than scene with the same name in different paths (and anyway you shouldn't, it's such an obvious terrible practice).
EDIT: You say that you tried even without " (as you should do, when entering string fields in the inspector you don't need them), what happens if you modify the method body to:
public void LaunchSceneOnButtonClick(string sceneName)
{
SceneManager.LoadScene("OtherScene");
}
Try this and report back what happens please.
I had also the problem that
public void loadScene(string scene)
{
SceneManager.LoadScene(scene);
}
did not work for me in the first place. What did I do? I'm saving the names of my scenes in a List (read from a file) and from that List I choose the scene I need.
But somehow the reading progress seems to give me some unwanted characters so the scene loading does not work as intended. When I added .Trim() it started to work.
For a better understanding, here's an example:
string s = sceneList[1].Trim();
this.loadScene(s);
public void loadScene(string scene)
{
SceneManager.LoadScene(scene);
}
So make sure that you get rid of unwanted characters.