I know, I know… you all have been waiting
for this. Who am I to show you, how you can add all
those tiles and all those features without this essential part? Yes, you know what I am talking about, well,
I guess you read it in the title, but yeaaah we are going to delete tiles, finally. Deleting itself is quite easy, but of course
we want to integrate it in a nice way. Therefore I will show you a way, how you can
add different tools in a similar way to adding new tiles. But using a tool will not only add a tile
on click but rather can execute any custom code you defined. Let's stop talking and jump right to where
left in episode 4. For our tools we will create a new ScriptableObject. I create a script called "BuildingTool". But instead of letting it inherit from ScriptableObject,
we will use our BuildingObjectBase as parent. This will automatically include all the fields
from it to this class and of course, it will be a ScriptableObject. We also add a new menu entry for this class. Let's give it a look by creating one of those
tools. But to make sure it fits to our design, I
need to add a Category as well as an UICategory for all the upcoming tools. Note that we will not need the Category for
the upcoming code, but of course it should be separated from the other categories. To show the new UI Category in the UI we need
to add it to the according script in the manager object. Let's take a look… ah yeah, my UI design
is perfect and so flexible, it does definitely not break when adding new categories… Yes. In the according folder I can now create a
BuildingTool and of course, it will be an eraser. Select the new categories and you can also
select a PlaceType. Now even that we don't want to add a new Tile
we need to add a TileBase. On one hand the according image will be used
for the UI and - you might missed it - we will add a tile. Every time we show the preview of the selected
tile. So for this I got a simple icon online. Like we did in episode 1 I use the image to
create a Tile from it and save it in my Tiles folder. Now I can select the Tile Base I want. If you added everything correctly you should
now see your new eraser button, you can see the preview and you can even draw with it
- wait, what? Does this count? I guess not… Like I said we are going to use custom code
to handle the logic behind all our tools. Now your first thought might be to add this
code into the BuildingTool class, but ScriptableObjects are not meant for this. They don't have an awake or a start method
and basically are just not MonoBehaviour classes… so why not using one of those, right? Let's add a new script "ToolController". For now let's just create a public method
called "Eraser" and log a message. We will also make this class a singleton. Now we just need to call this method when
selecting and clicking with the BuildingTool, you know, instead of drawing with it. But for this, we need to know that the eraser
object we created is meant to call exactly this method. Let's start by adding an enum to the BuildingTool. Later on this should be the list where all
of your tools are listed. We then add an according field to the class,
where we can select one of those tool types. This will give us the opportunity to connect
the created object with the custom function we want. I add a new public method I call "use". Whenever this method will be called the custom
code gets executed. At first let's get the one and only object
of the ToolController. Remember that we can only write it like this,
because we made it a Singleton. And since we don't have an awake function
available here, we will just do it in this method. With a simple switch statement, we then compare
the selection of "toolType" with the available ToolTypes from the enum. Now we can just call the eraser function from
the tool controller. If no "toolType" has been set, I will log
an error. Back in Unity we can now set the ToolType
for our eraser. But at the moment we don't use the "Use" method,
we still just draw a tile. So let's take a look in our BuildingCreator
script. At first we need to make sure, that there
is a single point of truth for drawing. Currently we have different places in the
script, where we call "SetTile". Therefore let's expand the "DrawItem" method
with new parameters for the map, the position and the TileBase. Now we can call this method instead of the
SetTile method. Just replace your calls with the according
parameters. I will leave the preview map updates as they
are, because the preview does not affect the placing of the tools, so it can stay. Also update the initial call of the DrawItem
method as this method needs three parameters now which were previously defined inside the
method. Now that every real tile placement will be
executed by this function, we can adjust to logic to execute the custom code for the tools. If you were curious why all the code works,
even though the BuildingTool is not a BuildingObjectBase: Since we inherit from BuildingObjectBase,
a BuildingTool can always act as one, because it contains all the fields like its parent. Vice versa this won't work, because the BuildingObjectBase
misses the toolType attribute for example. So our BuildingTool currently acts as BuildingObjectBase
and is saved in the "selectedObj" variable. But we can check the type of the "selectedObj"
variable to find out if it might be a BuildingTool. And based on the result we can make different
things. In code this will look like this. But our BuildingTool still looks like a BuildingObjectBase
and won't offer us the "use" method we created earlier. For this we need to explicitly cast it to
type BuildingTool and now we can just call the use method. But if it is not a tool we will just call
the default "SetTile" method like previously. Now let's test it and see if we will see the
log inside the eraser-method of the ToolController. Great, as you can see the log appears and
also nothing gets drawn. I guess we could… starting erasing now? Finally? Yes. Let's fill the method with some beautiful
logic. Or, well… not quite yet. To delete something on the tilemaps, we need
the tilemaps first, don't we? Import the tilemaps package. Now I can add a variable for the list of tilemaps
we will retrieve from the hierarchy during the start method. Therefore I use the FindObjectsOfType method. Be careful with the "s" to retrieve an array. But I want a list, because lists are much
more nice, so I add "ToList()". This method comes from the System.Linq package,
which is always a little bit annoying to remember. But good for you is me mentioning it :) Instead of just saving it to the tilemaps
variable, I save it to a temporary variable. So I can loop through it and make some checks
to filter it. In my simple case I just don't want to add
the PreviewMap due to obvious reasons. I simply compare it by name. If the map is not the PreviewMap I add it
to the tilemaps list. Now remember from episode 4? The tilemaps get created during another start
method, so we need to make sure that this one here runs after the mentioned one. In Unity go to Edit > Project Settings > Script
Execution Order. Now add the ToolController script somewhere
after this "Default Time" slot and hit "apply". Now when using the eraser we should have all
tilemaps we want available. I am sure you now really want to delete something,
so let's start with erasing the tile on the clicked position for ALL maps. Crazy, isn't it? To start with this we need the position inside
our Eraser function. So let's add this as parameter. Now let's go back to where this method is
called - inside our BuildingTool. Here we need to repeat this step. Add the position as parameter and also pass
it to our Eraser function. And one step further back we are in our BuildingCreator. When calling the "use" method we have the
position already available so we can just pass it. Now let's loop through all the maps and just
replace the tile at our position with null. This one line here basically is the deletion
part. Had you guessed it? Well let's give it a try. I add some floors and walls and then… everything
gets removed. I just realized that you can click and hold
with the "Single" Place Type. That was not the behavior I initially planned,
does this count as bug or feature now? Ok great, we can delete things. But I see it might be not practical to remove
everything. So let's change the code to only erase the
tile on top. One approach could be to use Raycasting but
I don't have colliders on my tiles, so I will show you a different way. What tilemap is above one another is determined
by this sorting order. We define the value when creating the categories
from where we create the tilemaps automatically. By using this value we can determine which
map is first and which one last. Then we just loop through them until we find
the first one with a tile at the given position. Sounds easy? Yes, because it is. All we need to do is to sort the maps after
retrieving them based on the mentioned value. Therefore we will use C#s Sort function. Since the sorting order value is inside the
TilemapRenderer component, not the Tilemap component we need to get this first. Then we can just compare the two values against
each other and return the result. The sorting process is then done for you. I use b as the first value, so that higher
values are on top and low ones at the end. By swapping those two, you would invert your
order. Now we can loop through them. I am going to use the Any method here. That means it will stop looping when it returns
a true. So if the map has a tile at the given position
I remove it and return true. Else I will return false and the Any-method
will run this code for the next tilemap. Yep that's everything. Let's test it. As you can see, if I remove a wall the floor
below becomes visible again. And testing the feature-bug of holding the
single event I consider it as bug. Let's fix it. Inside the BuildCreator we can find the problem. When holding we call the HandleDrawing method
everytime during a update. This is fine for keeping the line and rectangle
preview up to date, but we also find the handling for the single place type here. Let's just move it inside the release handling. Now a single item only gets placed when releasing
after clicking, which is totally fine in my opinion and the bug is gone. Now the more-feature-than-bug click and hold
eraser won't work anymore. So let's change the place type to "Rectangle". Now this won't work like you would expect. It just deletes, but we want the preview to
be drawn and delete on releasing the click, right? If you take a look in the BuildingCreator
you can see the RectangleRenderer calls the DrawBounds method with the preview map. And what happens here? Yes, our DrawItem gets called and it will
be handled as … a tool. By comparing the map parameter with our variable
of the previewMap we make sure that the if statement only matches if we are not drawing
the preview currently. Testing it again, it will work as before but
for a rectangle. Note that this is not limited by the kind
of tilemaps, so it might remove floor and wall at the same time. But if you have managed it to this point,
expanding the Erase method is no magic :) I really tried to find some nice ideas for
other "Tools" and one might guess my almost 1000 hours in Rimworld could have helped,
but I leave it at that with the eraser. If you have some suggestions for the tools
you want to see coded, let me know in the comments or leave a message in my Discord
channel. Maybe there will be a "All The Tools - The
Absolute Special Episode" video one day - who knows? ;) And for all of you still being here, I will
offer you the priceless view of this octopus I painted. During sketching I was so focused on drawing
eight arms - you know "octo" - "eight" - that I only painted six. And I just realized it after coloring. So, this is my hexpus, hexopus Yeah, hexopus…
great. 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.