Search Unity

How to find image within the Unity Canvas in a C# script?

Discussion in 'Scripting' started by DirtyOldSkunk, Jul 22, 2021.

  1. DirtyOldSkunk

    DirtyOldSkunk

    Joined:
    Dec 19, 2012
    Posts:
    25
    Hello! I've been following along with a Unity tutorial which has been very fun so far. But I'm stuck on a point where I cannot progress further, and it seems I'm getting errors that the tutorial does not encounter.

    The error is simple. My player has a max health, and current health variable. I am making a health bar from a UI element (a red image scaled to look like a bar, that shrinks as the max health is divided by the current health.) We are creating a variable called uiHealthBar, to manipulate the UI element, and it is declared in the script as "uiHealthBar = GameObject.Find("HUD/Health/Bar").transform;" in the start function. For some reason, whenever I run the game it gives me a "NullReferenceException", and another "UnassignedReferenceException".

    Apparently the variable for uiHealthBar is not being assigned, even though I'm declaring it in the script, and the tutorial I'm following has no issues assigning it this way. Is this an outdated way of manipulating UI elements within the Unity canvas?
     
  2. RadRedPanda

    RadRedPanda

    Joined:
    May 9, 2018
    Posts:
    1,647
    Could you post the whole error, along with the code? The error usually mentions what line of code the error is occurring in, so you can narrow down the problem that way.

    My guess is that when you try and "Find" your GameObject, it fails to find something with that string name, and so when it tries to access its transform, it fails.
    Find()
    is very fragile, in that if the string names of stuff isn't perfect, it'll break. Not only that, it's extremely slow to lookup an object like that, so almost everybody steers away from it.

    It's better practice to create
    SerializedField
    s in your code, and directly drag the reference that way. If you need a more in depth explanation of that, just ask.
     
  3. DirtyOldSkunk

    DirtyOldSkunk

    Joined:
    Dec 19, 2012
    Posts:
    25
    Thanks for responding! I think that is exactly what the problem is, it seems like it is not able to find the GameObject in scene even though it is present. I believe at some point in the series he is demonstrating how to swap the UI out with another via script to represent shield/power-up/etc, and is opting to show us how to assign via script, so we have practice of doing so in code. I agree though, at a later time I'd probably prefer to manually drag the references into the editor on a SerializedField variable, but for the sake of following this tutorial I wanna make sure I can get this code working too.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Photon.Pun;
    5.  
    6. public class PlayerController : MonoBehaviourPunCallbacks
    7. {
    8.     #region Variables
    9.     public float speed;
    10.     public float sprintModifier;
    11.     public float jumpForce;
    12.  
    13.     public int maxHealth;
    14.     private int currentHealth;
    15.     private Transform uiHealthBar; //For some reason, Unity does not want to assign this variable on Start.
    16.  
    17.     public Camera povCam; //normalCam
    18.     public GameObject cameraParent;
    19.     public Transform weaponParent; //The weapoon transform on the player prefab
    20.     public Transform groundDetector;
    21.     public LayerMask ground;
    22.  
    23.     private Rigidbody playerRig;
    24.  
    25.     private Vector3 targetWeaponBobPosition;
    26.     private Vector3 weaponParentOrigin;
    27.  
    28.     private float movementCounter;
    29.     private float idleCounter;
    30.  
    31.     private float baseFOV;
    32.     [SerializeField] private float sprintModifierFOV = 1.5f;
    33.  
    34.     private Manager manager;
    35.  
    36.     #endregion
    37.  
    38.     #region MonoBehavior Callbacks
    39.  
    40.     private void Start()
    41.     {
    42.         manager = GameObject.Find("Multiplayer Manager").GetComponent<Manager>();
    43.         currentHealth = maxHealth;
    44.  
    45.         cameraParent.SetActive(photonView.IsMine);
    46.         if (!photonView.IsMine) gameObject.layer = 8;
    47.      
    48.         baseFOV = povCam.fieldOfView;
    49.  
    50.         if(Camera.main) Camera.main.enabled = false;
    51.  
    52.         playerRig = GetComponent<Rigidbody>();
    53.         weaponParentOrigin = weaponParent.localPosition;
    54.  
    55.         if (photonView.IsMine)
    56.         {
    57.             uiHealthBar = GameObject.Find("HUD/Health/Bar").transform; //Error Code, cannot find object
    58.             RefreshHealthBar();
    59.         }
    60.            
    61.     }
    62.  
    63.     private void Update()
    64.     {    
    65.         if (!photonView.IsMine) return; //Stops you from controlling the character, if it's not one your computer is supposed to have
    66.  
    67.         //Axis Input
    68.         float horizMove = Input.GetAxisRaw("Horizontal");
    69.         float vertMove = Input.GetAxisRaw("Vertical");
    70.  
    71.         //Controls
    72.         bool sprint = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
    73.         bool jump = Input.GetKeyDown(KeyCode.Space);
    74.  
    75.         //States
    76.         bool isGrounded = Physics.Raycast(groundDetector.position, Vector3.down, 0.1f);
    77.         bool isJumping = jump && isGrounded;
    78.         bool isSprinting = sprint && vertMove > 0 && !isJumping && isGrounded; //Can only spring if sprint button is held & moving forward & not jumping
    79.  
    80.         //Jumping
    81.         if (isJumping)
    82.         {
    83.             playerRig.AddForce(Vector3.up * jumpForce);
    84.         }
    85.  
    86.         //Self Damage TESTING KEY
    87.         if (Input.GetKeyDown(KeyCode.U))
    88.         {
    89.             Debug.Log("Self damage");
    90.             TakeDamage(100);
    91.         }
    92.  
    93.         //Head Bob
    94.         if (horizMove == 0 && vertMove == 0)
    95.         {
    96.             HeadBob(idleCounter, 0.025f, 0.025f);
    97.             idleCounter += Time.deltaTime;
    98.             weaponParent.localPosition = Vector3.Lerp(weaponParent.localPosition, targetWeaponBobPosition, Time.deltaTime * 2f);
    99.         }
    100.         else if (!isSprinting)
    101.         {
    102.             HeadBob(movementCounter, 0.035f, 0.035f);
    103.             movementCounter += Time.deltaTime * 3f;
    104.             weaponParent.localPosition = Vector3.Lerp(weaponParent.localPosition, targetWeaponBobPosition, Time.deltaTime * 6f);
    105.         }
    106.         else
    107.         {
    108.             HeadBob(movementCounter, 0.15f, 0.075f);
    109.             movementCounter += Time.deltaTime * 7f;
    110.             weaponParent.localPosition = Vector3.Lerp(weaponParent.localPosition, targetWeaponBobPosition, Time.deltaTime * 10f);
    111.         }
    112.     }
    113.  
    114.     void FixedUpdate()
    115.     {
    116.         if (!photonView.IsMine) return; //Stops you from controlling the character, if it's not one your computer is supposed to have
    117.  
    118.         //Axis Input
    119.         float horizMove = Input.GetAxisRaw("Horizontal");
    120.         float vertMove = Input.GetAxisRaw("Vertical");
    121.  
    122.  
    123.         //Controls
    124.         bool sprint = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
    125.         bool jump = Input.GetKeyDown(KeyCode.Space);
    126.  
    127.  
    128.         //States
    129.         bool isGrounded = Physics.Raycast(groundDetector.position, Vector3.down, 0.1f);
    130.         bool isJumping = jump && isGrounded;
    131.         bool isSprinting = sprint && vertMove > 0 && !isJumping && isGrounded; //Can only spring if sprint button is held & moving forward & not jumping
    132.  
    133.         //Movement
    134.         Vector3 moveDirection = new Vector3(horizMove, 0, vertMove);
    135.         moveDirection.Normalize();
    136.  
    137.         float t_adjustedSpeed = speed;
    138.         if (isSprinting)
    139.         {
    140.             t_adjustedSpeed *= sprintModifier;
    141.         }
    142.  
    143.         Vector3 targetVelocity = transform.TransformDirection(moveDirection) * t_adjustedSpeed * Time.deltaTime;
    144.         targetVelocity.y = playerRig.velocity.y;
    145.         playerRig.velocity = targetVelocity;
    146.  
    147.  
    148.         //Field of View
    149.         if (isSprinting) { povCam.fieldOfView = Mathf.Lerp(povCam.fieldOfView, baseFOV * sprintModifierFOV, Time.deltaTime * 8f); }
    150.         else { povCam.fieldOfView = Mathf.Lerp(povCam.fieldOfView, baseFOV, Time.deltaTime * 8f); }
    151.      
    152.     }
    153.     #endregion
    154.  
    155.  
    156.     #region Private Methods
    157.     void HeadBob(float p_z, float p_x_intensity, float p_y_intensity) //p_ means "paramater"
    158.     {
    159.         targetWeaponBobPosition = weaponParentOrigin + new Vector3(Mathf.Cos(p_z) * p_x_intensity, Mathf.Sin(p_z * 2) * p_y_intensity, 0);
    160.     }
    161.  
    162.     void RefreshHealthBar()
    163.     {
    164.         float t_health_ratio = (float)currentHealth / (float)maxHealth;
    165.         uiHealthBar.localScale = new Vector3(t_health_ratio, 1, 1); //ERROR CODE
    166.     }
    167.     #endregion
    168.  
    169.  
    170.     #region Public Methods
    171.     public void TakeDamage(int p_damage)
    172.     {
    173.         if(photonView.IsMine)
    174.         {
    175.             currentHealth -= p_damage;
    176.             RefreshHealthBar();
    177.  
    178.             Debug.Log(currentHealth);
    179.  
    180.             if(currentHealth <= 0)
    181.             {
    182.                 manager.Spawn();
    183.                 PhotonNetwork.Destroy(gameObject);
    184.                 Debug.Log("YOU DIED");
    185.             }
    186.         }
    187.      
    188.     }
    189.     #endregion
    190. }
    191.  
     
  4. DirtyOldSkunk

    DirtyOldSkunk

    Joined:
    Dec 19, 2012
    Posts:
    25
    I tried to add some comments to make it a little more clear what is happening in the code. The error is happening on Line 57- " uiHealthBar = GameObject.Find("HUD/Health/Bar").transform;". The Health Bar icon is represented as the "Bar" object. The "HUD/Health/Bar" are all empty game objects or UI elements, that are child objects to the Canvas. The whole error reads: "NullReferenceException: Object reference not set to an instance of an object"
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,735
    StarManta likes this.
  6. DirtyOldSkunk

    DirtyOldSkunk

    Joined:
    Dec 19, 2012
    Posts:
    25
    Thank you for linking that article. That was pretty informative, but I'm still having some issues figuring out how to move forward with this problem

    What makes GameObject.FindObjectWithTags() that much more desirable than "GameObejct.Find()"? Instead of reading through object names, now it will read through object tags. Doesn't it still have to search through all the objects of the game scene to whittle it down to what object has that tag, isn't it a pretty similar thing? Or is cutting the names out and only using tags actually that much more efficient? I'd have personally thought it would still be a similar level of calculation to have to read through all the object tags and select them in that fashion, but I'm pretty green to all this so I'm probably pretty wrong, too.

    I suppose I can try using GameObject.FindObjectWithTags() and tag the UI element I'm trying to edit? My only concern there is I was thinking of making specialized tags for all my HUD elements, the health bar I'm working on included, and if I tag them to find them, I'll never be able to "re-tag" them with the future labels I want :( That's the biggest issue with using tags, in terms of my concern- Once you've tagged an object, that is it's one and only tag, and if you had a better idea what to tag it with... well then you have to sacrifice the other tag to change it. This is one reason why I was really hoping GameObject.Find() would work. But I suppose at this point searching by tags might be my only bet.

    The other issue is manually assigning the variable- it cannot be done in this case. The player GameObject this script is attached to is instantiated into the scene on Start. It doesn't start in the same scene as the canvas in the inspector, it lives in my prefab folder and isn't in the scene on startup- so even if I make it a public variable in the code, Unity won't let me drag the reference onto the script physically. This is because it's not just sitting in the scene with the same object I'm trying to reference (at least I think that's why?). The game starts on a loading screen and then chooses a map (a different scene); then when it is loaded, the player is instantiated into that scene with the canvas and stuff I want to reference. So I don't think manually assigning is possible in this case, the UI element must be assigned via script, since it doesn't even start on the scene the canvas is loaded into. I've tried dragging the health bar UI on physically, it just denies it.

    I've also tried transform.Find() before posting this, I can say that was also just as much of a failure. That didn't want to work either :(

    Unfortunately I'm a little bit confused by the other ones, except for GetComponentsInChildren, but I'm not sure how that would help me when I'm trying to reference another object in the first place. I can't really get components of it's children when it can't find the child objects to begin with. I'll have to keep reading, but the other solutions either seemed to over my head at the current moment, or un-useful in the current context I need, or both. Though it was still an informative read, and I've bookmarked that so I can return to it.

    Even if GameObject.Find() is a particularly broken variable, I'm still confused as to why the tutorial I'm following is able to use it, but I'm unsuccessful. I've copied their project and script to the T, just to ensure I'm not getting any errors, and they're able to get away with it perfectly. After reading that article I see how GameObject.Find() can be slow and stubborn, but I find it weird in my case, because there are not many GameObjects in my project currently for it to have to sort through and read. Only calling it once on start, I have the path to the object clearly written up in the script, etc... Even if it is a terrible variable, I don't think there's any reason why it should be returning errors in this case. Like if it exists in Unity then it exists for a reason, and if other people are able to use it fine, then why am I getting errors when I literally copy them? As a newbie, I also wish I could understand why GameObject.Find() is breaking on me when I've been directly instructed to do it, so I see what's happening and why it's a nasty variable, rather than just being frustrated and confused.
     
  7. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,775
    It's not much better, but it's a little better. Unity maintains a Dictionary-like lookup table for objects that are tagged, so looking them up by the tag is fairly quick. It maintains that list because looking them up by tag is the only reason it'd be tagged, so it's worthwhile to keep track of them. But every object has a name, so a similar lookup table for names would not be worth the trouble; so for name lookups, it does need to crawl through every object.

    But it's really not even about speed. There are any number of things that can change a thing's name, including instantiating a copy, and any name change will cause GO.Find to break. Tags are more likely to stay the same.

    That fragility is probably why:
    Because there are so many tiny, tiny ways to break it, GO.Find is fragile, and that (more than performance) is why it should be avoided. It's not really worth finding out which of these tiny thing is causing your project not to work, because even if it's fixed there just isn't a scenario where .Find is the best option.

    Anyway, I don't think using tags is the best way here either, even if it's an improvement to Find. Your instincts here:
    .... are absolutely correct, and they do severely limit the usefulness of tags.

    Another thing you say which is correct:
    That's a yes. Unity won't let you assign references across scenes, because it can't know that they'll both be loaded.

    So, wat do? You need an object in the scene to reference a UI object in a different scene. The best way to handle that one is a combination of these two approaches:
    1) Create a UI controller script that is a singleton.
    2) On that script, have a manually-assigned reference to the UI element you want.

    Now, from anywhere, you'll be able to access UIManager.instance.thatImageObject. Super-fast, super-reliable.
     
  8. DirtyOldSkunk

    DirtyOldSkunk

    Joined:
    Dec 19, 2012
    Posts:
    25
    Thanks for the feedback! That all makes a lot more sense, thanks for clarifying some of my questions. I'll have to do a more research on singletons, the article gave some insight, but left me with a lot of questions. I'll have to do some research tonight on how people use them practically, I was pretty confused by some of those examples, and how they would relate to my project.

    I assume the UI controller script will exist in the same scene as my UI element? I'm just really unsure where the script would live, how I declare an instance of the GameObject as a singleton in the first place, and how I reference it in a different code and alter it's transform...

    I think I started it, kind of? I created a script called "HealthBarUI" placed on the UI health Bar. I made an instance of that script a variable, as well as a public transform for the UI Object that I dragged in via the inspector (called "uiHealthBarTransform" just for now). It allowed me to reference this script from my PlayerController script, but this is where it gets hairy. I'm not really sure how to reference the object still? In my PlayerController script, I tried to declare it by saying "uiHealthBar = HealthBarUI.instance.uiHealthBarTransform.transform", and it still fails to find the object and get it's transform. I'm just getting the same errors I got from the start- "NullReferenceException" and "UnassignedReferencfeExceptions", all pointing back and saying the uiHealthBar variable is unassigned.

    I'm probably incorrectly trying this singleton thing, that is a new concept to me. I'm going to keep watching videos and reading up about how it works. But for now I'm still confused and can't seem to get it recognizing, it hasn't created any new errors, so I don't think I'm totally butchering how the singleton is supposed to be used?
     
  9. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,735
    I don't like putting anything like that in any scene. Instead I make it appear the first time I touch it.

    Simple Singleton (UnitySingleton):

    Some super-simple Singleton examples to take and modify:

    Simple Unity3D Singleton (no predefined data):

    https://gist.github.com/kurtdekker/775bb97614047072f7004d6fb9ccce30

    Unity3D Singleton with a Prefab (or a ScriptableObject) used for predefined data:

    https://gist.github.com/kurtdekker/2f07be6f6a844cf82110fc42a774a625

    These are pure-code solutions, do not put anything into any scene, just access it via .Instance!

    If it is a GameManager, when the game is over, make a function in that singleton that Destroys itself so the next time you access it you get a fresh one, something like:

    Code (csharp):
    1. public void DestroyThyself()
    2. {
    3.    Destroy(gameObject);
    4.    Instance = null;    // because destroy doesn't happen until end of frame
    5. }
    For your use case you want the second one, which instantiates a prefab, in this case your UI.

    You can make it the entire canvas on up, or just part of it, but you MUST parent it to a canvas, which means you have to find the canvas, which means you're right back to the same place.

    THEREFORE... Make all your UI as a single prefab that has at its root a canvas, load it by the second example above.
     
  10. DirtyOldSkunk

    DirtyOldSkunk

    Joined:
    Dec 19, 2012
    Posts:
    25
    Hey thanks so much for sharing those examples! I appreciate everyone reaching out and sharing their support. Bare with me here, I'm still super green to coding, I've been on and off with it for about a year, and there are a lot of things I'm still learning...

    What exactly does .Instance do, as well as the variable class? I assume it just refers to an instance of data that exists somewhere in the game, correct? That seems like something I should get familiar with, in all the tutorials I've been studying from, I haven't come across this being stressed much yet. I'm just getting to the point where I'm sending info between scripts, so this is all a little shock and awe to try and take in on my own, haha. I'm confused how the scripts you sent me access the GameObject I need to reference, if it won't be attached to the UI, if it isn't being found and assigned in the script, and if it doesn't exist in the same scene (so I cannot physically assign via the inspector). I'm probably just being a newbie and misunderstanding tbh!

    Another problem that might be creating some confusion (at least on my part), is I think you believe I'm trying to spawn in the UI as the prefab, but I'm not at that point yet. I'm actually spawning the Player in as the prefab, and I have my Health Bar UI nested in the scene view under my canvas. My bad not clarifying. I plan on making the Health Bar into a prefab down the road, but for now I'm just trying to reference it as it exists in the scene, to make life easier and just figure out the player's scripts, first. So really I'm not even trying to find the health bar as a prefab, I'm still at the point where I'm trying to get the Health Bar recognized just sitting in the scene hierarchy.
     
  11. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,735
    In this context it is the static way to get at this thing.

    Note it is a getter, which triggers the blob of code that either loads and/or creates it "on demand." Until you touch Instance, it won't exist. _Instance is the "backing store" that privately stores the thing it made so it doesn't make a fresh one on the next access.

    I'd recommend either doing the above and letting it loads itself, so no scene and nobody needs to care about it.

    OR... if you insist on it being in a scene, put the UI in its own scene and load it additively. In that case you can also just use a lazy getter to find it.

    Additive scenes are super-powerful, but there's more timing issues that can come from additive scene loading. Prefabs load instantaneously. Scenes don't load until the end of scene (at the earliest... might be later).