Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question Grid-based movement on a Tilemap: How do I make each Tile store who's standing on it?

Discussion in '2D' started by sandbok, May 31, 2023.

  1. sandbok

    sandbok

    Joined:
    Dec 1, 2016
    Posts:
    19
    I'm working on a turn based strategy game where you move units on a grid.

    upload_2023-5-31_23-10-38.png

    Each square on the grid can occupy at most one unit. The most obvious solution seems to be having each Tile store their "occupant", ie which unit, if any, is currently standing on them.

    I tried making a class that extends the base Tile class and added a public GameObject field. But in the Editor, I can't see this field, which makes it difficult to know if it's actually working as intended.

    upload_2023-5-31_23-15-28.png

    I'm not sure if I'm doing something wrong.

    How should I approach this problem?
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,514
    This presumes the "your data model is stored in Unity" approach.

    A lot of games do this, it works, but it makes it very hard to ever do a save game.

    I prefer to use the tiles merely as a "presentation layer" and keep all my own state in something I can easily save.

    For a grid game, I would just keep your own collection of "other things" that are on the map.

    If you're already indexing the tilemap by Vector2Int coordinates, you can even just make a Dictionary of those.

    https://www.reddit.com/r/Unity3D/comments/rqe8dv/dictionary_with_vector2int_as_key/

    You can see some examples of griddy stuff in my MakeGeo project.

    MakeGeo is presently hosted at these locations:

    https://bitbucket.org/kurtdekker/makegeo

    https://github.com/kurtdekker/makegeo

    https://gitlab.com/kurtdekker/makegeo

    https://sourceforge.net/p/makegeo
     
  3. sandbok

    sandbok

    Joined:
    Dec 1, 2016
    Posts:
    19
    Can you explain what you mean about the save game?

    Considering that I'm already using the Tilemap, it does make sense to me that each Tile stores its occupant. Having to come up with my own separate data structure to store extra info for each tile, and cross-referencing with the Tilemap, seems tremendously wasteful.

    I'll take a look at your examples later, thanks for linking.
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,514
    That's not the correct framing. If you have a massive world two miles on a side you are not going to want to tell Unity stand up ten zillion-zillion tiles to store your world... you'd probably run out of memory.

    Anything you want to save that is stored / changed in Unity's structures needs to be moved back to your own C# structures before saving, generally speaking. Here's more:

    Load/Save steps:

    https://forum.unity.com/threads/save-system-questions.930366/#post-6087384

    An excellent discussion of loading/saving in Unity3D by Xarbrough:

    https://forum.unity.com/threads/save-system.1232301/#post-7872586

    Loading/Saving ScriptableObjects by a proxy identifier such as name:

    https://forum.unity.com/threads/use...lds-in-editor-and-build.1327059/#post-8394573

    When loading, you can never re-create a MonoBehaviour or ScriptableObject instance directly from JSON. The reason is they are hybrid C# and native engine objects, and when the JSON package calls
    new
    to make one, it cannot make the native engine portion of the object.

    Instead you must first create the MonoBehaviour using AddComponent<T>() on a GameObject instance, or use ScriptableObject.CreateInstance<T>() to make your SO, then use the appropriate JSON "populate object" call to fill in its public fields.

    If you want to use PlayerPrefs to save your game, it's always better to use a JSON-based wrapper such as this one I forked from a fellow named Brett M Johnson on github:

    https://gist.github.com/kurtdekker/7db0500da01c3eb2a7ac8040198ce7f6

    Do not use the binary formatter/serializer: it is insecure, it cannot be made secure, and it makes debugging very difficult, plus it actually will NOT prevent people from modifying your save data on their computers.

    https://docs.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide
     
  5. sandbok

    sandbok

    Joined:
    Dec 1, 2016
    Posts:
    19
    I don't intend to have a massive world loaded at any point during gameplay. An individual "level" will have perhaps 250 Tiles loaded at once. I would think adding half a dozen variables for each individual Tile to keep track of would be negligible, from a performance angle.

    My real concern is that Unity will not expose an individual Tile's properties in the inspector. If I can't easily see what's actually going on, I can't build and test my game. Even if I do build my own Tile class that extends the default Unity one, I can't check it like I can a regular GameObject.

    Regarding Loading: I intend to store all levels as prefab GameObjects within Unity. I only plan to fuss around with importing/exporting data for things like dialog, where I don't want to be manually entering the data by hand.

    I haven't thought far ahead enough for saving/loading a Level mid-progress, but I figure I can create a data structure for that later on.
     
  6. karderos

    karderos

    Joined:
    Mar 28, 2023
    Posts:
    376
    its not wasteful, you fundamentally dont understand how the tilemap is working

    there is no "tile instance" for each tile you placed on the tilemap

    when you set a tile on a tilemap it is drawn on a mesh, and it has no more attributes

    to prove this you can set a tile, then you change the sprite on the tile, and it wont change its appearance on the tilemap unless you refresh it, this makes it possible to create entire maps using only 1 tile, while changing the sprite of the tile, setting it on a new position, and as long as you never refresh the old tiles they will never change back.

    With this in mind, what you should do is create a dictionary of vector2(x,y) like kurt said, and store each entity in that dictionary.

    This is not wasteful, its the most effective way to do it
     
  7. SeerSucker69

    SeerSucker69

    Joined:
    Mar 6, 2021
    Posts:
    68
    I like using a 2D array, something like UnitMap[x,y]. You can keep it simple and store the int of the unit number there, or have a whole struct or Class containing lists of units, resources etc.

    In other words, keep your game data separate from your "screen display". This helps you do game logic without having to look at whats on the screen. example if (UnitMap[x,y]>0) {there is a unit here!}
     
    sandbok likes this.
  8. sandbok

    sandbok

    Joined:
    Dec 1, 2016
    Posts:
    19
    That explains a lot. It seems like a weird limitation to me, but you're right, I misunderstood how the tilemap works.

    With that in mind... I'll think about the dictionary solution. I'm not sure how much info I'll need to store in each Tile, but right now if it's just one field, that works.
     
  9. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,514
    It only feels weird because you are still thinking about it as a useful repository of state.

    It is a presentation mechanism, nothing more.
     
    sandbok likes this.