2016 - 2017 • Digital Arts and Entertainment Course
One of the main courses I teach in the Game Development track of the Digital Arts and Entertainment program is called AI/Gameplay Programming. The year the course was introduced, my colleague Thomas Goussaert and I created a small 2D framework from scratch. The framework was built to teach students the core techniques of AI for Games. One year later we've expanded and improved the framework. I was responsable for implementing the core modules of the framework as well as most of the AI topics, using SDL and Box2D for the PC version.
I am currently refactoring the framework to make it cross-platform (targeting PC, PlayStation 4 and Nintendo Switch). I am also focusing on optimizing using several techniques and guidelines (Memory Pools focusing on Data Oriented Programming, Template Specialization to mimic pure virtual interfaces at compile time, avoiding virtual functions, etc.). Last but not least, changing platforms should be automatic (users don't have to change project code) and all platform modules should be standalone (NDA purposes).
Below are code snippets of some of the most interesting parts of the framework. These snippets reflect some of the design decisions that have been made and the way the features are implemented in the framework. Feel free to analyze the entire github repository as well!
Multicast Delegate: this class accepts function pointers with different signatures and stores them in a container. Using variadic templates and a tuple (to store the passed parameters) I was able to implement a multicast delegate in C++, which can be invoked using the CallFunction() method.
Memory Pool: below you can find some functions from the memory pool implementation. The memory pool allocates a predefined blob of bytes and initializes it. When expanding is allowed, the pool doubles in size whenever there is not enough space. Users can flush the pool to start reusing it. At this point, the pool cannot deal with pointers to other pools (that are expandable). This would create invalid pointers whenever the child pool resizes. This can be fixed by using offset pointers instead of absolute pointers. Another current issue is that this pool can have problems with fragmentation because it is a stack based pool. Allowing defragmentation at fixed points could further improve this memorypool. Please see the github repository for the complete implementation.
A* Navigation Mesh Pathfinder: using custom polygon and geometric utility classes I was able to implement an A* pathfinding algorithm that supports 2D Navigation Meshes. The pathfinder creates a query at runtime, using perpendicular projection on adjacent edges, to determine it's graph. Using the traditional A* algorithm the shortest path to the goal on the graph is found. To optimize this graph based path I use the Simple Stupid Funnel algorithm to get rid of unnecessary points. The memory pool class is being used for the query to avoid dynamic allocations. No external libraries were used for this implementation.
Curiously Recurring Template (CRT) pattern: most classes in the framework take advantage of this CRT pattern. Using the CRT pattern we can mimic pure virtual functions at compile time, and also determine the correct platform module for the current setup. Because of this a working version of the entire framework can be shared, without providing other platform specific code. Once an user has access to a certain platform (PlayStation 4, Nintendo Switch, etc.), the only thing he/she needs is the platform specific implementation. Below you can find a small example of such an implementation. This SDLWindow class inherits from the EWindowBase template class. This EWindowBase class defines functions and members, but does not provide an implementation. It also has no virtual functions or platform specific members. So instantiating from this class would result in a compile error. The SDLWindow specializes itself to this EWindowBase (see the class definition) and it provides a platform specific implementation. In another file (stdafx.h) I typedef the platform specific type, using #ifdefs, to a general type which is then being used by the user on project level. Because of this, the user does not have to change his/her project code whenever he/she is switching platforms. We also have zero overhead when wrapping platform specific code!