If you create a level for your game, Unity
will save it automatically. But what if there will be changes during runtime
or even better, the player creates a big part of the tilemap? Well then you need to save it on your own. But don't worry, creating a basic saving and
loading system is quite easy, of course the details will take more time, but this is your
part, not mine, so let's start. All you need to save tilemaps is of course
some way to edit a tilemap during runtime. In my case I use the code from my tutorial
series about InGame Tilemap Editing. Here we can draw different tiles onto different
tilemaps, so the result of this video will also work for all your existing tilemaps in
your scene - yey. I start by adding two buttons, one for saving
and one for loading. You can do this now however you want, but
at least you need some way to trigger saving and loading. Of course this can also happen during exit
and on start or something like this. Now to add some click events we need a new
script first. Let's call it SaveHandler. And now add public methods for saving and
loading. We will leave them empty for now. Also import the "UnityEngine.Tilemaps" package
at the top. We will need it. I also add three variables: the first one
is a private dictionary. You will see its use case soon. Just add the line like I did. The second one is a variable we need to fill
in the inspector, so make it serialized. It is of type BoundsInt and will be the border
of the tilemap area we are saving. More explanation later. And the third one is the filename for the
file which will be saved and loaded. I make it serialized and also give it a default
value. Make sure to always add the file type of "json"
as well. Now… to save the content of one or more
tilemaps we need those tilemaps. Of course you can just use a prefilled list
where you add the maps. But in our case the tilemaps are generated
during runtime based on the different layers we can draw onto. So I will show you how to get all the tilemaps
from your scene. The initTilemaps function will be called on
start. I will use the FindObjectsOfType method. This will now return all Tilemaps that exist
in the scene. Just make sure to use "Objects" which returns
an array. Explicit type declaration helps with errors
like this, like you can see here. Now instead of just saving the list, I will
rather save this data in the dictionary from earlier. By using the name of the tilemaps object it
is easier to find the according map on loading, because the name will be our unique identifier
for the different maps during saving. Now be aware that you can have tilemaps with
the same name - this will lead to errors in this script. So if this is true in your case, you need
to expand your identifier by something besides the name. Just make sure that it stays the same during
play sessions. I loop through the retrieved maps and add
the entry to the dictionary. Now that's nice, can work, sometimes. The thing here is, like I said, the tilemaps
get created during runtime. In the Start Method of the according script. And we retrieve them also in a Start method. So we need to make sure that this method here
is being executed later that the one that created the maps, right? It's actually quite easy. In Unity go to Edit > Project Settings. Now find the "Script Execution Order". Every script that is not mentioned somewhere
here will be executed during this "Default Time" slot. So let's just add our SaveHandler and put
it at the end, that's fine, as long as it's below the "Default Time". And while we are here, let's prepare the buttons. At first add the script to your scene, I will
add it to my universal manager object. Then you can go to your buttons and add a
new onClick event. Drag and drop your manager object into the
GameObject slot and now you can look for your script and the according method. If you can't see your methods make sure they
are public. Repeat this for your load button as well. Now that this is prepared let's switch back
to the script. During start, all the tilemaps will be saved
to the dictionary. So on load or save we can easily work with
them. Of course, if you add or remove tilemaps during
runtime you need to call the initTilemaps method before executing the saving or loading
methods. For the saving part: I highly recommend you
to watch my tutorial about Saving & Loading with JSON Files. We will use the FileHandler script I explain
in this tutorial for this saving process as well. So it will definitely help your understanding
of the process and might help you adapting the process to your own needs. But if you don't want to, you will find the
script linked in the description. But the following explanations assume you
know about this video. And not a requirement but recommended is my
tutorial about saving high score lists. This will show you another use case on how
to use the FileHandler script. At first we need to define a format which
will be saved. We do this by creating a native C# class. Let's start by importing the "System" package. That we can convert a class to a JSON object,
the class needs to have this Serializable tag above. Therefore the import of "System". The following variables now also need to be
public. Our first one is the key. This must be the key of the dictionary we
created earlier. Remember: In my case this is the name, but
in all cases it should be a string. This variable will be used to search in the
dictionary and to retrieve the according tilemap when loading. This is much faster than looking for a matching
value in a list. The second variable is a list of type TileInfo
which will hold all the tiles being used. What is TileInfo you might ask? This. A new class. Every single tile will be represented by an
object of this class. It consists of the TileBase and the position
in the tilemap as Vector3Int. That it's easier for me later to create instances
of this class I also add a constructor here where I assign the given parameters to the
variables. So for every tilemap that exists in our scene,
we instantiate an object of type TilemapData. Then for every filled tile, we create an object
of type TileInfo with the information of the position and the used TileBase. Then every TileInfo object will be saved in
our TilemapData object. That should then be all information you need
for one tilemap. Now let's code the process I just explained. Since we are working with several tilemaps
- but don't worry it also works with one - we want to save all those TilemapData objects
to a list. This list will then later be passed to the
FileHandler script from my mentioned tutorial. For each tilemap in our tilemap dictionary:
We create a new object of TilemapData. We don't have a constructor here, because
it is not necessary. So I can only assign the key like this. Looping through a dictionary will retrieve
an object where you can use .Value and .Key. So we can easily assign the key of the dictionary
to this variable like this. Now we want to check the tilemap for existing
tiles. For this you need some kind of boundaries. You can't just say "give me everything". You can either create those border manually,
like I will show next, or you retrieve them automatically from your tilemap like this
code snippet does. But let's do it by hand, so you have a chance
to understand it better. Taking a look at my game, I can't pan. So my boundaries are quite simple. The center of my tilemap is also the center
of my screen, so my width is currently like 1, 2, 3, 4, 5, 6, 7, 8, 9 tiles wide in one
direction or 18 in total and and 1, 2, 3, 4, 5 - 10 tiles tall. Earlier we created that BoundsInt variable,
remember? This is where you need to define those borders. Choose the lowest position on the bottom left. In my case this would be -9 for x and -5 for
y. To reach to the top right just set the full
size: here 18 and 10. For me, my tilemaps are all located at 0 for
the z-axis. Please make sure to set them all to 0 or to
change the boundaries value here, if your tilemaps are located differently. Now back in the code we can loop through all
the (x,y) combinations of the given boundaries. This looks like this: with two for-loops. If your z-axis is not zero, you need to add
this value to the loop as well - or if it is one static value - like 0 in my case - for
all your tilemaps, just follow along and replace the zero in the upcoming variable. Since we want to save the position as Vector3Int,
let's create a variable for it and use the values from the loops for x and y. I set the z-axis to zero. Change it if needed. Now we can retrieve the TileBase based on
the position variable. At this point we either retrieve a TileBase
or a null value. Saving null values might not be a great idea
so let's add this if statement. If we retrieve an actual TileBase we can create
an object for it of type TileInfo. Thanks to the constructor we can just pass
the two parameters for the TileBase itself and the position to the brackets here. Then we can add this object to the list of
TileInfo objects which exists in our TilemapData class. This list already exists because we not only
declared it but also initialized it here. Now this will happen for all the tilemaps
and all the tiles inside the given boundaries. But right at the end of our foreach loop,
before the code jumps to the next tilemap, let's add this TilemapData object to the list
we created at the start of this onSave method. Great, now we should have all the data we
need. At least I do - like I said you might to need
to add some more variables for extra data. But let's move on for now and you will later
see if you need additional information. So I mentioned my FileHandler script from
the JSON tutorial. Grab it from your storage or get it from the
GitHub link in the description. Either way you need to drag and drop it into
your scripts folder. Now we can just call the script from any other
script by its classname. That works because it is static. We call the "SaveToJSON" method and assign
the type of our list "TilemapData" as type. The method takes two arguments: the data itself
and the filename. Now if you made everything correct the saving
will work. Let's start play mode. I then draw on two of my tilemaps. Here in the hierarchy you can see all my five
tilemaps. That means in the saved JSON we will then
see five "TilemapData" objects, but only two of them will have entries of type "TileInfo". Now let's check it. I will click the save button. This logs the path where the file is saved,
which comes from a line in the FileHandler script. So let's open this file. With some auto formatting it also looks more
readable. Here we have the three empty entries and the
two filled ones. "tiles", the array of the "TileInfo" objects,
is here. But what happened to the file type we defined
as TileBase? This is what a TileBase looks like during
runtime if you debug it. Looks a bit different, doesn't it? Well what we can see in the json is - obviously
- the instanceID. Ok, but what does this mean? The instance id of an object is an unique
identifier. So based on this id you can find unambiguously
an object. Those Ids here reference to the tiles that
have been created earlier and it's quite simple to use them. But this approach comes with one downside:
you can only use the instance id if you do NOT generate the tiles during runtime. Everything generated during runtime will have
a different instance id every time. In this case you need to use something constant
as reference for those tiles, like the position from the sprite sheet they are coming from
or whatever you use. This won't be part of the tutorial. On load, the first thing we need to do, is
to retrieve the parsed json file. Fortunately the FileHandler script will handle
this part. Because we want to retrieve a list we also
need to call the method which reads a list. The magic in the background: Remember the
list we saved, where the TileBase contained some more information? Well when transforming the json back to the
list, the InstanceID gets automatically converted to the TileBase object again. The saved and the loaded list look the same,
taking a look in the debugger. That's the really big advantage of using the
instanceId. Now we can loop through the data. Since we saved the dictionary key in the data,
we can now easily retrieve the according tilemap for the data entry we have. But beforehand we should check if the key
exists in the dictionary or else the code would throw an exception. We will prevent this error by check it. If it does not exist I will simply log an
error message and skip the rest of this loop iteration and therefore this entry. Because we did not save the null-values let's
clear the whole tilemap before drawing back onto it. Now I can create a variable which tilemap
I want to fill. If there are entries, I loop trough the tiles
list which contains the position and the TileBase. This one line will now fill the timemap with
the data saved in the json. Well yeah, basically that's it. For all the tilemaps we assign the single
tile values and there is nothing more to do here. Let's give it a try in play mode. I hope everything works for you - if not,
try again, rewatch the tutorial or compare your own code with the GitHub repo, also works
wonders and there are some additional comments. Of course and as usual this tutorial and script
only provides the basic use case and I hope it is enough to help you to understand how
you can save tilemap data or also other data in Unity. Thank you for watching and also thank you
to everyone supporting me and the channel. Special thanks to my supporters on Patreon:
Pat Rick & nuxvomo. Thank you all. And as usual: If you enjoyed the content consider
liking and subscribing and I'll see you next time.