I worked on Project Sirius during my time with The Molasses Flood / CD Projekt Red.
I cannot share images/videos/details about the characters I worked on,
but I can discuss what I learned on the technical side.
My time at The Molasses Flood was such a dream; I am so thankful for the incredible amount of knowledge that I gained. Below are some details about my favorite systems that I worked on!
When I started at TMF, the life sim systems were very sparse: we had a simple daily scheduler, NPCs that switched activities at the same time robotically, and SmartObjects where AI could only ever stand. I became the sole owner of the system; my work involved adding animations and over-time effects to the SmartObject activities, adding dynamic insertion to the scheduler for activities like socializing, permitting player-driven events to interrupt the schedule, varying the timing of activity swapping, and much more. The designers and I agreed that the entire system needed a refactor, but proving functionality was more important than improving ease-of-use, so I continued adding features to the initial structure for several months.
When I finally refactored the life sim systems, I met with design and leads to establish new goals. 1) NPC state needed to be saved, tracked, and updated between levels, because previously that data was lost on actor destruction. 2) NPCs needed to dynamically choose the best available activity, and activity insertion needed to be simpler; there is no point in pre-scheduled activities if NPCS can come and go, stats can fluctuate, the player can make requests, etc. 3) The system needed to be easier for designers to use, so I nativized some functionality, and I attempted to organize and name exposed variables/functions very clearly. Through clever use of actor handles, utility scoring, and data tables, the new system mostly achieved those goals; no system is ever perfect, but I am very proud of what I created for the team.
Balancing the frequency and power of enemy attacks requires fine tuning; designers need the freedom to swiftly update attacks and iterate on combat scenarios. Originally, our enemies selected different attacks through their behavior trees; rarer, more powerful attacks branched earlier than more common attacks, with cooldowns set up per branch. This solution was far from optimal; attacks had to be considered by AI individually rather than compared together, attack patterns were extremely repetitive and predictable, and adding or changing an attack could be tedious.
My first improvement was implementing a custom decorator for semi-random subtree injection; designers could add all the attacks to a central decorator, allowing pre-selection comparisons. After joining, our senior AI programmer suggested moving the same functionality off the behavior tree to a manager, which made designers' lives much easier! The manager tracked cooldowns, distance validity, etc. and updated the 'best' attack based on a mix of prioritization and random selection. The behavior tree simply queried the blackboard and executed the attack, if any.
Focus 'Heat' Dispersal
This was my first time working on combat for a multiplayer game! For a fair and fun multiplayer experience, enemies should distribute focus relatively equally amongst a group of targets. To accomplish this, target priority was modified by focus 'heat'; each enemy that was currently focused on that target added +1 to the score, causing their priority to lower. If all potential targets had equal priority + heat, the nearest target won the tie.
An issue that occurred with this system was rapid swapping in cases of uneven enemy numbers; If Enemy A & B are focused on Target 1, and Enemy C is the only one focused on Target 2, then Target 2 has lower focus heat. Enemy A or B will swap to Target 2, which creates the same imbalance again. The fix: Enemies subtract focus heat from their current target. This change translates to slightly favoring their current target over similarly-scored targets while still being able to correct heavy imbalances.
Engagement & Action Tokens
In my previous experience with combat AI, the enemies attacked constantly and simultaneously with ranged attacks to create a bullet hell. In a game with mostly melee attacks, spacing and differentiating the attacks is more important. I developed a two-tiered token manager to assist with that task.
An AI with a target became eligible for an engagement token, which allowed them the option of attacking. Each AI type required a tuneable amount of engagement tokens to be claimable in their target's bank; enemies could stalk or taunt their target until enough engagement tokens became available, at which point they could move closer and try to do damage.
An interesting note is that enemies do not release the engagement tokens until a higher priority enemy steals them, their target changes, or they have received a certain threshold of damage. In the first iteration, enemies released their tokens after successfully attacking; this caused enemies to jump in and out of combat too swiftly, and prevented fellow enemies from getting the heat off a friend who was under heavy fire.
Enemies with engagement tokens could request a specific-action token, if required for the action. The specific-action tokens allowed special moves to truly feel special/rare, even when fighting groups of the same AI type, and also prevented awkward-looking synchrony.
Custom Pathing Pattern Tool
Our custom pathing system generated points and scored them based on an opt-in list of tuneable factors, similar to EQS. I extended the existing system with new options, such as hysteresis time and friend proximity preference, but my more important work was making the system friendlier for designers. I began organizing our tuneables, adding tooltips, hiding unnecessary information; any small change that improved the tool's overall readability was a win.
PCG Map Zones
I generated faction-specific zones within larger maps using proprietary PCG systems and ensured that AI could query and respect the boundaries of these zones while pathing. This task was important to ensure that enemies couldn't follow the player across the whole map, but mostly I enjoyed this task because I got to play with the proc gen tools. I love to learn about new systems!
When I initially joined the project, I was the sole AI programmer; most of the AI work thus far had been done by our combat designer and our creative director. I was given a lot of responsibility and system ownership very quickly, but I never felt rushed or overwhelmed thanks to the support of my team. I could write pages about my experience, but I will narrow the list to my top five lessons.
1) Writing documentation and creating debugging tools is just as important as creating new systems and features. The whole team needs to know how to change your system and how to check for failures in your system. Ownership is responsibility to make a system that is understandable and useable for players in the game, but also for developers in the engine.
2) Remote work requires just as much, if not more, communication than in-person work!
3) Chunk significant changes into small Perforce pushes to free up files more often, to find bugs via diff more easily, and just for personal sanity.
4) Take notes while brainstorming and debugging. Notes will remind you how much you have touched and learned throughout the project. You can showcase work and advocate for yourself easily!
5) For a more technical lesson - I learned how to balance blueprint and native code, and that giving most classes a native base is important for avoiding hard references. My personal rule of thumb is that blueprint should be used for code that designers may want to reference or tweak, and native should be used in most other cases, especially for complex code.