Monster shadow

VIDEO

How to save Unity Tilemaps

Description:
Learn how to load and save Unity tilemaps using scriptable objects. Alternatively, I'll show you how to convert tilemaps into JSON or a compressed string so that you can easily save them to a database and allow players to create their own levels to share. Extra: Learn how to add buttons to your unity editor scripts. Scripts: https://bit.ly/3wLN8Sk ========= SUBSCRIBE: https://bit.ly/3eqG1Z6 0:00 Intro 0:30 Base tile script 2:30 Tilemap Manager 3:45 Create the level scriptable object 5:00 Saving the map 11:00 Adding buttons to the editor 14:00 Clearing the map 15:45 Loading the map 21:00 Saving to JSON 22:40 Manual serialization for compression
ADD A COMMENT

322ss
Thanks for the video, some interesting points there. BTW, it would not probably be a good idea to ever serialize those scriptable objects into json as is, those tile instance IDs can change AFAIK. So after adding or removing some stuff from project and then loading a level might not work as expected, even if the save/load was editor only feature. I guess you already know this, but there is that pretty print parameter for JSONUtility which outputs the data with line breaks and indentations, it is pretty useful when you need to read that data in text editor.
Tarodev
You're right, you should never serialize a ScriptableObject. It was naughty of me to use it as an example. Hopefully viewers don't stop the video right there and take it to heart without listening to the following advice, heh.
Ironbard
Subscribers++; I'm crunching on a game and this might just save some features from the chopping block. Thank you!
srisrid multi purpose channel
Cool :)
Виктор Курбатов
Cool, thanks so much for your time and consideration
SEE ALL COMMENTS


Transcript:

okay so a question i often see asked is how do i
save and load my generated tile maps uh both in the editor and at runtime so
i'm going to show you how to do that as well as showing you two separate ways
to actually serialize and save your maps depending on what type of game you're
making so to start i've just got my grid
and then i've got a ground map and a unit map for separation
okay and to make your life easier in this process
i always recommend creating a base level tile script and i'll show you how that
works basically it's just a class that derives
from the unity provided tile class okay and then i
create just a little uh asset menu create asset menu uh attribute here and
what that does is it just allows me to right click
create 2d tiles and just a new level tile so i
could call that tree or something if i wanted a tree
tile okay and then in here i've just got a enum
about what what the tile is so i'm separating
it here by the actual map type so here i've got grass i could add water
lava sand you know whatever i want and then
units and i always recommend assigning an
integer value to your enums because you could come in here and you could add
you know a few more things like this and then later on down the track you
could remove this like that and if you have not set a
unique integer to these uh c-sharp will do it
for you and if you remove this this will actually
uh shift the indexes and in the uh editor in the unity editor you'll
actually your enum values that were previously set
will change so that's uh that's something to avoid so always set
integers and as you can see here i'm jumping up a
thousand that's just so that uh i give myself enough growth enough
expansion growth to have a hundred ground tiles before
before i run out it's just a nice way to keep it organized
all right so once you've got that sorted and you've created your tiles
and in your palette let's start actually painting so i'm gonna grab my grass tile
a little island get my unit the snorlax there and then maybe a
harlequin there so how do we save it so what we'll do
is we will create a tile map manager and i've got a tarmac manager
object here so i'm just going to slide that on there
and then open it up so what do we need in here
we need a few functions we need a save map function
we'll have a clear map function just for ease of use and then we'll also
have a load
so let's work on saving first what we will do is we will grab two
references and that will be to our tile maps
whoops tile map and it will be our ground map
then we've got a unit you'll add additional maps depending
on what you need and we'll have a integer here that will tell us
what the level index is that we're trying to to save so we might be saving
level one or level two now we need actually something to save
so let's create a new script and it will be a scriptable object let's
call this scriptable level
and open that up we can delete this stuff let's turn this into a scriptable
object and we need a few things here we will
have an integer and this will be our level index
we will have a list of tiles but we don't want to be saving
just the tile because we need to tell unity where the actual tile has been
saved right so we're going to create a new object
here and it's going to be a public class saved tile to keep it nice and
simple and this will have a vector
3 inch because that's what the tilemap system uses and this will be a position
also this will have a level tile okay so basically all we're saying here
is this is our tile and this is where we want you to
save it over here we can say a save tile and this will be our ground tiles
like that and then we'll just copy that and these will be our unit tiles okay
so nice and simple and then across at our tile map manager we want to
create a new scriptable level and we can do
that by script usually you'll create like a asset menu
thing here in the same way that i did here
and then you manually create these scriptable objects in the editor
but what we're going to do is we're going to create it via script
okay so new level and we're going to be invoking the actual factory method of
scriptable objects called create instance and it will be of
type scriptable level okay so we've just
created in memory a version of this scriptable level and
then we need to assign uh stuff to it so
okay so level index we will assign that to our
level index which is right here which you'll be setting in the editor
will also whoops let me level will also set its name
and let's call it something like level and level index as that seems to make
sense next up is to set our actual
tiles so to do this we need to loop over our tile map
check to see if there's a tile in that position check what type of tile it is
and then we need to create a new saved tile
object so that we can place it in the list here
okay so let's just comment that for now and we'll create a local function here
that we can use for all the older maps okay so this will
return this will be an ieee numerable
and it will return a save child get tiles from that okay and this will take
in a tile map now to grab all the tiles
from a map unity does it a little bit weird
um so i'll show you how to do that we go map
cell bounce and all positions within and basically this is just a vector
three inch i believe yeah so basically it's taking all the
positions regardless of if the tile has been filled or not
and it's just returning a position so it's gonna it's gonna calculate the
bounds and um just iterate through them so what
we need to do first is we need to say if map has tile and then we send in this
if it does will bananas so now that we know that there is a tile
let's actually grab the tile so this will be a level tile because
we're going to cast to it map sketch tile
and we'll use this generic one so that we can actually pass
it to a level tile and we'll just send in the position there
so now that we've got our level tile we can actually say
okay well what sort of type it is all right and we can grab whatever
information like right right now we've just got this
and it's absolutely bare basic so in the future you'll add additional
things here like additional logic you know is it rotated all this kind of
stuff um so but for now we've just got that
now that we've got our tile we can return it so
yield return new save tile and the position will be equal to buzz
and the tile will be equal to level tile
okay so now that we've got this function we can
now say our new level dot ground tiles equals
get tiles from map and we'll send in our ground map
and this needs a list to properly be serialized
um next we'll do our new level dot unit tiles equals get files from map
this time we'll send in our unit map and to list
all right so now we've got a scriptable object we're setting its index
its name and the tiles so now we need to actually save the object and i'll show
you how to do that what we're going to do is we're going to
create a little class that should only be in the
editor and that's because it won't actually
work in a built product so what we need to do is set a
conditional statement here um if we're in the unity what is it
unity editor we will run this and in here we will create a public class
and we'll call this um descriptible object utility
something like that and let's actually make that static all right next
we need a little function that will actually save the
assets so let's call that level save level
file and this will take in the scriptable level
objects and first we're going to say asset database
oops i said database create asset this will be the level and
the path will be something like uh it will be assets
for such resources and i'll show you why i put it in the resources folder
levels and here we will have the
level dot name dot okay so that's that's the path that
we're going to be saving this physical file to
next we'll say asset database save assets
and then we'll give it a refresh as well force a refresh and make that static
it's in a static class okay so now we can use
that let's just grab that and then down here we'll say
save level file and then we'll send in the new level
oops this will need the scriptable object utility that we just created
and there we go so that should save the file
now we need a way to actually uh trigger this from inside the editor so
let's create a another script let's go back to the
editor and actually create a folder called
editor and this is required to make custom
utility drawers for this script here i'd like to put some buttons here so that we
can easily click it from within the editor
so let's create a new script call this tile map manager
editor okay open that up and we'll remove this and this will be
deriving from editor scripts
and then this here will be a new attribute custom editor
and then here we'll say type of and it will be of a timer manager
and then here we'll be overriding on inspector gui
okay in here we will say default inspector
which will just draw the uh inspector as it is already with
you know the serialized objects we will grab
a reference to our actual timeout manager script
so let's just say the scripts and here we'll cast this to automat manager
just target so target is the actual script object that it's attached to
and here we'll say if gui layouts button and then here is just the name of
the button so this will be save map
like that and from here we can say script
save map and that will actually call the attach tile manager script
uh and run this function here so let's try that out all right
as you can see we've got our save map button here i'm going to put in my
ground map and my unit map let's make this level 1
and save map and parent directory must be created all right so what we need to
do is we need to create our resources folder
and then we need to create oops a level folder levels and now
that should work save map there we go so now we've got our level
and it is not serializing
properly because i have not made this serializable
okay so that will force this class to be serializable in the editor
and let's try to redo that so let's delete that level
comma manager save and there we go so we've got our ground tiles and their
positions okay so now that we've saved it let's
make a convenient way to clear it so
with that we can just say let's grab all of our tile maps
equals find objects of type tile map and then forage
um maps clear all tasks then in our editor again
we can copy this and this one will be clear map
this one we'll call the clear map function
command manager clear map and there we go
so now let's work on actually bringing that level back
so in time manager load map so basically how this is going
to work is it's just going to load whatever the level index
um we've got here so this this is going to be used for saving and loading
just for brevity okay first we need to grab the levels so let's say level
equals resources dot load and we'll use the
generic method and we'll say that it's a scriptable
level and then in here we will say it's in the levels this will
automatically look in the resources folder by the way
and then it will be in the levels folder level
and then the level index now this can actually be null
uh if you entered a level index that doesn't exist so let's actually check
for that if level equals null and say debug
log say debug error and we'll say level
model index because not exist and then just return
as we don't want to continue right now we know our level is not null
uh let's just clear the map just in case there's any stragglers
so now we actually need to assemble our maps so let's start iterating over our
tiles so this will be of level ground tiles
and this will be a saved tile and then this will be a
in here i'll have a switch statement and it will be a
type save tile file type and then in here i'm just going to
generate what i've got so these two here are not applicable as
they are my unit types so right now i've just
got grass type tile so what i want to do is just lay the
tile in the position that the save tile is declaring so what i'll do is i'll say
um ground map set tile
then this will be at position save tile dot
position and it will be of type save tile
tile now just bear with me for a moment and i'll explain why i do
it in this kind of like convoluted way they grab that and now let's do it for
our unit tiles and now in our unit tiles we'll have
uh quinn and then we've also got a snorlax don't okay so the reason i
do it this way iterating over the tiles and then actually specifically
uh checking what tile it is to do something um
i know in this scenario right now we're literally just doing the same thing just
setting down the tile but for example in a in in your fleshed
out game when you summon an enemy you might
actually want to add this enemy to a list in your game
manager or something so you would first set the tile and then here you
would say you know game manager dot uh let's uh this this is not going
to work or game manager dot add enemy or whatever and
then add the enemy in or for example in your lava tile you
might not just want to set the tile you might also want to spawn a particle
system on the lava or something um so this is a
a way to give you that granular control over each
tile um but as you can see here like i've doubled up so
if if you've got all enemies or if you if you've got like you know 10 different
ground types and they don't specifically do anything uh special you can just
stack them on top of each other like this and they'll all just
hit this case and then i'll do the same thing
so this is just how i do it you can uh you can do it however you want but
this works well for me okay and this should actually already do what we want
it to do and and load it all onto the the map so
let's create a click for this in our tilemap editor
we'll do this and this will be load map and this will just call the loan
function so it will say load map see if the level
exists clear any stragglers and then actually
generate the map so let's give that a crack
so timeout manager level index one and load map and i did something wrong here
which is here i'm actually setting our monsters
to the ground type um and i actually don't like repeating this
code so let's create a little function here let's just say
our own version accept file and this will actually take in
the tile map and then save tile like that and we'll grab this
and we'll say map and this will just set the tile like that
and then here we can just say this will be the
unit map and we want to send in the tile
okay so let's try that out again so tile map
load and there we go it loads up to our correct
tile map we can clear the map again and then let's say we want to create level
two and i'll just draw something crazy
at a snorlax summer map save map and now we can say level one let's load
map two load map and there you go it's that
easy uh loading your level in game so
see how i do this here just grab the level like that
you could have in your level manager uh load level and then
send in an institute value or at what level is this
uh what level is this player at and then just grab it from
the resources folder like that and then assemble the map
just like this and then there you go you've loaded your level
okay so i've shown you how to serialize them into a
scriptable object but what if you wanted to offer
a level editor uh in-game to your players like if they wanted to create
their own levels like mario maker or you know whatever
uh they can't exactly create actual uh files on the file system
and then share those with other people it's just not it's just not a logical
way to do it so what you could do is instead of
creating a scriptable object you could actually just save the data
into a json object and then just store the string in a
database which can be shared to other players
quite easily it's also a really easy way to
push new content to your game without necessarily
updating the game and sending these new scriptable objects into the new build
so it's actually quite easy to do and i wouldn't actually recommend
serializing this scriptable level object into json uh but i'm just going to show
you how to do it uh using this object um so we could just
say json equals and now that we've set you
know we've set all of our parameters here all the tiles and the
name and stuff we'll just say whoops we'll say we'll use unity is
built in json utility and we'll say to
and just new level so i'll just show you how that looks
attach a little break point touch to unity
and i will save this map now back in unity we can see
this big ass serialization now this is not an efficient efficient way to
serialize your object if you're wanting to save it
um at the very least you wouldn't use a scriptable object you would just use
like a struct or something and that will serialize down a little
bit better than this but and then also because there's a lot of
repeat words you could even compress the string an even better idea
would be to create your own serialization
methods so for example instead of this whole thing here there's one tile
you could say all right so this is tile type
1 and then here it is just the x and y because um you don't need to
actually show all that you don't even need to show
the this is x this is why this is z you can just say
uh this is at position one two like that and that could be your full tile
serialization right and then the next one will be you know the next one and
you could you could do a whole map of just that and it will just be this tiny
little string which you could potentially compress
even further all right and then you would send that
off to uh the database if it's mario maker
um or save it locally if they're just still editing it
um and then ultimately it's super easy for other players
to go through a list of levels that someone else has made and easily just
pull down a level uh with hardly any data transfer at all
really actually while i'm at it i'll just
quickly show you how i would tackle uh doing this so i'll just do it at the
bottom here so let's say instead of the scriptable
level object you have your own little structure and let's actually say that
it's got the same stuff as the scriptable level okay
and in here it will have a function which
will return the actual serialized string and it will be called serialize
now i will be using a string builder that's the most efficient
way to do it when you're constantly appending the
strings now the trick to this is you are setting little keys
uh for larger words to basically uh compress it so for example first
we're going to be doing the ground tiles so on my builder i'm going to append
let's say uh let's say g okay and now and this will be also an
open bracket let's say so this will this is going to be g the the ground tiles
so now i'm going to say for each ground tile
say builder dot append and this will be for example the tile type so the ground
tile oops ground tile tile type
and we can serialize that down to its actual integer
values which will be you know this will be 0 for example for the grass
and then directly after it it could be here we'll have our ground tile
position x and our ground whoops and another one of those
comma grand tile position okay and then at the end there of the
ground tiles we can just say all right that's the end
of the ground tiles so then you do that for your units and then
other other whatever else you've got in your level
and then just simply return builder dot to string
and there you go you've got your level so now instead of creating these
scriptable levels like this you would just create a new
level which is this object assign the things like you did up there and then
instead of using this scriptable object utility to
actually save it you would just say level new level dot serialize which is
this function here and it would return the compressed
string which you can then send off to your database and save
so anyway uh i hope this helped you i know it was a bit convoluted and i
i took a few tangents here and there just to teach you a few things but uh
this is how i do it and it's worked quite well for me in the
past so if you liked it and you learned
something leave a like subscribe to the channel
and i will see you in the next one
you