Here’s a little gif of something I was working on this past week, getting tile outlining working!
This is something a lot of strategy games do but I haven’t seen any really good breakdowns of how to do it, so I thought I’d write up a little bit here to help anyone looking to do something similar.
The general idea is that you grab all the tiles in the group you want to outline, and then check all the neighbors of those tiles. Amit of Red Blob Games has a great article on this method in general here. If a tile neighbor is not in your current group, you can safely say that that edge needs to be drawn. Once you have all the edges you need to draw, you have two options.
And your options here are really engine-specific, because you have essentially two options: draw all the segments individually, or try to draw them as one line only. In the case of Unity, which I’m using, I’m using the build in LineRenderer component to draw the lines. The LR has no ability to “skip and jump”, meaning that I essentially need to draw each segment in one stroke, as if it’s a pen on paper. This means I could draw each line segment as an individual line (easy, but HIGH render overhead), or try to draw them as a single continuous line (hard, but lower render overhead).
Because I knew the I could easily get into scenarios where a tile group may easy have 100+ edges, this meant the difference between spawning 100 individual LineRenderers or having like… 1. So, as tends to be the case, I chose the efficient, hard path!
Not only this, but because the LR needs to draw a continuous path, the edges themselves basically need to be sorted before you actually draw the line. Attempting to just draw all the segments without thinking of sorting gives you this beauty:
This again is because Unity’s LR draws like a pen, where you have to draw as if you never “pick the pen up”. People who have used Turtles in Python can understand this as well.
So to sort the segments, I end up basically looping through all the identified edges, and, for each edge, try to find the next segment who’s start point is the end point of my current segment. Assuming I find that point, I then add that relationship to a new List (in C#), and then find the next point, and so on. And for the most part, this works!
But! There’s some more complicated logic when dealing with more complex scenarios like this:
Notice here how there are sort of “orphan” blocks in the middle that aren’t connected to the “main” outline. These are created as part of the same loop! So why aren’t they connected?
What happens is that, as I’m looping over edges, I’m also storing points that have been “processed”. If I ever get to a point where my next point I chose was already “processed”, I can assume (because the line is always a continuous loop), that my current line is finished. But! If my total processed edges is LESS than my total input edges, I know I have points left over that need to be stitched! So in that case, I make a new group, and then build the line using the same algorithm, and keep going until I’ve process all edges!
Then, for each group that I return, I spawn a line renderer (meaning some groups do still get >1 line renderers, but we aren’t talking 100s), and trace the group’s points, finding each next edge’s key (or start point) using the previous edge’s Value (end point). And it works!
One interesting thing here as well was some weirdness around Unity’s LineRenderer when it came to simplifiying a line - basically if you actually drew the whole loop and tried to Simplify() the line (necessary for sharp edges), it would cut a diagonal line across the last tile, like this:
So instead I skip drawing the last segment, turn on the Loop flag of the LineRenderer component, and it works?
Not totally sure why this “fix” fixes it, but everything all works now!