Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Question How to properly save data from a custom map editor?

Discussion in 'Scripting' started by azeews27, Jun 11, 2020.

  1. azeews27

    azeews27

    Joined:
    May 13, 2020
    Posts:
    9
    Hey! I created a scene dedicated to creating map layouts. It's a 2D isometric game, I coded everything in a way each tile is a prefab that gets instantiated at runtime. This map editor scene is supposed to be run so I can "paint" my tile prefabs into the grid and store that permanently in a file if I click the "save changes" button (for clarification, I am the only one that has access to this editor, the custom maps are literally the maps that will be shown ingame - it's NOT a game with a "create your map" feature.)

    The most reasonable way I thought to implement this was to have a ScriptableObject for each map that has an array of GameObjects, to save which tile prefab goes where. The script that instantiates everything at runtime reads from that and instantiates accordingly. The problem is that, apparently, SOs are not the right way to do this, since they are not supposed to save variable data permanently. And, indeed, when I reopen Unity, the whole map design is gone. So instead, using an external file to save the tile data seems appropriate. But I'm not sure how that's possible, cause I have to store a list of prefabs. How would I ever translate that into a json? I could maybe use an ID system, but honestly... that sounds way more complicated than I want it to be. Or maybe there's a workaround for using SOs? Or should I not use them at all?

    Here's an example of how the SOs look like:
    (It's not a 2D array so it can show in the inspector)

    upload_2020-6-11_12-17-22.png

    Any ideas? Thanks a lot in advance!
     
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,735
    ScriptableObjects will work for this but only if your map editing happens at Game Editing time. If this is a map editor for end users, it won't work, as you discovered.

    You do need some kind of "ID system" as you will need some way of identifying which prefabs you want to load.

    Unity actually has a module for this exact purpose called "Addressable Asset System". You can find documentation for it here: https://docs.unity3d.com/Packages/com.unity.addressables@1.9/manual/index.html

    The long and short of it is that you can get a unique ID for each prefab and save those IDs in your save file (these IDs are called "addresses"). Then you can use Addressables.Load to load that prefab from the address.

    If you don't want to use Addressables you will need to build your own way of identifying and loading specific prefabs. If you only have a few different prefabs, like 1-10, you could pretty easily just have a ScriptableObject that holds your prefab references, and a function that maps the numbers 1-10 to your 10 different prefabs and call it a day.
     
    azeews27 likes this.
  3. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,748
    If your data is in a grid, and your objects are named as shown (e.g. "Floor X", where X is a single character), then there's a very simple and efficient way to do this: just put a string in a file. Each character in the string represents a tile, and when you open the file you create in a text editor you'd see a straightforward representation of your level grid.

    (note: I'm saying "character" rather than "number" because the system I'm going to describe below doesn't care whether you're using letters or numbers, and if you add tiles to your repertoire you can start using Floor A, etc)

    First step: saving and loading a text file as a string:
    Code (csharp):
    1. using System.IO;
    2.  
    3. //saving
    4. string someLevel = "9000999904004505606545849";
    5. File.WriteAllText(Path.Combine(Application.persistentDataPath, "someLevelName.txt"), someLevel);
    6.  
    7. //loading
    8. string someLevel = File.ReadAllText(Path.Combine(Application.persistentDataPath, "someLevelName.txt"));
    That's the uber simple version, but hopefully clear enough (and with enough google-able terms) that you can expand it to fit your needs.

    Now interpreting the text:
    Put all your "Floor X" prefabs in a folder in Resources, so that you have e.g. Assets/Resources/Tiles/Floor 0.prefab. Now, you'll be able to load these based on the contents of a string, like:
    Code (csharp):
    1. GameObject[] tile = new GameObject[someLevel.Length];
    2. for (int c=0; c<someLevel.Length; c++) {
    3. string thisAssetPath = $"Tiles/Floor {someLevel[c]}";
    4. tile[c] = Resources.Load<GameObject>(thisAssetPath);
    5. }
    ...and from that point, you should have an identical "tile" array as shown in your ScriptableObject, and you can loop through it and spawn your level objects the same way you already are.

    Outputting the text from a level editor may look something like this. (There's definitely better ways to do this, but this is quick and dirty and probably works):
    Code (csharp):
    1. StringBuilder sb = new StringBuilder();
    2. for (int c=0; c<tile.Length; c++) {
    3. char thisTileChar = tile[c].name[6]; // the 7th character (index 6) is your main differentiator
    4. sb.Append(thisTileChar);
    5. }
    6. string someLevel = sb.ToString();
     
    azeews27 likes this.
  4. azeews27

    azeews27

    Joined:
    May 13, 2020
    Posts:
    9
    @PraetorBlue @StarManta
    Thanks a lot!! Will give both ideas a shot.

    By the way, for now I only have floor cause that's the placeholder I'm using, but in the future I hope to have multiple different types of tile (e.g. grass, tree, water, mountain), and the number itself represents it's height. It's a game very similar to Tactics Ogre, if you want some reference:



    So I think using that character in a string approach directly wont work, but I'll try working around it by creating IDs a bit more complex than single chars.

    Anyway, thanks a lot!
     
  5. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,748
    Yeah, that's probably true. As long as you design a consistent scheme you can use the same concept.

    If you don't want to be restricted by that and would rather not worry about encoding stuff, the next step up from that would be using JSON. I'd recommend Json.NET in general for JSON, and you'd be able to save the entire name of the prefab in each tile slot, and not have to worry about parsing the string yourself. Plus it'd allow more complex data structures if that would help more.
     
    azeews27 likes this.