With the basics and the cubing and the naming all in place, I got back to designing the game itself. This started out, as similar to most forays into unknown territory, with some experimenting. The land was flat and lifeless except for the characters, so that was a clear first task to address. Let's stick some walls boxes in there. Great, looks much better already ... except that the automated characters have a preference for, let's say, the direct route. That is, as the crow flies, or rather as the crow's shadow moves. That is, as if nothing was in their way. Look, they kept walking straight into things, okay? Just banging their stupid little heads against anything in their way, over and over again. Programming as visual metaphor, there it is.
This wasn't an unexpected issue, it was clear that the beeline control mechanics I'd established at the outset were soon going to be overwhelmed. Actually, bees really don't move like that at all do they? If my characters actually did move as bees do, they would probably have been okay. Instead they moved according to an even more basic A to B vector. This was the natural point at which to delve into Unity's pathfinding system, a great rabbit hole unto itself. Even though there are numerous subtleties to getting this system to really work as desired in all cases, as I came to find out, actually just getting it working at all was not too painful. A so-called "nav-mesh" needs to be applied to the scene first and "baked" into it, whereby Unity finds the boundaries of any objects and, along with the prescribed size of characters that will adhere to it, establishes the areas that can and cannot be traversed. With a navigation intelligence component then applied to those characters, they can move from A to B even if a sea lies between them, providing there is a valid route around it. With the endpoint specified, a character will go about working out its best route all on its own, no further programmer intervention needed.
At this point the whole feel of the project shifted. Everything up to then had been purely dictated by yours truly, and as such it all seemed very mechanical. Even though everything was still really just as mechanical, I mean, the pathfinding procedure is still just crunching the numbers according to its own hidden rules of course, the fact of just specifying an endpoint and having a character take some non-linear path to that point just added a certain sense of realism. Is the character going to go left or right around that block? Are they going to turn at this junction or the next? We have no idea, until they go and do it.
As design progressed and I came to have multiple levels, each with different obstacles in them, it became necessary to look at ways of updating the nav-mesh at various times, otherwise it would remain as it was first baked. In other words, if we bake a nav-mesh for some level that has a box in the centre and nothing else, then when we switch to the next level occupying the same space, which has a box off to one side and nothing in the centre, then the nav-mesh will still be as according to the former level, and the characters that adhere to it will bash up against the box of the latter level as if they think they can go through it, and avoid the region in the centre as if there were something in the way.
There are a couple of methods to deal with this. The first is simply to manually bake the nav-mesh when something in the scene changes. That's the most straightforward and efficient way, but it puts responsibility on the programmer to make sure that all objects in the scene are catered for at all times. An automated alternative is to apply a "nav-mesh obstacle" component to each object in the scene, which causes the nav-mesh to adapt itself to such objects on-the-fly. This trades the ahead-of-time responsibility of the programmer, which is more error-prone but also more efficient, for just-in-time responsibility of the automated system, which is less error-prone but also less efficient, as the system is constantly on the watch for things that move.
Without noticing any big performance issues with my little game, at first I went with the latter brute-force approach, which sufficed for some time as I progressed and I thought the issue was solved. But as things started to get more complex, that automated approach didn't completely cut it, and I ended up with a hybrid approach. In particular, an object may be desired as an obstacle at ground level, but its top surface may also be desired as a navigable platform. Applying the nav-mesh obstacle component to such an object makes it completely unnavigable, both as something that can't be traversed either through or over. For such objects I completely removed the nav-mesh obstacle component, and did a manual bake when they first spawned in the level and also if and when they moved. For all other objects I used the nav-mesh obstacle component and let Unity handle them automatically. This approach seems to work for all cases I came up with, but ultimately each game will require its own attention, there's not really a one-size-fits-all solution, and I imagine this is especially true when performance is considered in larger games.
So that was one more challenge down, and an important one at that, which required a bit of digging and multiple iterative tests. Still, for all the artificial intelligence now in the game, it still didn't require much natural intelligence to play. In other words, I really needed to crack on with some level design. The stage was set, and all the pieces were ready, plus I was now used to Unity beyond the surface features, although still nowhere near its deepest depths I'm sure. Time, now, to take this working concept and turn it into a real game. Time for some real levels.