turn-based strategy - Pc - self-published - steam - custom engine - C#
Project Key Details:
 - Status: Launched in 2019 on Steam
 - Game Engine: Custom
 - Languages: C#, HLSL
 - Frameworks: XNA, Monogame, SlimDX, SDL2, DirectX
 - Tools Used: Visual Studio
 - Crowdfunding
 - Game Design
 - Game Architecture
 - Graphics Programming
 - AI Programming
 - File I/O and Serialisation
 - Performance Optimisation
 - Narrative Design
 - Procedural Generation
 - Gameplay Programming
 - UI Development
 - Maths - Matrices, Quaternions
 - Multithreading
 - Research & Development
Overview:
Predestination is a turn-based space 4X strategy game designed and developed by Brain and Nerd and was the first Irish game to be successful on crowdfunding platform Kickstarter in 2012. It went on to be approved through Steam's Greenlight system, released through Steam's Early Access programme in 2015 and was fully launched in 2019.
I was the solo programmer on Predestination (aside from a brief stint by a placement student) and created a custom game engine for it using C# and HLSL. I created new graphics technologies and techniques as part of the project, wrote engine-level graphics code dealing with vertex and index buffers, and created custom shaders in HLSL. I implemented all of the gameplay, built extensive AI systems, and learned foundational skills in engine-level performance optimisation, quaternion maths, and game design.
This was the project that kickstarted Brain and Nerd and led us to secure funding for new projects and grow as a studio with new staff. It's also where I got my start running a company, creating financial projections, and self-publishing.
Crowdfunding
Predestination was originally borne out of graphics technology experiments I began after University, including a procedural planet generator and galaxy skybox creator. When we started building Predestination, the Northern Irish games industry was tiny and there was no funding available, so we took to Kickstarter to crowdfund it.
Predestination became the first Irish video game to be successful on Kickstarter, and we went on to launch a second successful crowdfunding campaign to expand the game with a new 3D ship designer technology. In total it raised around $70,000 USD in seed funding. I went on to consult with other companies on their crowdfunding campaigns, and helped them generate similar successes.
Game Design
The core team for most of Predestination was just three to four people, and we used a collaborative game design process around a whiteboard to help highlight problems early and brainstorm solutions. Most of the features in Predestination started life on a whiteboard, and most were re-designed and iterated on throughout the project.
At this early stage in my career, I didn't have the experience to effectively manage scope. I learned some harsh realities about feature creep and assessing feasibility and scope throughout the project, which went over our original time estimates by several years and was never completely finished to the standard we wanted. This project also taught me the importance of having specialised game design and production departments in a studio, something Brain and Nerd fixed in later projects.
Game Architecture
I built a custom game engine for Predestination using the XNA 3.1 framework, as the project emerged out of several graphics tech prototypes I'd built in XNA in my spare time. It was later converted over to the Monogame framework in order to add 64-bit support and higher memory address limits.
 - Game Loop: I used a screen-based approach in which each aspect of the game (Galaxy, Planet, FleetCombat, Shipyard, Diplomacy etc) was split into its own separate Screen managed by a ScreenManager. I implemented a double-buffering system using a global render target, and the entire game runs in a render loop rounded down to 60FPS, 30FPS, or 15FPS, with input handling and update logic separated from draw code.
  - Design Patterns: I used a singleton design pattern for the screens, allowing each screen to be directly accessed when required. This was a time-saving approach that ended up introducing some serious bugs as one screen could edit the state of other screens. I ended up overhauling this by ensuring that screens only read from each other where possible and adding locks to data where code that accessed it was threaded.
 - Game State: The current game state is represented by a Level class, which includes all the races, fleets, and ships in the game. It also contains a Galaxy, which contains a list of Stars, which each contain Planets, which each contain Cities. This full data structure was kept serialisable and used for loading and saving games.
 - End Turn Hierarchy: The Level class contains an EndTurn method that performs global turn functions and then calls EndTurn methods on every Race, Star, Planet, Fleet, and Ship in the game. These EndTurn methods are called on separate threads to speed up execution and various UI elements such as the Next Turn button are disabled while the end turn process is running.
Graphics Programming
Graphics Programming - Planets
My area of interest when starting Predestination was graphics programming, and the project emerged out of several graphics tech prototypes I'd built in my spare time, including a procedural planet generator and terrain engine:
 - Planet Renderer: A procedurally generated planet system that creates a heightmap with continents and oceans by combining a series of processed heightmaps from NASA's STRM dataset. The planets had a variety of environments, textures, and atmospheres, and water was rendered based on extinction depth of light rather than being transparent.
 - Hexagonal Planet Grids: I developed an efficient technique to create a rotating planet that loops and can be represented as a square texture with a regular hex grid mapped onto it. This is not possible with a true sphere, our planets are actually rotating cylinders projected into pseudo-spheres and we hide that by locking the play area to exclude the planetary poles.
 - Terrain Rendering Optimisation: The entire planet is a rectangular terrain represented as a single square 2D texture of the heightmap and another of the environment map. To calculate the lighting correctly required knowing the height of several surrounding pixels, which would normally require several vertex texture fetch lookups. I managed to bring this down to a single lookup by encoding three neighbour pixel heights into the three unused channels in the heightmap.
Graphics Programming - Particles and Noise
The stars, nebulae on the galaxy map, and explosion effects in fleet combat were all created using a view-independent batched particle renderer I made. The background galaxy was also created with the same system, just not at runtime.
 - Particle Renderer: I created a system to generate the vertex and index buffers for a cloud of particles in batches of optimum sizes for GPU rendering. Rather than drawing them facing the camera, I made them view-independent by making each particle into a star shape using a basic cross-hatching technique. I used different colours and blend modes between batches to create everything from diffuse nebulae to explosions, and had colours change over time to create animation.
 - Galaxy Renderer: The background for Predestination was created using a separate Galaxy Renderer system I created using the same technique. It procedurally builds up a dense galactic background over time by drawing layers of stars, dust clouds, supernova remnants, and nebulae onto a large cubemap rendertarget set. I then exported these and included them with the game.
 - Noise Library: Created a library for generating 2D or 3D noise that tiles smoothly. At the time the only libraries available didn't generate tiling noise.
Graphics Programming - Pixel-perfect picking
The 3D ship designer in Predestination allows players to create their own fully custom ship designs by putting cosmetic components and functional components together. Rather than using a snapping point system common in other games, I developed an entirely new freeform placement technique with pixel-perfect picking that doesn't use any raycasting, collisions, or physics. I have yet to see this technique in any other game:
 - GPU Render Data Extraction: To place a ship piece, we need to know surface coordinates, relative world normal, which ship part it is, and some other data. I extract this data from the GPU by drawing the ship to a rendertarget using a special shader that encodes the data as a colour and then rasterising the data into an Vector3 array for picking.
I encoded the object space position of a pixel on a ship part relative to its parent as xyz coordinates in rbg space to produce a colour, and the same for the relative world normal. I also encoded a data layer containing things such as part ID number, scale, and special characteristics such as whether it's flipped or mirrored. This technique ensured that the data on whatever is under your mouse pointer was precisely accurate.
 - Benefits of the technique: I used the position, normal, and data of any given pixel to calculate the exact root position and orientation to place a 3D ship part in it, allowing pixel-perfect placement of ship parts. Since it's done in a pixel shader, players can also zoom in and get more precise placement. New data also only needs to be extracted as a single frame rendered any time the camera finishes moving rather than every frame, making it extremely efficient.
The benefit of this system was that it meant we didn't need to implement any physics or raycasting in the game engine and didn't need any collider systems. It also meant that our 3D models didn't have to be specially processed for use in the 3D ship designer, which was useful as we were working with volunteer 3D modellers and wanted to keep the game open to easy modding. 

Graphics Programming - Ships
I wanted Predestination to be highly moddable, so I insisted on loading model files and designs from flat files at runtime.
 - .X Loader: I built a custom .X model loader and processor using SlimDX. It loaded the raw vertex data and used reflection to stuff it into my internal model format. As part of the model loading process, it rescaled every model to within certain bounds used by the ship designer shader. This let me add models without worrying about them not working with the ship designer, and kept the resolution for the position shader as high as possible.
 - Design Tools: I implemented a suite of design tools into the 3D ship designer, allowing parts to be stretched, scaled, rotated, and moved independently or as part of a group, and mirrored along any axis. The resulting designs were saved in data files in a fully human-readable and editable format to encourage design sharing and tweaking.
Graphics Programming - Shaders
In addition to new graphics techniques described above, I also created custom shaders in HLSL for every aspect of the game. The galaxy map and star system window used a controlled bloom technique to give them higher dynamic range, and I implemented custom shaders for each race's ships and diplomacy screen.
 - Custom Ship Shaders: I designed a system by which custom shaders could be selected and modified at runtime and gave each race its own ship shader. This allowed me to add special effects such as glowing components or a particular shine to ship parts without editing the actual ship part. For example, for the United Colonies I extract the blue components of the ship part textures and give them a noise-based glow.
 - 2D Shaders: In addition to the 3D elements, I created screen-space pixel shaders to add holographic effects and special effects to story cutscenes and the diplomacy screen. Each race's diplomacy screen got a special effect that brought it to life, such as an underwater  distortion and lighting effect for the aquatic Z'loq or rising smoke for the robotic Starforged race. This was necessary as we had no budget for animation or additional 2D art.
AI Programming
AI Programming - Strategic AI
I designed and implemented a comprehensive and efficient turn-based AI for Predestination covering every aspect of gameplay. I naively took a zero-cheating stance to the AI and so spent considerable time creating a sophisticated AI that considers all legal moves just like a player would. While this did create a powerful and authentic AI, by the end of the project I had learned why games perform hidden tweaks for the AI to improve gameplay enjoyment.
 - Strategic Galaxy AI: The overarching strategic AI for the game. It first evaluates the state of the game by collecting statistics on each of the races that it knows about and comparing itself to them, then based on that it selects a goal such as Expand or Consolidate. It then creates lists of high and low priority moves, scores them according to various criteria, and then picks the highest scoring move. This became the paradigm I used for all of the game's AI systems.
 - Planet Colonisation AI: This was one of the most complex AIs in the game. It had to find good spots on a planet for cities, decide on a specialisation for each city, and create a queue of actions to build infrastructure and buildings. A sub-AI of this was also made to design well-balanced city blueprints as the AI gained new technologies, which I was then able to use as the default auto-updating blueprints for players.
AI Programming - Personalities and Cheating
Once I had a solid AI base to work from, I introduced personalities by adding weightings to certain actions. We tested the game extensively and learned that a strategy game AI has to be rubber-banded within certain limits to keep the game feeling challenging.
AI Personalities: Each AI gets assigned a random personality at the start of the game that influences its decisions, and each race has specialities that lend to one type of gameplay over another. I incorporated these by giving modifiers to the scores particular categories of action such as launching an attack or exploring space. I also gave each race a preferred weapon type, a preference for shields over armour, and an recklessness factor for fleet combat.
Rubber-banding: In testing the AI with players, I discovered that not everyone got the same feeling of difficulty from the same AI settings. Some people found that the AI trailed behind them throughout the entire game, while others found the AI got so far ahead as to be unbeatable. I ended up implementing soft rubber-banding, where the AI will cease taking actions such as expansion if it's too far ahead and will get free hidden bonuses such as purchasing free ships if it's too far behind. The end result felt more balanced and led to a better gameplay experience.
AI Programming - Tactical AI
When I started developing Predestination's AI, I didn't realise just how many aspects of the game that they AI would need to be able to play. Rather than faking it, I developed specific sub-AIs for each aspect of the game:
 - Fleet Combat AI: The Fleet Combat portion of the game has the most complex AI as it has to understand both the offensive and defensive situation. It creates a list of all valid move and attack combinations and for each it creates an offense score based on damage inflicted or better tactical position gained and a defensive score based on damage taken or threats it's moving into. I was able to use this to provide a good Auto option for players too.
 - Ship Design AI: A simple AI that designs ships based on the race's most advanced technology currently. It incorporates the race's personality in ratios of offensive to defensive technologies, and I was able to use this to create automatic ship designs for players.
 - Diplomacy AI: The AI takes turns proposing deals with the player and each other, and evaluates the deals you put to it. It has a diplomatic memory of deals that were one-sided or positive interactions and gifts, which it combines with a score based on the deal offered.
Performance Optimisation
Performance Optimisation - Dynamic Background Loading
With Predestination being built in a custom game engine, everything from shaders and draw code to end turn logic and managing files had to be written manually and support a wide range of low-end devices. I managed to reduce average memory usage by over 50% and load times by over 90% by implementing a dynamic background texture loader
 - Dynamic Background Texture Loading: All 3D models in Predestination were loaded as geometry only and started with no textures loaded. In order to allow the render thread to continue while loading, the model would default back to a shared 256x256 blank texture.
Upon becoming visible, the model would immediately start a background thread to load its textures and then seamlessly swap them out once loaded. The background loader starts with a 256x256 version of the texture to load it quickly, then it would load the full 1024x1024 texture.
Once the model was no longer visible, the texture would be disposed and go back to the shared version. This resulted in a brief and almost un-noticeable visual blip when entering a screen where the texture is low-resolution for a split second, but in exchange we no longer had to pre-load textures and the screen swap became instant.

Performance Optimisation - Rendering
Over time I implemented some significant rendering optimisations:
 - Render Layer System: We applied a destaturated blur effect behind many UI elements such as the dropdowns and panels, which became a performance hog. I solved this with a render layer system in which elements were divided into UnderUI, UI, and OverUI layers.
The entire UnderUI layer was rendered to a fullscreen rendertarget, then the UI area was masked out of it and the blur effect applied to this area, and finally the OverUI layer was drawn on top. The result was that the blur process only had to be done once, providing consistent load and ensuring the background behind the UI was always blurred.
 - UI visibility checks: There was a lot of render time wasted drawing offscreen or out-of-panel elements on scrollable panels. We implemented quick visibility checks on all UI elements and all 3D models on buttons, reducing render load by 50-90% on some screens.
 - Shared Rendertargets: The game used several full-screen rendertargets for effects such as UI blur or the 3D ship designer's pixel-perfect picking technique. I minimised the number of rendertargets needed by creating a shared pool that gets re-used throughout the game. This kept the rendertargets in video memory and cut video memory usage by 50%.
Performance Optimisation - Procedural Generation
We found that galaxy and planet generation times were sometimes inconsistent because certain combinations of variables had very few solutions that satisfied the gameplay requirements. In rare cases the game would generate a galaxy where players were cut off from everyone and couldn't progress. We solved this two ways:
  - Galaxy Generation: We overhauled galaxy generation into a phased approach, placing the homeworlds first in an evenly spread pattern roughly equidistant from the galaxy centre. Next the game generates two nearby star systems you can reach during the space exploration tech era, followed by more you can reach with further technology. Finally, the game randomly generates stars to fill in. This ensured perfectly playable games, reduced generation times by 70%, and made generation times consistent.
  - Planet Cache: Planet maps in Predestination were generated at runtime, causing a short delay the first time you open a planet. Although this delay was short, it was not always the same length and so it became noticeable. We solved it by pre-generating a series of 100 planets of each size and type and then serialising them out to a Planet Cache folder which we could then keep in the game files. This reduced planet load times by over 90% in exchange for adding some file size to the build.
Other Key Development
File I/O and Serialisation
In order to better support modding in Predestination, I wanted to ensure that as much as possible was read from and written to human-readable flat files. Since I created by own custom game engine, I handled loading and processing of files manually and had to use a few different techniques:
 - Flat files: The majority of the game data was loaded in from flat text files in the content directory in human readable formats. This included the data on weapons, buildings, the tech trees, ship captains, planet leaders, and procedural generation probability tables. Every image used in the game was also loaded at runtime and could be edited outside the game. Where images or models were referenced, they were referenced by relative path so that they could be edited also.
 - SlimDX Runtime: To maximise moddability, I needed to be able to load models at runtime. I chose to use the SlimDX library to load .X models at runtime, and then manually process their index and vertex buffers. At the time we were working with volunteer 3D modellers who would often make mistakes with a model's root position, rotation or scale. I wrote a processor which would load in data about the model such as scale or rotation or an offset, and adjust it accordingly.
 - Save Game Serialisation: The game state was represented by the Level class, which contained lists of Races, Fleets, Ships, and the Galaxy data structure containing Stars which contained Planets, which contained Cities, which contained Buildings. I ensured that all of these classes were serialisable and then used a binary data formatter for the saved game files. Upon loading a saved game, a Regenerate method was called to re-initialise everything in the game, re-build the level state using the loaded data, re-load data from files, and re-link up things that required references to each other.
 - Save Game Versioning: When I updated the game, I would update a lastCompatibleSaveVersion variable to indicate whether old save files were still compatible. I was able to allow old save files to be loaded even if the new Level format contained new information, as long as those variables were serialised last. Simply placing new variables at the bottom of a class was enough to allow old save game files to continue functioning across many versions. When I had to make a more extensive change, I would update the last compatible version number and the game would refuse to load old saves.
Narrative Development
The core storyline of Predestination begins in the year 2472, when a temporal disaster throws dozens of ships back in time and scatters them throughout the galaxy. Ships belonging to different empires crash-land on planets and begin rebuilding their civilisations.
I developed this storyline initially to explain why every race starts on equal footing in a 4X game and discovers space travel at the same time, and because I like the idea of the storyline being a Predestination paradox. The game featured a replayable sandbox mode, one challenge map, and a story-based singleplayer campaign:
 - Mission Format: The game's singleplayer missions were all loaded from flat text and image files. Each mission included a specific galaxy setup and seed, a unique set of planet leaders and ship captains, unique buildings and technologies, a custom modified tech tree, and custom ship designs. We released 5 episodic missions and one challenge mission, and had intended to release further content but the game was not a commercial success. The format was designed so that we could add new content without ever revisiting the code, and so that people could mod the game to add their own missions.
 - Cutscenes and Narrative Delivery: Each mission comes with a start sequence story explaining the setup for the scenario, and a series of conversations and cutscenes triggered at certain points along the story. There are also choices that can be made in many of the missions, leading a branching narrative that has real in-game consequences.
The player progresses to the next phase of the story by completing particular objectives, such as owning more than 3 planets or researching a certain technology.The game used reflection to read variables directly from the game state, with most of them contained in a special GameStatistics class.
 - Core Storyline: The standard sandbox game mode of Predestination can end in one of several different endings depending on how the player does. There are six different ending sequences, one for galactic domination, one of two different diplomatic victories, two different endings with destroying the Revenants, and a final one for reaching a technology victory. There are also custom loss conversations for being destroyed, deposed as leader of your empire, or allowing your economy to collapse.
UI Development
 - UI System: Since Predestination was built on a custom game engine, I had to implement even basic features such as drawing UI elements, clicking buttons, and mouseover effects on elements from scratch. The entire UI was created directly in code with no external editor, so I implemented a modular panel system with scrollbars and render masking.
 - Animation: One of the core principles behind the UI was that everything had to have a smooth animation or transition along with a sound effect. I implemented a zoom animations for opening the star system window, smooth transitions for sliding panels in and out of the screen, and a fade system for switching between screens.
 - Tooltips: The single biggest usability improvement to the game came from implementing a universal tooltip system. This let me attach a piece of tooltip text to a specific UI element, and I implemented special tooltips for specific parts of the game such as planet info.
 - Tutorial System: I created a tutorial system that created chains of tutorial panels that point to individual elements on the screen with arrows. Tutorial stages can block you from clicking anywhere but on certain UI elements to continue, and some will trigger when you complete a certain task. I used reflection to retrieve the variables to look at for evaluating tutorial task completion, allowing me to load the tutorial from human-readable text files. This let me make rapid edits to the tutorial without recompiling the entire game.
Maths
 - Quaternions: I learned a lot about using Quaternions while developing the core engine tech for Predestination, especially for the 3D ship designer code. I implemented common features such as swing-twist decomposition, rotation between two vectors, rotations around arbitrary axes, etc. I frequently find myself returning to the Predestination codebase to pull out an old quaternion solution.
 - Matrices: The vertex and pixel shader code for Predestination involved setting up and using the view, world, and projection matrices. Although I only had a basic understanding of matrix maths going into the project, I was able to successfully implement all the matrix operations needed and manually create matrices from quaternions for certain tasks.
 - Vectors: The engine code in Predestination involved extensive manipulation of 3D vectors for position and direction, including building meshes directly from vertex buffers and vertex shader code. I implemented generalised vertex methods for projecting one vector on another, rotating a vector around a point on an arbitrary axis, and simple ray plane intersection.
Back to Top