Wednesday, December 12, 2018

battleMETAL - Why that was a bad idea, a terrible idea - Part 5 - AI deux

Ah, that army...well played.

    Now that we have some context about how the Quake AI code works, we can look into why it was a no-go for battleMETAL. The first hiccup is how battleMETAL builds its AI entities. Like the player, AI entities are built using the same factory pattern code. The entity is actually a bunch of entities strung together - things like torsos, arms….legs. battleMETAL units are not animated as a complete distinct model. Because they are collection of parts, each part has its own animation. This breaks the original Quake C architecture and its reliance on the frame macros (as was explained in the last post).


    The unit piece with the most animation was the leg entity for mechs. However, the animations for the legs are not authoritative to other entity states. Therefore most code hooked up to leg frame animations is not decisive, but rather cosmetic; reacting to game events as they happen. Just having this one attached entity threw off the need for using Quake frame macros. A different solution was needed, and that was using a state machine. State machines are abstract computer designs that describe a series of ‘states’ for an arbitrary object. An AI object is a good candidate for state machines, where the state in the state machine could be a set of behavior or actions that the AI performs.


    Mind you, this was after a few other attempts to split the difference with Quake’s original AI implementation. The original code also had a set of generic AI behaviors that most monsters would invoke in about the same sequence. There existed functions like ai_walk, ai_run and so on, these were called on a per-frame basis and packed into a frame macro for the monsters. Additionally, other functions were used by the AI during gameplay to assist in calculating attacks, spotting the player, and moving around defined node entities.


    All of this existing functionality was put off to the side, to be reworked into the new AI design. Using a state machine, I broke down almost all the possible actions that an AI could take into 2 categories - states, and actions. I then declared a state to be a collection of actions. I credit my cousin Eamonn with the idea of making actions were atomic as possible ie each action as a function call would only do 1 specific thing. For example, the walk state would be the following actions:
    Scan for enemy
    Move to patrol node

 
And that would be just the walk state. If the AI spotted a valid target in Scan For Enemy, the the AI would transition to the Run state. The code marks each state with an integer, 0 = Walk, 1 = Run. Each entity then has a series of member function pointers to each state like mech.st_walk(). When the AI is created, the code tells the AI which version of of st_walk to use. In the case of mech units, they’d be assigned the mech_walk() function. 


    Then, the AI keeps track of which state it is in by having a variable called .attack_state, when the AI executes its code every frame it looks up which function to run by using its state variable. Once this paradigm was implemented, I took the idea to the next step - organizing collections of states into ‘behaviors’ or ‘unit types’. Tanks and Mechs would clearly need their own sets of state functions because of how different these units are. Using this approach I found it was easy to implement even more unit types like turrets and non-combat vehicles. Each behavior set, although using the same list of actions has their own logical flow for states, and changing the state they are on. The result is a fairly flexible and open-ended design for adding AI and its behavior.


    There are still some limits due to the foundation of this code being on Quake. A big one is AI movement. The Quake C uses an engine function to actually execute the movement of bots and no physics operations are carried out, this results in stiff movement of bots that doesn’t really look natural. This also results in bots running more slowly during gameplay, their per-frame updates are less because of how proportionally expensive it is for the game engine to move bots. In the end I’m ok with the outcome of the AI, despite its limitations I was able to add a few diverse unit types to the game. This diversity helped make the game more fun and engaging to play.


No comments:

Post a Comment