Netherguild and the Imprecise Hovering
-Or, "how I fixed hovering over tiles without using colliders." A technical tale from my last update about stubbornness and creative solutions!
Netherguild is the turn based strategy game I've been solo developing for the last year and a half. As you can see, it usually sports an isometric camera-
If you look at the cave floor, you'll notice it's "3D"- it has small, perlin-noise based hills and ditches-
(I talk more about the inspiration behind it in this article: Indiedb.com)
And, since the beginning of my development of Netherguild, I vowed to never use Unity's physics system-be it colliders, rigidbodys, everything- Why? I thought it'd be a fun challenge to try and abstract everything into tiles and only simulate physics using animations or particle effects, also I figured not using this mostly-unnecessary feature will improve performance and reduce the final file size (due to Unity not adding un-included features into the build. I'll be frank here and say I don't know if it would also do that with physics, but I hope so).
All of that culminated into one big problem when it comes to raycasting and letting players precisely select the tile they wish to select-
With an absence of colliders, I defaulted into raycasting on a plane. And while that is a great solution in the perfect, clean world of 2D- which the early prototype did use- it really didn't fit the rugged, rough world of 3D tiles.
Some tiles are poking above the plane and some are below it-
As you can imagine, pretending the floor is uniform when it really isn't is bound to make you fall, and it did make navigation uncomfortable, imprecise and downright bad as soon as you were navigating a more steep area of Netherguild's randomly generated mine level (with bigger differences between tile heights).
Tiles that *should* have obstructed other tiles weren't getting picked up correctly- this method was just imprecise with 3D, and made hovering feel unresponsive.
And, during this update, when @Hallgrim pointed out to me that hovering was inaccurate (in addition to other people before him), I had to face my fears and solve this problem.
I mean, I can cast a ray, but how can I know where it should stop? How do I know which length it should be? Will I.... have to use colliders and physics, systems that are just overkill and unnecessary for my game?!?
My first direction of attack was considering "how can I make a more abstract version of colliders?"- I used this approach for checking mouse-hovering on buildings in the overworld for my game, pretty much just checking if the mouse is within their "fake rects"-
But, am I going to create a big tile manager script that checks if a tile is hiding another tile? how the hell would that even work and be as precise as it should be?
No... There must be a better way!
And then, I figured out a new approach-
In Netherguild, what decides a tile's height is a "Heightmap"- currently, it's just perlin noise with an added dictionary for exceptions, like the elevator. Somewhere along the generation process, once a tile is spawned/instantiated it's assigned a tile and height according to this class.
So... why not cast a ray from the camera in the direction of the mouse on the screen, and proceed along that ray until we're below the tile? As soon as we're below a tile, that's probably the tile we're hovering over!
But uhh... if I want this to be precise, this is going to be a lot of segments and checks. For example, if I check for every 0.1 units of the tile, in an average distance of 10 it'd usually mean a hundred checks. Per frame. And what if in future maps/levels there'd be even more distance between the camera and the floor? am I going to check 500 times per frame? Hell naw-
There must be a better way!
Instead of checking for 100 times per frame, which just feels wrong (even if it'll work with modern CPUs) why not get as much precision if not more with 11 checks?
And it works a bit like this:
I start with a certain length of the ray (10 units). Every time I check along the ray, if the point is above the tile, move the ray forward. If it's below the tile, move the ray upwards. Then, I divide the check length by two (5 units, 2.5 units, 1.25 units).
Doing this ends up giving me a lot of precision when it comes to the exact length of the ray.
Oh no. When I hover "between tiles", at times the hover would flip out and quickly toggle between two tiles. If earlier the it felt unresponsive, now it feels hyper-responsive, and not in a good way.
Luckily, this is an easy fix. And! We get to abstract tiles, in a way.
All I ended up doing, is taking the final "mouse position", and checking for how far away from the center of the tile it is. All there is to it is rounding the X and Y coordinates and subtracting to check the vector magnitude. If it's too far away from the center, the imaginary "collider" isn't hit.
There we go! Works like a charm.
If you enjoyed this weird technical article, why not join Netherguild's discord server and say hello?
Thanks for your time and hope you have a great day (: