How to access a variable globally?

Hi! So I have a game board consisting of tiles as shown below:

Each tile is a game object with a script of the custom class “Tile” (inherited from Monobehaviour) attached to it

I want to create a variable, something like:

public List<List<Tile>> gameBoard

in order to reference all the tiles on the board using a coordinate system.

However, where should I put this variable such that it can be accessed from any script besides using a singleton?

I think tried using scriptable objects, however, they can’t reference in-scene objects and it said type mismatch.

What should I do in this case? Thanks!

It’s probably a good idea to have a think about what you want to do with the tiles and why you want to make it globally accessible, then avoid making it globally accessible.

It’s the global accessibility that’s the bad part, but if you’re committed to that then just use a singleton.

If you just want to grab a tile from a coordinate you could do something like:

GameBoard.Instance.GetTile(tileIndex)

Often it’s nicer to flatten a 2D array (which I’m guessing your List<List<>> is doing) into a 1-D array, where int index = x + y * width; Then you can use a Tile array. Tiles then are uniquely identifiable by just their index (no 2D coordinate necessary). But Vector2Int is just as viable if that’s your preference.

1 Like

Whatever you want to do, DO NOT USE global variables. They are always a code smell, and if you want to use them probably it’s a workaround or something to start a messed code. Keep classes and functions isolated each other. If you need to access a variables stored elsewhere, give it as argument in contructor or in the method that needs the variable.

1 Like

I see! I really like your idea of compressing the 2D array into a 1D array. Thanks for the tip!

I see thanks! I was just wondering: Will there always be a solution where you can isolate classes and functions in any game system? Are there occasions that you must have dependencies?

If you are going to offer easy global access to all the tiles, you might as well make that part of the Tile class. Then it’ll be easy to find, the API will be nicely named (e.g. Tile.Get), and you can fully encapsulate all the implementation details of registering and deregistering instances inside the one class.

You’d then also want to give Tile an early execution order, so that its instances will be able to insert themselves into the array before other classes try to retrieve them.

[DefaultExecutionOrder(-10000)]
public sealed class Tile : MonoBehaviour
{
	static readonly Tile[] instances = new Tile[TileCount];

	public static Tile Get(int x, int y) => instances[GetTileIndex(x, y)]

	void OnEnable() => instances[GetTileIndex(x, y)] = this;
	void OnDisable() => instances[GetTileIndex(x, y)] = null;

	...
}

If you want to use a ScriptableObject instead, you can add Register and Deregister methods to it, give all your tiles a reference to it via a serialized field, and have them register themselves at runtime during OnEnable, and deregister during OnDisable.

[CreateAssetMenu]
public class TileCollection : ScriptableObject
{
	readonly Tile[] instances = new Tile[TileCount];

	public Tile Get(int x, int y) => instances[GetTileIndex(x, y)]

	internal void Register(Tile tile) => instances[GetTileIndex(tile.X, tile.Y)] = tile;
	internal void Deregister(int x, int y) => instances[GetTileIndex(x, y)];

	...
}

Edit: Third option, with no need to rely on script execution order:

public class Tiles : MonoBehaviour
{
	static Tiles instance;

	[SerializeField] Tile[] tiles;

	public static Tile Get(int x, int y) => (instance ??= Object.FindAnyObjectByType<Tile>()).tiles[GetTileIndex(x, y)];

	void OnDestroy() => instance = null;
}

Hi Thanks for your reply! I tried using our second method of scriptable objects. However, it returns the error of

NullReferenceException: Object reference not set to an instance of an object

whenever I tried to access the tiles in the instance array using the Get() method. Is it because you can’t reference in-scene objects in scriptable objects?

Their example implies that you register the tiles at runtime before you should access them. Though there’s always the possibility that you’re trying to get a tile before they have been registered; such is a common problem with dealing with this sort of stuff.

I would myself have a component on a common parent for all these tiles, which has a reference to every tile. This can either be done at runtime or edit time (preferably automated with editor scripting).

That said with anything tile based, consider whether your tiles can be represented as pure data/C# objects, with a visual representation.

Like @spiney199 said, you need to modify your Tile class to have them register themselves to the TileCollection:

[DefaultExecutionOrder(-10000)]
public sealed class Tile : MonoBehaviour
{
	[SerializeField] TileCollection tiles;

	void OnEnable() => tiles.Register(this);
	void OnDisable() => tiles.Deregister(this);
}

For more information, here’s a talk detailing the Runtime Sets design pattern that TileCollection uses:

You can assign references to scenes objects in the fields of your scriptable objects - but only at runtime.

You can’t do it in Edit Mode, because the scene will only be loaded at runtime, and Unity doesn’t support persisting references across scene boundaries to disk. So all references to scene objects assigned in Edit Mode would become Missing References, the moment that the scene is unloaded, or the scriptable object is reloaded to memory from disk.

If I have a parent object containing the reference to every tile, doesn’t that mean I have a dependency on that object whenever I want to reference the tiles?

Also what do you mean by the pure data and visual representation? Thanks

Thanks for the tutorial. I have tried using that method, but somehow the indexing just doesn’t work. The following is my code:

public class TileCollectionSO : ScriptableObject
{
    public readonly List<Tile> items = new List<Tile>(42);

    public int GetTileIndex(int x, int y)
    {
        return y * 6 + x;
    }

    public void Add(Tile tile)
    {
        items[GetTileIndex(tile.xcoor, tile.ycoor)] = tile;
    }

    public void Remove(Tile tile)
    {
        items[GetTileIndex(tile.xcoor, tile.ycoor)] = null;
    }
}

And for my tile class:

public class Tile : MonoBehaviour, IPointerUpHandler
{
    [SerializeField] private TileCollectionSO tileCollection;

    public Image sprite;
    public int xcoor;
    public int ycoor;
    public Card occupiedCard = null;

    private void OnEnable()
    {
        tileCollection.Add(this);
    }

    private void OnDisable()
    {
        tileCollection.Remove(this);
    }
}

However, I want the list items to arrange the tiles in ascending index order as I am building a card placement system, where the player will click on the tiles to place cards. Therefore, I want to check whether the card is placed at a tile whose ycoor is smaller than 3.

But the indexing somehow isn’t working. For example, the first element of the List is Tile(21), which should be Tile(0) instead. I also tried using an array instead of List, but it throws an error.

Any idea what’s wrong? Thanks

If xcoord and ycoord are based on the transform.position of the tiles, then you can simply rotate your camera, until the tile at (0,0) is located in the corner you’d like it to be.

You might also want to check that you haven’t gotten width (tiles/row) and height (tiles/column) mixed up when calculating the element index. The correct formula is y * width + x.

1 Like

One object or another is going to have to reference these tiles. If it’s on a common parent to these tiles, then any child game object can simply GetComponentInParent for it, or potentially have a serialised reference to the component.

In any case, multiple ways to solve the problem here, and multiple might be needed in the end.

As in represent the state of these tiles in pure C# (classes that don’t inherit from Unity’s component or scriptable object types). Then from this data structure, build your visual representation.

Consider using a Model-View-Controller architecture to handle your Tiles.

So instead of referencing your tile GameObjects directly, you store your tiles purely in a data format within a Scriptable Object.

public class TileManager
{
    public event Action OnTileUpdated;
    [field:SerializeField]
    public Tiles[][] tiles {get;private set;}
}

Then your tile game objects (your views) react to changes in the data.

Basically right now you archicture is set up like this:

Controller → Tile (GameObject)

In other words, you’re trying to make GameObjects globally accessible. Instead:

Controller → Data ← Tile (GameObject).

GameObjects do not have a permanent life cycle so they shouldn’t be globally accessible (unless they’re a undestroyable singleton).

1 Like

You’re right. At this point, I might just set up a serialised reference to the GameBoard (parent to all the tiles) in my other scripts

And the main reason I can’t have Tile as pure C# classes is that I used the unity UI interfaces as I want to detect clicking of the Tiles. But I probably should change this mechanism in the future. Thanks tho

1 Like

I’m a bit hesitant to reply at this point, it has been covered in lots of different ways, but… I must be missing something here. If I needed to implement such a thing (and you really shouldn’t think of this as just “tiles on a board” but rather “things on a thing”) I “think” I would create a game object that was the board with a script for “board stuff” and child objects with a “tile script” on each.

So the only object that generally needs to be available is the board isn’t it? Can anything that needs to happen to tile be handled via the board?

The tile objects certainly wouldn’t be held in a grid or perhaps not even in an array. Sure it can be an array but the board handles those details. It just happens (in this case) that you want to display them as a grid but that is just how you render it, not how it is represented internally.

How many “other scripts” would require access to the board? If not many wouldn’t you just assign the board reference and be done with it?

When you get your game working you can review for refactoring candidates but at least you have a working app. That can make design and refactoring easier as if things break you can revert back to the working copy you had.

I could be missing something important to the discussion but I don’t see this being particularly difficult or unusual.

1 Like

You have a point. I might as well first see which scripts really need the tiles set. Thanks

There’s absolutely nothing wrong with global variables. Just put them in a static class and access from anywhere as you need. Don’t limit yourself with some weird “clean-code” rules.

Only the Sith and junior programmers deal in absolutes.