Update 19: June 27th – July 11th
Another two weeks, another progress post.
On the personal side of things, I finished Shadow of the Colossus and Ico, the former of which I’ve been replaying before I get to The Last Guardian. I knew they were good, but I was caught off guard by how good they were and how well they hold up. Ico especially, as both the incredible use of space and gorgeous architecture left me impressed moreso – I think – than the spectacle of the slaying colossi in the other game. After finishing them, I read some interviews and such from Ueda (the studio’s director) and some critics on his games, and I’ve been rather sucked in by the minimalist style and design philosophy that the games do so well. Overall it got me thinking a lot about my plans and priorities as well as what makes elegant game design, something I feel inspired into refining myself as things go forward.
They also made me really appreciate inverse kinematics and finer attention to kin-aesthetics, something I’d like to really experiment with soon.
Development-wise, I’ve ended up refactoring almost all of the Bird AI – something that doesn’t really feel like progress, but it is – and built some more on top of it to kind of finish off the flying part of their behaviors. Still some spitshine to do there, but I’m ready to move on I think.
This blog goes well into depth about what I’ve written, so avoid it if maybe you’re a casual viewer or at all interested in avoiding spoilers about how they work.
Travelling and Elevation
I refined last blog’s soaring system and intend to actually give an explanation this time.
For starters, the birds can hold information about what node to circle – a node containing a (rather arbitrary) radius length and some max/warning/min heights for the Ave Apex to refer to when needed. When in the travelling state, the bird will access the node, create a vector between the two, then find the cross product * the node radius to get a point at a right angle – a point it will rotate towards at a certain rotational rate and be pushed towards at it’s current flight speed.
Since the point is always at a constant right angle away, it will ease into and lock at a circling distance around the node. Given a new node from some level script, the bird might then target a new node, which it will easily target and ease into much the same.
All the while, the bird is slowing down and lowering in elevation due to gravity. I’ve got a simple script that detects when this happens and has the bird gain some elevation every now and then when it dips below the node’s minimum point.
The bird could engage in more interesting flight patterns with a more interesting use of nodes. If, say, I created a new type of node with a lower radius and proximity trigger to the nextNode – and if the nodes were arranged correctly – they could do figure 8’s or complicated patrols. The node system prevents a lot of hassle with math and makes it easy (as a level designer) to set up a path and have them do it.
Line of Sight and Targeting
To preface the LOS system, I’ll say that the LOS trigger itself and it’s interaction with the AI_Controller abstract class is generic and implementable across any given implementation, but that the PreyObserver that instantiates them is built specifically for the birds and won’t necessarily see use among more individualistic AI types.
The “Prey” system utilizes two kinds of entities; a multitude of PreyInfo classes from the level – each attached to an object the designer deems as “prey” – and the single PreyObserver class (effectively a singleton, but not necessarily), which is fed all relevant prey at the beginning and interfaced through the Ave Apex AI Controllers at runtime.
Each PreyInfo simply holds information received from it’s controller when relevant, such as whether the prey is dead, how much a food value it has, how many predators are active on it, and whether it is “hidden” (a state it detects itself when entering or exiting certain triggers). The Observer keeps track of the hidden and visible prey, and when accessed – after first checking the prey isn’t already detected or exceeding in predator count – the observer checks if the the given Ave Apex has LOS on them, from which it will then instantiate an LOSDetection object.
Having an LOS timer on an object seemed strange to me at first, but without making some unusual arrangement of tuples, custom classes, or some other needlessly abstracted data structure, I simply spawn an object on the bird’s eye that keeps checking LOS on the target and adding/decreasing a timer until the prey is fully detected or forgotten. It’s cleaner and more separate to code, it’s generic, and it updates itself so that code in the bird doesn’t have to. I add whatever the bird has LOS on to a “detecting” array first, deleting it and destroying the LOSDetection object if it fails but adding it then to a “known” array if it does it work.
Swooping and Diving
Once the bird has detected it’s prey, it updates the “known” prey every now and then to determine it’s current target; now simply by what has the highest ‘food value’, but easily expandable in the future. When the bird has a chosen target, it instantiates a “landing array”.
This landing array consists of n number of nodes from the landing point to the furthest out for landing and a ‘target’ point and an ‘exit’ point used for swooping. These are simply Transform objects parented to an Empty object that has the LandingArray class – an item that holds these Transforms in lists and has methods for checking if the path between them all is unobstructed and has enough space around them.
When the bird detects it’s target and spawns one of these, it continues to circle while the array follows the position of the target (on the nearest bird navmesh point) and rotates to look at the bird. When the landing array is valid – unobstructed and clear enough for the bird to fit – the bird changes it’s node target to the Xn node and accelerates in a dive.
When the predator reaches Xn – the decision point – it will stop updating the landing array position and now check whether the ‘target’ and ‘exit’ nodes are unobstructed, meaning it can follow them, thus swooping down, playing an animation, and dealing damage before swooping back up into it’s circling pattern. If they are obstructed or (more simply) the prey is currently hidden, the bird folows the landing nodes and increases it’s drag, beats its wings, and transitions to walking and seeking mode with another animation when it touches down.
Odds and Ends
Also these weeks, I created and worked up some hitboxes and an inherited health system for the bird. The ragdolls were a little difficult to set up, given that it isn’t a human, but it gives a somewhat convincing corpse upon dying that could use some polish later on. The more important thing is that it can take damage and die – and that it was easy to implement just by inheriting from earlier work.
Besides all the mentioned above, I also did some minor script optimization, backed up my work in like 4 different places, experimented with the lighting a little, and cleaned up all 30-some warnings that Unity has been yelling at me about since the 2018.1 upgrade (the log is clear!). I’ll probably just skip the 2018.2 update and hop on 2018.3 which has some more useful stuff like nested prefabs. From then on I’ll just resist any further major updates into 2019 an onwards but who knows, I might try.
I knew it was rash to say the Ave Apex AI would be done by the end of July and maybe even earlier, but I’d say that confidently now were it not for vacation. Vacation! What was that again, anyway? That thing I get twice a year now at the ripe young age of 20 when I should be out having fun more instead of game development. Snark aside, I’m going to the Great Colorado Sand Dunes which I’ve always wanted to see since I read the Dune books some time last year (and I’m rereading now in anticipation). Oh and I’ll be moving in to a new apartment for the school year, so goodbye another weekend.
So what do I plan to actually do for update? Well I’ll be working on the ground AI, meaning the seeking and attacking part of it that hunts the player down on the ground, but I won’t be doing any work on that blissful week of vacation. After next blog, I’ll spend some time finishing things up and polishing the birds up, then experimenting with IK before school starts. From there I’m real excited after that to scope out what’s next!
Next update will be August 1st, which by then I’ll have done a regular amount of work but it’ll have been three weeks instead of the normal two. This means only one dev blog in July but that’ll spill over into 3 in August, so no big deal.
Until August, cheers!
One reply on “Atavism Dev Log 19 – In-Depth Bird AI”
[…] I figured they’d just spin around an array of points until they found a good path (like the landing array in the swooping section of this blog) but when I sat down to map out what they’d do, I […]