No More Monkey Business

 

Project Info

Language: C++
Engine: Unreal 4.27-chaos
Team size: 14
Time frame: 7 weeks
My role: Gameplay & Audio programming
GitHub: Click here
Itch.io: Click here

Project summary

What if humans never evolved and monkeys and apes were the dominant species? What if said monkeys did business?

This project had the theme “What if” and my team leaned into the tagline above and created a lightning-fast physics-based brawler game. In No More Monkey Business, the player controls Latte, a long-time employee at the titular Monkey Business. Passed over for a long-overdue promotion for the boss’s nephew, Latte flies into a rage and decides to take revenge on the corporation which scorned her by absolutely demolishing their offices.

We were inspired by games like Going Under, Hades and Ape Out for this project, and wanted to weave fast-paced destruction together with environmental storytelling. The player navigates a series of offices, destroying furniture, office supplies and important documents while avoiding (or attacking) the company’s security guards, who come after the player with tranquilizer darts.

FMOD Integration in Unreal

For this project, I had the pleasure of working with Erik Larsson again, a sound designer with whom I worked closely on another project at FutureGames. We wanted to use FMOD for audio again, so this time I had to tackle integrating it with Unreal. At first, it wasn’t too different from using FMOD with Unity, but after a while I ran into a team-blocking issue. Our Perforce setup required programmers to push new binaries whenever a header file was changed so that artists and designers could get the latest version of the project without building it in an IDE. Unfortunately, after setting up the FMOD integration, our non-programmers started getting complaints from FMOD about missing modules, and our programmers were unable to run the project solution in their IDE because of missing binaries.

My (admittedly hacky) workaround was to plug the FMOD-module-shaped hole in our .p4ignore and have the entire team use my binares, that were working fine. I uploaded the binaries that I had created to our OneDrive and the team copied them all manually. After that, the .p4ignore made sure that the wrong FMOD modules would not be pushed by anyone again. This felt like an extremely shaky workaround at the time, but it was stable in the long run even though it required manual setup by all team members. I even shared the same solution with two other game teams using FMOD and Unreal.

Even though this blocked our version control for more than a day, I feel like the integration was worth it in the end. After the complicated setup process, Erik’s audio work was made easy. He could just plug in FMOD events into our blueprints and his workflow was pretty much the same as it had been in our last project. So by investing some time and energy up front, we ensured that our audio would be implemented smoothly and quickly.

Eight Directional Movement

After doing the audio engine integration on this project, I moved on to gameplay-related tasks. Together with Martin Ohlsson and William Sundin, two of our other programmers, I started tackling our character controller. First we had to solve movement. Our designers had requested an “arcade-y” feel to the player’s movement, and wanted us to go from smooth 360 degrees of freedom to eight-directional movement. This would obviously result in some limitations on our player’s attacks like not being able to aim thrown objects freely, but the tradeoff was a more coherent game feel.

William and I took some first steps to restricting the movement by interpreting gamepad stick input as a value between -1 and 1 and then snapping the player character’s rotation to eight predetermined values. This solution was unreliable though, and could be overridden by our attacks, which would still take 360 degree input.

However, when we discovered Unreal’s FVector::ToOrientationRotator() function, we could create a more flexible system based on the right and forward vectors taken from the player input.

Attacks

Together with Martin, I implemented and tested our two attacks (punch and lunge) as well as our player’s throwing ability. We wanted the player to be able to deal damage to the security guard enemies by punching them, lunge at them or throwing objects at them. At the same time, the attacks all had to be connected with our physics system so that they would destroy our Breakables - desks, cabinets, tables, chairs and other such assets.

It took some time to wrap my head around working with Blueprints (this happened in parallel with the movement work). Thankfully, I was helped a lot by Dave van Egdom, our technical designer, who had lots of experience working with Unreal Engine Blueprints.

Our attacks all consisted of two main components: the logic for dealing damage to enemies and Breakables (contained within an AbilityComponent) and the animations. The punch would deal damage in a cone in front of the player by using a sweep, and the lunge would first do a line trace forward, and if no walls were in the way, it would move the player forward, then get a collection of nearby Actors to affect as a parameter when it was called, resulting in an area of effect ability.

After some issues trying to connect the animations with the logic, we decided to split up the attack logic into three parts: Try, Notify and Do. Whenever an attack was executed, first the Try part of the code would be called. Here, we checked if any other abilities or animations were currently being run. If the Try statement was passed, we would let the related animation play and use an Animation Notify to trigger the logic.

Animation Notifies were a nifty tool we found to be able to trigger an event exactly at the right frame in an animation. This was critical for our attack animations to be timed correctly. When the notify was reached, we would reach the Do part of the logic and execute the sweep and damage code. This Try-Notify-Do setup meant the player could spam input as much as they wanted, but we still controlled the cooldowns on the attacks.

Interacting with and Throwing Stuff

Another important ability our player had to be able to interact with the world of the game was picking up objects and throwing them. For this ability, we relied on the same Try-Notify-Do structure Martin and I had created for the attacks, and created an InteractComponent which took care of all the logic interactable objects required. This component contained functions for finding interactables, trying to pick up or throw an object, dropping objects that were in the player’s hands if a non-throwing action was triggered, as well as the picking up and throwing.

The most complex part of the script was the DoThrow. I would add a force to the thrown object, detach it from the player, re-enable its collision and reset its rotation to make sure it would behave as a proper physics object after it left the player. However, when I finally got the throw to work, the most persistent bug in the entire project appeared. Thrown objects would often simply stop in mid-air, like their physics had been disabled.

I began to debug this issue and while digging around in our code I could not find anything that indicated that we were disabling these objects somehow. We asked our code mentor, Emil Ström, and he helped us realize that the cause was the Chaos physics engine we were using. For some reason, the objects would get put to sleep midair. Finally - there had to be a solution. In code, I tried to force wake up the objects whenever they would fall asleep mid-air, but it didn’t work! Turns out the sleep/awake event in the 4.27-chaos version was still “todo” and could not be relied on. As a workaround, I tried waking up all physics objects at regular intervals in our Update function. Huge performance hit, completely unacceptable.

I was getting ready to give up and just ship with the bug or remove the throw altogether, but then someone in the team suggested trying the same solution in blueprints instead of code. It worked! I couldn’t believe it, finally the throwing was working as intended.

Takeaways

Chaos for Unreal Engine 4.27 caused us so many headaches in this project, so the biggest thing I learned has to be not to use bleeding edge experimental tech if at all avoidable. We had the choice to use UE 4 or UE 5, but opted for 4 because 5 was completely new and we wanted something stable. Little did we know that the exact physics features we would need were not stable in 4. My FMOD setup blocked the team for maybe a day and a half, and elicited (legitimate) complaints from the team while I was solving the issues, since they could not access version control.

Chaos, however, blocked us several times for days at a time, so it was by far a bigger time sink. My issue with the throwing ability came up in the middle of the project but took an inordinate amount of time to solve, and it was all because of a feature being experimental. Ironically, I think we could have saved ourselves a lot of time and energy if we had taken a chance and gone with the newer engine version.

I was also vindicated in my belief that setting up audio middleware early is worth it in the long run. Making life easy for our sound designer was worth blocking the team for a short amount of time, since he could rapidly implement sound events after FMOD was set up. This freed up a lot of time for him to do other design tasks, and since he happened to be the product owner in this project, I think that helped keep the scope realistic.

Using Animation Notify in the way that we did, by letting animation drive gameplay logic rather than the other way around was a risky move, but extremely useful for getting the system to work the way we wanted. It’s worth noting, though, that this is generally not a good idea - especially if the game in question is networked, this could present issues.