Specialization Toys or Sus Loa of Death
Project 7
Toys or Sus

FPP Controller

For this project, the first thing I did was to create a first person controller, to act as the main gameplay interface for the player. It used raw input data in combination with constant variables for acceleration, max linear velocity, max angular velocity, gravitational acceleration, etc. It then used the PhysX interface that Pu Ekstrand implemented to translate the math to in-game movement.

Since the game is an online multiplayer game, data for dead reckoning is then sent to the server to be broadcast. Each client controls their own movement, and the host is responsible for the AI as well. The clients then receive the movement data for the other players or AI, and move/animate them accordingly.

AI and Gameplay

Like in Loa of Death, animations were controlled by the Animation Controller System I wrote. But this time since the game is networked, we had to come up with a solution to be able to determine which animation to play when, based on data sent from the server. We achieved this by using the movement and dead reckoning data, and determining actions based on velocity, if the actor is grounded, and currently playing animations.

The AI in this game was built quite differently from the previous project, and this time used Behavior Trees, steering, and PhysX. I would have liked to expand more on their functionality, but because of time constraints I had to prioritise differently. This meant that the AI are always "Clean" and just go about their business doing minigames. They steer to the next point on the path created by the pathfinding they do, and it works nicely. I also implemented weight-blended steering for use in future AI.
fig 1.1
fig 1.11
fig 1.2
fig 1.22
fig 1.3
fig 1.33

Behavior Tree

For the main decision making of the AI, I wrote a general behavior tree implementation that does not use any inheritance, no heap allocations, and therefore no pointer chains. The nodes are stored in an array on the data segment, and the lookup time for a certain node is O(1), as the index of the node is always known through the node ID counter that assigns the node with an ID upon creation.

The nodes themselves contain three data variables, which use a dynamic type interface structure that can act as a variable of different types. You assign a value, a type, and a semantic to the variable, as well as a CONST flag and size of data type, and can then get and set the value with the type and semantic. This makes it so that I don't have to allocate any memory on the heap, and won't have to deal with loose pointers. Since all nodes are preallocated in the node array, the code is very cache friendly as well.

The Node structure also contains another structure called Child, which keeps track of the node's children indexes. It is not strictly necessary, but acts like a failsafe that tells you what you did wrong or if you can even add more children to a node. It also serves as to not make the Node class as bloated and more easily navigated.

Finally, the Node contains a variable "Type" and some IDs, one for itself, one for its parent, and one for which index its lambda has in the appropriate array. The Type variable is an enum which is either Composite, Decorator, or Leaf. This defines which array of lambdas should be indexed when executed with the node ID.

The behavior tree's execution flow is semi-recursive, so the root node calls on its only child's Execute() function, which calls on a function in the appropriate function array, which, depending on node type, does its logic and then calls Execute() on its respective child/children, and so on.

A node returns either Failure, Success, or Running, as is customary for behavior trees.

The first nodes I created were some staples which always should be part of any behavior tree, such as Sequence, Selector, Failer, Succeeder, Inverter. Then I added some for the gameplay specifically, such as "move to position", "wait", and "get new target".

Sequence and Move to position seen on the right, with an implementation example from the AI controller in the game.

Over all I am very pleased with how this turned out, and I am in retrospect happy with the restrictions I put on myself, with no inheritance or heap allocations. They made me think twice about structure, and finding ways around them was a fun challenge.
fig 1.4
fig 1.44
fig 1.5
fig 1.55
fig 1.6
fig 1.66

Navmesh creation

Finally, I wrote a tool for creating and editing navmeshes in our editor. It features automatic generation of vertices and triangles, but it's completely naïve, and every mesh only creates triangles for itself. That's why I added "Combine close vertices" and "Add/remove triangle" so that putting it all together would be a simple matter. I also made sure that one could add vertices on the fly without having to rebuild it all.

This was very convenient, as one could test a navmesh in-game, and then easily edit it in the editor, export, and reload the level in game, and test it that way. The tool exports the navmesh data to a binary file that is loaded in the game when the level is loaded in.

To make this tool even more handy, I could have added functionality for adding vertices for a single or multiple meshes to the pool without touching the already existing, but for the scope of this project it was not necessary, so I prioritised other more pressing matters.