Search Unity

NullReference Exception on Build, works fine in editor

Discussion in 'Scripting' started by DevM22, Sep 4, 2019.

  1. DevM22

    DevM22

    Joined:
    Aug 10, 2019
    Posts:
    14
    Hello, first time posting here and fairly new to unity so not sure how to solve this.

    I'm building a FireEmblem/Advanced wars type game, so when selecting a unit this type of behavior is what should be happening upload_2019-9-4_16-24-57.png , behind the scenes there is a MapManager script that has a function that gathers all the tilemap information (this is important since the tiles contain their associated cost) and a pathfinding script that takes that information to create the edges of a graph. The tiles themselves are custom tiles so they have a scriptableObject associated with them which has the adjustable cost of walking over them.

    Thing is that in the editor it all runs smoothly but when building the project and running the game it doesn't create the highlight area shown above since it gives an error on the pathfinding script, it gives an error while checking one of the tiles (NullReferenceException) to create the edge with the weight, I have checked the build logs and this is the error upload_2019-9-4_16-29-31.png , while in the editor that same spot detects the tile name just fine ( so its not null there but it is in the build) upload_2019-9-4_16-30-15.png , I have tried to basically change the way the pathfinding script grabs the matrix with the tile info so its not a reference to an object since I read garbage collector and references could have something to do with it but no luck.

    To avoid Pasting all the code of the script ill paste the relevant parts:

    Code where it gives the error, it throws the error when trying to access mapArea at the position 1,6 so meaning its null there for some reason ( in the editor it isnt).
    Code (CSharp):
    1. private void addEdges(int x, int y,string type)
    2.     {
    3.         int currentTileNumber = ((x * sizeX + 1)) + y;
    4.         Debug.Log(gameObject.GetComponent<UnitInterface>());
    5.         //top, right , down,left is the order
    6.         if (y + 1 < sizeY)
    7.         {
    8.             if (unitMap[x, y + 1] != null)
    9.                 {
    10.                 if (mapArea[x, y + 1].isWalkable && unitMap[x, y + 1].team == gameObject.GetComponent<UnitInterface>().team)
    11.                 {
    12.                     graph.edge[addingEdges].src = currentTileNumber;
    13.                     graph.edge[addingEdges].dest = currentTileNumber + 1;
    14.                     graph.edge[addingEdges].weight = mapArea[x, y + 1].getTileCost(type);
    15.                     addingEdges++;
    16.  
    17.  
    18.                 }
    19.             }
    20.             else
    21.             {
    22.                
    23.                 if (mapArea[x, y + 1].isWalkable)
    24.                 {
    25.                     graph.edge[addingEdges].src = currentTileNumber;
    26.                     graph.edge[addingEdges].dest = currentTileNumber + 1;
    27.                     graph.edge[addingEdges].weight = mapArea[x, y + 1].getTileCost(type);
    28.                     addingEdges++;
    29.  
    30.  
    31.                 }
    32.             }
    33.                
    34.            
    35.         }
    36.         if (x + 1 < sizeX)
    37.         {
    38.             if (unitMap[x + 1, y] != null)
    39.                 {
    40.                 if (mapArea[x + 1, y].isWalkable && unitMap[x + 1, y].team == gameObject.GetComponent<UnitInterface>().team)
    41.                     {
    42.                         graph.edge[addingEdges].src = currentTileNumber;
    43.                         graph.edge[addingEdges].dest = currentTileNumber + sizeY;
    44.                         graph.edge[addingEdges].weight = mapArea[x + 1, y].getTileCost(type);
    45.                         addingEdges++;
    46.  
    47.                     }
    48.                 }
    49.                 else
    50.                {
    51.                 Debug.Log(mapArea[x + 1, y] + " " + x + " " + y);
    52.                 if (mapArea[x + 1, y].isWalkable)
    53.                     {
    54.                         graph.edge[addingEdges].src = currentTileNumber;
    55.                         graph.edge[addingEdges].dest = currentTileNumber + sizeY;
    56.                         graph.edge[addingEdges].weight = mapArea[x + 1, y].getTileCost(type);
    57.                         addingEdges++;
    58.  
    59.                     }
    60.                 }
    61.  
    62.            
    63.         }
    64.         if (y - 1 >= 0)
    65.         {
    66.             if (unitMap[x, y - 1] != null)
    67.             {
    68.                 if (mapArea[x, y - 1].isWalkable && unitMap[x, y - 1].team == gameObject.GetComponent<UnitInterface>().team)
    69.                 {
    70.                     graph.edge[addingEdges].src = currentTileNumber;
    71.                     graph.edge[addingEdges].dest = currentTileNumber - 1;
    72.                     graph.edge[addingEdges].weight = mapArea[x, y - 1].getTileCost(type);
    73.                     addingEdges++;
    74.  
    75.                 }
    76.             }
    77.             else
    78.             {
    79.                 if (mapArea[x, y - 1].isWalkable)
    80.                 {
    81.                     graph.edge[addingEdges].src = currentTileNumber;
    82.                     graph.edge[addingEdges].dest = currentTileNumber - 1;
    83.                     graph.edge[addingEdges].weight = mapArea[x, y - 1].getTileCost(type);
    84.                     addingEdges++;
    85.  
    86.                 }
    87.             }
    88.          
    89.         }
    90.         if (x - 1 >= 0)
    91.         {
    92.             if (unitMap[x - 1, y] != null)
    93.             {
    94.                 if (mapArea[x - 1, y].isWalkable && unitMap[x - 1, y].team == gameObject.GetComponent<UnitInterface>().team)
    95.                 {
    96.                     graph.edge[addingEdges].src = currentTileNumber;
    97.                     graph.edge[addingEdges].dest = currentTileNumber - sizeY;
    98.                     graph.edge[addingEdges].weight = mapArea[x - 1, y].getTileCost(type);
    99.                     addingEdges++;
    100.  
    101.                 }
    102.             }
    103.             else
    104.             {
    105.                 if (mapArea[x - 1, y].isWalkable )
    106.                 {
    107.                     graph.edge[addingEdges].src = currentTileNumber;
    108.                     graph.edge[addingEdges].dest = currentTileNumber - sizeY;
    109.                     graph.edge[addingEdges].weight = mapArea[x - 1, y].getTileCost(type);
    110.                     addingEdges++;
    111.  
    112.                 }
    113.             }
    114.         }
    115.     }
    Code relevant to getting the mapArea, its a bit altered from what I had originally but no dice, I create the matrix with the right size before hand and call a function called copyMatrix ( which I also created to try and solve the problem) that takes the Matrix from the pathfinding script through reference and matches it with the MapManager Matrix (instead of what I had before of mapArea = map.GetCustomTilesBlock(map.GetBoundries()) that just references the mapArea on MapManager;
    Code (CSharp):
    1.  mapArea = new BaseCustomTile[map.GetBoundries().size.x, map.GetBoundries().size.y];
    2.         map.copyMatrix(ref mapArea, map.GetCustomTilesBlock(map.GetBoundries()));
    Finally CopyMatrix
    Code (CSharp):
    1.  public void copyMatrix(ref BaseCustomTile[,] Matrix, BaseCustomTile[,] MatrixToCopy)
    2.     {
    3.         for (int i = 0; i < unitMap.GetLength(0); i++)
    4.         {
    5.             for (int j = 0; j < unitMap.GetLength(1); j++)
    6.             {
    7.                 Matrix[i, j] = MatrixToCopy[i, j];
    8.             }
    9.         }
    10.     }
    And the function that gathers the tiles given a bound
    Code (CSharp):
    1.     public BaseCustomTile[,] GetCustomTilesBlock(BoundsInt bounds)
    2.     {
    3.         BaseCustomTile[,] matrix = new BaseCustomTile[bounds.size.x,bounds.size.y];
    4.         for (int x = bounds.xMin + Mathf.Abs(bounds.xMin); x < bounds.xMax + Mathf.Abs(bounds.xMin); x++)
    5.         {
    6.             for (int y = bounds.yMin + Mathf.Abs(bounds.yMin); y < bounds.yMax +             Mathf.Abs(bounds.yMin);y++)  
    7.             {
    8.                 BaseCustomTile tile = (BaseCustomTile)tilemap.GetTile(new Vector3Int(x-1, y-1, 0));
    9.                 matrix[x,y] = tile;
    10.                
    11.             }
    12.         }
    13.         return matrix;
    14.     }
    Maybe its something really dumb Im missing but doesn't make any sense to me that works in one place and it doesn't in the other.
     
  2. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,187
    Without knowing much of how all things are setup, my general tip when something is null in a build, but not in the editor is an order of operation errors. After that, there can be platform specific stuff, but based on what you're saying, wouldn't surprise me if you have stuff just being done in a different order then you expect.

    You need to make sure that scripts are executing in the proper order. Is your array populated before you try to access values? Are the parts of the array setup before you try to access them?

    Remember that if you have several scripts with Awake, there is no way of knowing their order. Same with Start. Unless you setup the script execution order part.
     
  3. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    Another poster recently had a problem that occurred only in builds and not in the editor, and it turned out that they were using Unity cloud builds, and the Unity version for the cloud builds didn't match what they were running locally.
     
  4. DevM22

    DevM22

    Joined:
    Aug 10, 2019
    Posts:
    14

    I have the script execution order set up and it seems that it is only on that specific tile for some reason, the others are populated and give no error up until then.

    Im also not using unity cloud builds :l.
     
  5. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    I notice that your "copyMatrix" function is relying on the length of unitMap instead of the matrices being copied.

    This is a very open-ended problem. There could be some kind of mistake in initializing your data, or in how you modify your data after it's initialized, or in the logic of what indices you access. It's likely you'll just need to start checking things with Debug.Log calls or by stepping through your code in a debugger until you can narrow down the source of the problem.
     
  6. DevM22

    DevM22

    Joined:
    Aug 10, 2019
    Posts:
    14

    Found the problem :p, guess it didn't have to do with that.

    For anyone having the same problem I had one of the scriptable objects with no c# script attached, not sure how that happened since it was only that specific tile type. Weird how it ran fine in the editor.
     
  7. aethergemstudio

    aethergemstudio

    Joined:
    Apr 25, 2021
    Posts:
    2
    I'm taking this post out of the graveyard to add my experience, because I had the same problem as the OP, but in a greater scale, and had no luck finding an answer in the forums. My project was running just fine in the editor, but my build was throwing Null Reference Exception Errors all over the place for no aparent reason. I checked the execution order or my scripts, checked that everything was in place in the correct time. It took me 3 long days to find the solution: Turns out the problem was the way I handled my references.

    I made the terrible mistake of referencing scripts by calling GetComponent() to their singleton instances. Something like: GameManager.Instance.GetComponent<blablabla>().somevariable; All Null Reference Exceptions went away once I replaced those. I didn't remove the singletons btw, I just no longer reference using them with GetComponent(), and have them just to avoid duplicated instances.

    I just hope this can help someone out there.
     
  8. dnorambu

    dnorambu

    Joined:
    Sep 14, 2021
    Posts:
    11
    Hi aethergemstudio, how did you end up knowing that references to singleton instances where the problem? I think a have the same issue but i really want to know why is this causing trouble, thnx
     
  9. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
    Thanks for coming back to mention this. This isn't surprising since there are so many broken and subtly deficient singleton implementations floating around the wiki space and example code, it's a complete nightmare.

    Personally I dislike putting anything like a singleton-ish manager in a scene and instead prefer it to lazy load on demand, and this is the pattern I always use:

    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. }