top of page

Michael Ennis

Linkedin Logo.png

Games Programmer

ChemPunch

ChemPunch is a fast paced, wave defence action game with a focus on close quarters combat and high intensity gameplay. The main goal of ChemPunch is to defend your objective from waves of moth-infested enemies attempting to destroy it. To accomplish this, the player is provided two weapons; a chemical-infused gauntlet on their right arm and a chemical flamethrower on their left. The player can switch which chemicals are used by both arms through the left and right bumpers (controller), or Q and E (keyboard).

Depending on the arm used and the chemical selected for that weapon, different abilities will be triggered; sludge (pink) heals the player, frost (blue) slows down enemies and acid (green) makes them explode. The chemicals are limited, however; once the player runs out, they will have to acquire more from the puddles surrounding them. The objective moves to different areas of the map as the player holds the enemies back, unlocking various doors and expanding the combat zone.

I worked with seven other talented students to develop Chempunch for the Abertay games development competition, DARE Academy. This involved six teams competing for a grand prize pool of £5000 to support further development of our projects. The competition begins with a month of in person development over the summer, from July to August, referred to as "hot-housing". During this phase, we received personalized feedback from industry veterans, who provided invaluable mentorship and guidance. Development continued over the following months, concluding with a showcase of our completed games at EGX London 2024.

Scroll down for technical breakdown

image.png

Technical Features

Chemical Weapon System

One of the core systems I implemented is the chemical-weapon system. Each arm can have a different chemical assigned, allowing for chemical effects to trigger based on their actions. These effects vary slightly based on the arm used, allowing for unique combos for the player to discover. For example, the sludge effect immediately restores health to the player when enemies are punched, while the flamethrower returns health over time. 

The chemical system utilizes two enums bound within a map to determine which chemical is assigned, named WeaponChemicalMapping. The key for the map is the EWeaponType enum, (right or left arm) and the value is the EChemicalType enum (sludge, frost or acid). I implemented this to allow easy access to the specific chemical equipped, as the end user only requires access to the weapon type used to determine this.

The amount of chemicals available to the player is stored within the chemical system alongside the ability to add or subtract from a specific chemical. I decided to abstract this logic away from the player class to allow for easy access for designers, ensuring they could set default values. This decision also made it simpler for me to implement chemical puddles to regenerate chemicals for the player to use.

To apply chemical effects, I implemented an IStatusEffect interface. This interface has a single pure virtual method ApplyStatusEffect,  overridden by both the player and enemy class. Initially, both the player and enemies would react differently to the chemicals; with the player receiving benefits when applying chemicals to enemies, while the enemies receive drawbacks.

 

 

This design decision encouraged me to develop an interface, as custom logic could be implemented for both classes. In the final iteration, however, only sludge affects the player. As such, a component-based implementation could have been simpler and could have reduced the complexity of classes themselves, increasing readability and abstracting logic to a separate class. Overall however, developing the chemical interaction system taught me a lot about when best to utilize components and interfaces, providing me with more options for systems design in the future.

image.png

Screenshot of ApplyStatusEffect pure virtual method implemented in Player

Punch Mechanics

Another core mechanic I implemented is the punch mechanics, alongside hitstop effects. Because this action is used frequently by players to send enemies flying, it was important that each punch felt satisfying. To support this, I implemented hitstop mechanics to briefly pause enemy animations on impact, alongside immediate sound effects and a small chemical explosion visual effects.

When the player presses the right mouse button or right trigger, that input is sent to the ChemPunchInputComponent. This component utilizes the enhanced input component system in Unreal to bind player logic to input events. The UseRightArm method triggered by this input sets the IsPunching boolean within the ChemAnimInstance animation controller, triggering the punch animation to play. I decided to encapsulate the input functionality in its own dedicated component to simplify the process of adding new input actions and lighten the complexity of the player class. 

The punch action is implemented through a combination of animation timing and collision detection; first, the animations play, showcasing visually the player swinging their arm. Simultaneously, a collision box swings across the screen in time with the animations, following along a spline component. This simulates swinging of the arm mechanically, as when enemies collide with this box, they get flung backwards. Finally, hitstop effects occur when enemies are hit, pausing both player and enemy animations. This slight pause gives the eyes time to register the collision that has taken place, leading to heavier and more satisfying punches.

The punch logic is tied to custom anim notifies triggered within the punch animation itself at specific keyframes. When the StartPunch anim notify is initially triggered, the punch collision box begins to follow along with the player punching animation through a timeline, as showcased above. Initially, the collision box is disabled, allowing for the box to move to an appropriate position on the spline mesh before colliding with enemies. I decided to incorporate this initial setup period to allow for more fine tuned control of the collision arc to time it more appropriately with the animation itself. 

 

Later on, when the PunchAnimNotify is triggered, the player's collision box enables collision. After this, once EndPunchAnimNotify is triggered, the collision box is disabled, allowing precise control over which points along the animation the punch collider is activated.

I decided to incorporate an anim notify system to the punch mechanic as it allowed for designers to easily move the notifies to a specific keyframe, letting them fine tune enabling and disabling of the punch collider. By creating the StartPunchColliderTimeline method in blueprints, I also allowed access to designers to change the length of the timeline by modifying the Punch Collider Timeline Length variable, letting them access the core mechanics of the punch system without having to dig into C++.

Upon reflection, manually modifying the spline position could have provided even more precision. However, during development, animations were iterated upon and replaced frequently, which would have resulted in having to constantly modify the spline position. My delay system for the collision box was simpler to modify, as it only required changing a single variable. Due to the time restrictions of the competition, I decided that this approach was appropriate as it provided enough precision without requiring more development time.

image.png

Screenshot of punch montage, showcasing anim notifies at specific keyframed positions 

image.png
image.png

Screenshot of Player Blueprint StartPunchColliderTimeline method, showcasing how the punch collider follows along a spline component through the use of a timeline

Flamethrower Mechanics

Another core mechanic I implemented is the flamethrower mechanic. The flamethrower is a powerful weapon within the players arsenal, allowing them to dispatch multiple enemies at once and apply chemical effects over a wide range. Enemies hit by the flamethrower take damage over time and have a chemical effect triggered, depending on the chemical equipped. To emphasize this, I implemented visual effects to represent enemies being set on fire with different colours representing which chemical is affecting them.

The flamethrower mechanics also utilize the ChemPunchInputComponent to trigger player logic through input events, utilizing the enhanced input component system within Unreal. However, two different trigger events are bound for the flamethrower; Triggered and Released. The UseLeftArm method sets the IsShooting boolean to true within the ChemAnimInstance animation controller to enable the shooting animation to play. This method also calls AbilityFlamethrower within the ChemPunchPlayerWeaponComponent, which begins the flamethrower logic.

The flamethrower is implemented through the use of a cone collider, which starts small when the player begins firing, then scales larger until it reaches its maximum size. The scale rate can be modified extensively as it is directly linked to a float curve object, accessible in blueprints. I decided to implement scaling this way to allow for designers to easily modify weapon reach and scale timing without having to change any of the underlying code, allowing for quick iteration.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

The flamethrower interacts with the ChemPunchEnemyHealthComponent to deal damage to the enemy, through the StartFlamethrowerDamageOverTime method. This is called when enemies enter the flamethrower collider, triggering a timer that continuously applies damage at regular intervals. Once the enemy leaves, a second timer triggers that ends the original one after a set amount of time. This allows for the enemy to continuously receive damage through an event based system rather than a tick based one,  using the OnComponentBeginOverlap and OnComponentEndOverlap delegates.

In hindsight, simply manipulating tick alongside single timer dictating how much longer an enemy should still take damage after exiting the flamethrower collider would have improved readability and lowered the complexity of the system. However, at the time, I wanted to avoid utilizing tick as much as possible, as relying on it too much could result in runtime performance issues due to the large numbers of enemies within the level.

Screenshot of Flamethrower Float Curve, with time representing X axis and scale value representing Y axis

Screenshot of TickComponent function, showcasing usage of flamethrower float curve. Tick was manually enabled when firing, then disabled after finished firing.

Conclusion

During development of ChemPunch, I was responsible for many player facing systems, such as UI, gameplay mechanics, 3C's, input, and animations. Reflecting on what our team accomplished within 3 months, I am extremely proud of the final product. I learned a lot about systems level thinking, considering when to use interfaces versus components, how components and systems could interact and what designers, artists and other team members need from programming to embed their work seamlessly into the game. 

 

I returned from my internship to participate in Dare Academy as I wanted to utilize my professional skills and further develop them within the high pressure environment of the competition and see how much I had grown as a programmer. I was able to utilize several Unreal features, such as the enhanced input system, animation notifies, timelines and custom delegates in ways that I had seen used professionally.

 

As the project grew and time became scarce, I often had to develop features and fix bugs in ways that were less optimized but functional given the time constraints of the competition. This experience taught me the importance of making simple, maintainable systems that were easy to iterate upon and modify without overengineering them to fix problems that did not exist.

Overall, I had a great experience with Dare Academy and enjoyed working together in a multi-discipline team with other talented students, competing against other incredibly skilled teams. It strengthened my ability to think quickly under pressure and provide pragmatic solutions that solved the problems at hand, while keeping future considerations in mind.

bottom of page