Search Unity

Trouble Calling Method - What is up with my Raycast?

Discussion in 'Scripting' started by GrimeBrett, Aug 2, 2020.

  1. GrimeBrett

    GrimeBrett

    Joined:
    Jun 14, 2015
    Posts:
    7
    I'm a total hack when it comes to scripting in C#, but I'm slowly getting the hang of it. I'm at a complete loss for why I'm getting errors when calling a method on a gameobject directly, when it works perfectly when I call it via a raycast. I'll do my best to explain and show code examples below. I'd appreciate any help you're willing to give. Thanks!

    When I wrote the first iteration of my program, I used a raycast to detect if a gameobject was clicked, and then ran a script within that gameobject. It does this by passing the hit gameobject to the gamemanager, which executes it based on whatever gametype is currently being used. My code looks like this...
    Code (CSharp):
    1.     void Update()
    2.     {
    3.         // Is the mouse over a Unity UI element
    4.         if (EventSystem.current.IsPointerOverGameObject())
    5.         {
    6.             // If so, don't interact with the hexes
    7.             return;
    8.         }
    9.  
    10.         Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    11.  
    12.         RaycastHit hitInfo;
    13.  
    14.         if (Physics.Raycast(ray, out hitInfo) )
    15.         {
    16.             GameObject ourHitObject = hitInfo.collider.transform.parent.gameObject;
    17.  
    18.             selectedHex = ourHitObject;
    19.          
    20.             if (Input.GetMouseButtonDown(0))
    21.             {
    22.                 // Print hit hex name to the console  (When clicking center hex, returns "Hex10_10")
    23.                 Debug.Log("Raycast hit: " + hitInfo.collider.transform.parent.name);
    24.  
    25.                 // Informs the GameManager that a hex space has been clicked on
    26.                 gameManager.GetComponent<GameManager>().HexSelected(ourHitObject);
    27.             }
    28.  
    29.         }
    30.     }
    After the gameobject is passed along to the game manager, it simply executes the PlaceTile() function of that object. Easy as pie...
    Code (CSharp):
    1.     // Called from the GameManager (via the MouseManager) whenever a hex location on the board is selected
    2.     public void HexSelected(GameObject selected)    // Passes in the selected hex on the game board
    3.     {
    4.             selected.GetComponent<HexManager>().PlaceTile(HexAbrevText, HexValue);
    5.             selected.GetComponent<HexManager>().NotifySurroundingHexes(HexAbrevText);
    6.     }
    The above method works perfectly. However, for certain gametypes I need the AI to place tiles to set up the board at the beginning of the game. It seems to me that I should be able to simply write a script that calls the PlaceTile() function of whatever hex I choose. It works to a point, placing the tile on the board and setting certain values of said tile. However, as it runs through its other checks and calculations, it encounters an UnassignedReferenceException:

    UnassignedReferenceException: The variable upperRightHex of HexManager has not been assigned.

    This variable is one of the center hex's neighboring hex pieces, and Unity claims it isn't assigned, but it is. I wrote a script that finds and stores it at startup, and I can clearly see it assigned in the editor while the game is running.

    So my question is, what is different about the gameobject that the raycast returns, as opposed to directly calling the gameobject in script? Why does the first method work perfectly, and the second method has issues?

    Thanks in advance!
    Brett
     
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,909
    Absolutely nothing, assuming it's the same object.

    Correct me if I'm wrong but HexManager is a component on every hex right? And there are many hexes, in some kind of hex grid?

    What about the hexes on the right edge of the grid? Wouldn't they be lacking an "upper right" neighbor?
     
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,735
    Everything I see you doing above is 100% fine... my thought is perhaps you have an order of initialization problem, whereby the call that is failing is happening before the GameManager / HexManager is up for business.

    I recommend putting in more Debug.Log() to ensure the thing reported as null actually gets initialized before other components start meddling with it. If your problem is timing, here is some timing diagram help for MonoBehaviors:

    https://docs.unity3d.com/Manual/ExecutionOrder.html
     
  4. GrimeBrett

    GrimeBrett

    Joined:
    Jun 14, 2015
    Posts:
    7
    Thanks for the reply, Praetor. You're right that this is a hex grid, and the same error happens when I reach the edge of the grid. I will eventually write a script that adds additional hexes if the player ever reaches the edge of the pre-built grid, but I have gotten to it yet. I'll keep working. Thank you!
     
  5. GrimeBrett

    GrimeBrett

    Joined:
    Jun 14, 2015
    Posts:
    7
    THANK YOU, KURT! The problem was, indeed, timing. As soon as I put in some sort of delay on the BuildLayout() method, everything works fine. Currently, I have a simple coroutine that pauses for 2 seconds before it executes the BuildLayout() method, but that seems like a clunky solution. I'm sure there's a way to write the method so that it informs the game manager when the pre-build is done, and then it executes the BuildLayout() method, but I don't know how to do that.

    Since I'm self-taught, I typically just head to YouTube and the forums for guidance, but in this case, I don't even know what that sort of method would be called. I'd be very thankful if anyone could simply point me in the right direction.

    Thanks again for your help, Kurt. I owe you a beer!
     
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,735
    There's a bunch of ways... waiting 2 seconds is operational but as you noted, feels a bit clunky.

    Here's how I do it for most of my games, where I need explicit sequencing. For instance, I like to load the content scene separate from loading the player scene (loaded additively) and perhaps have multiple content scenes, or an enemy manager scene, or even an introductory movie overlay scene:

    - I have a custom MonoBehavior called "PlayerSpawn"
    - it contains nothing, just a class definition
    - it does NOT exist in any scenes
    - when the content levels are done doing their thing, creating or loading or connecting, their final act is to great a GameObject and add an instance of my PlayerSpawn script
    - meanwhile over in the player scene it just sits in a coroutine doing FindObjectOfType PlayerSpawn each frame until it finds it, then it puts the player there and moves on with business

    This sort of pattern lets me stack as much introductory stuff (movies, camera zoom-ins, etc.) in front of the creation of that PlayerSpawn script, and the player won't appear until it does.

    Think of it as specialized markers that one script is bottlenecked and waiting for, and another script puts in when it wants the other script to continue. With good naming of these scripts it can keep your intentions very clear going into the future.