Purger development journey


This is a summary of the development journey I’ve taken while working on the game Purger. latest build: https://fiddury.itch.io/purger 

Date: 7/3/2024

The Formation of the Initial Project

The initial creation of Purger didn’t start from a single concept, but from a combination of ideas from 2 previous solo projects, as well as additional systems I’d made from my final university project. It started with an idea I got from a chapter in a novel I’d read back in 2022 called “The Heroes” by Joe Abercrombie. This chapter depicts a battle where the perspective character changes between soldiers when the current one is killed. One soldier would narrate then get killed during battle, the offending soldier would then become the new perspective character until they were killed, and this would repeat until the end of chapter. The idea I had was the player can possess what killed them and keep on fighting with that character's body and abilities

So at the start of 2023, I started making a Unity game called “Body Snatch” with the core mechanic being this method of character possession. I spent roughly a month on this personal endeavor, but never ended up making a demo build. I only created the primary system for the mechanic as well as basic attacks, player controls, enemy AI, and some UI displays. Each character or “agent” had 3 core script components:

  • Agent: Contains all the shared abilities and attributes of the agent (including attacks), as well as managing who is controling it
  • Player: logic for player controls
  • AI (Enemy): logic for the AI

The Agent script would enable/disable the Player and AI scripts based on who would control that agent: 

  • Player: Player ON / AI OFF
  • AI: Player OFF / AI ON
  • Inactive: both are OFF

I got the mechanic to work and balanced it with a limited number of possessions the player could do to prevent them from being basically unkillable. I only dropped the project when I had to shift focus onto my final capstone project to complete my double bachelor's degree. That project would eventually turn into Dodge West (https://sites.google.com/view/henrykavadias-barnes/dodge-west), where I’d develop several reusable backend systems and refine my development skills. I used some of the systems from Body Snatch for Dodge West, including the damage and health systems. 

Until the end of 2023 I worked on Dodge West, eventually finishing the project as its lead developer with my graduation following shortly after. During this time my university's games development club was hosting a game jam competition for its members and I decided to throw my hat in the ring as a solo contender. The game jam had 2 themes to choose from, “Colour as a mechanic", and “You are the Final Boss”. I had an idea for the latter which became the first iteration of Purger. My concept was you played a massive and imposing gunship that would lay waste to all those beneath it, fighting off any resistance that presented itself. This stemmed from a narrative idea I had where people have access to these massive flying structures the size of cities that were highly technologically advanced, durable, and were used as Citadels, Fortresses, or Gunships. I wanted to translate that idea over to a game and saw this game jam and its theme as an opportunity. I would end up making a build and submitting it to the competition, but even with a week's worth of time to work on it, more pressing IRL commitments sapped much of my attention away from it. What was produced in the end was visually and mechanically barren. All you could do was fly around, swap between top down, orbit, and combat camera states, and aim with the turret (which didn’t even fire anything). 

It was a skeleton of a concept and didn’t win anything in the competition. However, outside the constraints of the competition it gave me the idea to expand the base concept to make an action combat game. It wouldn’t just be the player in control of a Gunship, but a collection of them fighting each other with the player in the middle of it. I did want to return to the concept in Body Snatch, but I thought this new idea would be more engaging, so I thought why not just merge the projects. And over the course of a week or 2, I refined the shortcomings of the initial Purger demo and successfully merged the core features from Body Snatch into it.

Additions from Dodge West, and First build

After merging Body Snatch into Purger, I went about adding all the useful systems I could from Dodge West. This predominantly included:

  • Audio System
  • (Refined) Damage System
  • Health and Lives System
  • Some VFX and explosion effects
  • UI systems
  • Scene Transition System

There were things from both Body Snatch and Dodge West I had to either rework or replace for Purger. Most importantly was the Enemy AI, which was built to move around with a 2D Navigation Mesh. Until I can figure out how to make a 3D Navigation map for the AI to use to effectively move around a static world, I made do with a more reactive solution, raycast collision detections. Currently (at time of writing) each AI has about 10 raycasts that are used to detect their surrounding physical environment. Depending on which ray detects a hit, the Agent will either move in the opposite direction or turn away. It isn’t a perfect solution, and still needs work, but it does reduce the chance an agent will get stuck in the environment.

After that and making sure my player controlled properly, I created a demo level and a basic start scene with an options menu for player mouse sensitivity and audio volume. I then made a new build (V0.1.1). Compared to its Game Jame counterpart, it was an improvement:

  • There were enemies to shoot and who could shoot back
  • All agents could suffer damage and die, the 
  • UI shows the players current weapon, health, their goal, and controls
  • Game has functional audio
  • Transitioning between scenes is smooth

But despite all that there were drawbacks (outside of the lack of visual design):

  • Turret aiming for the player was awakened (used manual crosshairs)
  • The combat/turret camera was cramped under the agent, and swapping between it and the other camera is a bit disorienting
  • (most importantly) The current backend infrastructure didn’t allow for any of the agents to have a dynamic range of turrets, only for one turret per agent

After stewing on it for a while, I felt that the game wasn’t engaging. It was too rigid and awkward to be fun. It still needed work, but I had set up things so that it would be easier to expand on. Fixing the combat camera was thankfully straight-forward, just needed to reposition the anchor for that camera mode. Expanding on the logic for the player and AI was also manageable. My initial design for the Agent-Player-AI controller script relationship was that the player and AI scripts could utilize inheritance, which would cut-down on code between child and parent scripts and also allow the creation of agents with the same attributes but different behavior (most importantly, different forms of AI behavior). The Agent script would always remain the same since both player and AI scripts would need to reference it frequently.

There were still some challenges that I needed to overcome before I could turn this concept demo into something resembling a game. Specifically 4 challenges:

  • Multi-Turret System (my bane)
  • Performance (Pooling system)
  • Possession Mechanic (Possessor Agent)
  • Player Turret Targeting (scan camera)

The Multi-Turret System

Out of all the systems I have made for Purger, the turret system was the most difficult problem to resolve. The initial set up for agent turrets was that they’d be attached to the model and only have one turret arm. This turret was placed under the agent model and could rotate from left to right (Y-axis) all the way around while their up and down (X-axis) rotations were restricted to prevent them clipping through the agent model. To do this I simply clamped the rotation of the turrets X-axis between two points for the AI and player. This worked, but suffered from a number of flaws:

  • Wouldn’t support agents with multiple turrets
  • Depended on the rest of the agents model never rotating on anything other than it’s Y-axis
  • Didn’t work for turrets that were mounted on the side of the agent model
  • The rotation locks were only relative to worldspace, so if the agent model rotated, the lock range would need to account for this

The first step was modifying the agent controller script to account for multiple turrets. This was done with a Turret Class that the agent script used a list of to track all of its turrets. This worked well and could be further changed to account for the rotation lock issues. The main challenge with multiple turrets is that their rotation lock or valid range of motion they can rotate in would be different depending on where on the model the turret was located. This meant that each turret would have a unique range they could rotate in on both X and Y axis. The other major issue was these range limits needed to be applied locally (relative to the model they are attached to) otherwise they needed to account for the rotation of the model. The requirements that needed to met for the turrets rotation limits were as follows:

  1. Turrets can’t rotate inside / clip through the model of the agent they are attached to
  2. Turrets can only rotate within their given range limits
  3. Turrets that breach this range limit will have their rotation locked and won’t be able to fire any projectiles
  4. Solution needs to work for all turrets regardless of the rotation of the agent model or the turrets placement on said model
  5. Solution needs to work for both player and AI controller scripts

After penning down calculations and experimenting with how the Unity Engine deals with object rotations, I created a solution that worked for turrets underneath or on the side of an agent model. It worked by giving each turret a upper and lower range limit for their rotation on its X and Y axis with a boolean for each to unlock all limitations for that axis. It then went through a process for each turret to check if their current rotation was in a valid range when either the player or AI was aiming with them. That process went as follows:

  1. Calculate the rotation the turret is trying to aim at
  2. Calculate the range limits with consideration to the models rotation
  3. If upper limit is greater than lower limit, then the rotation must be outside of the limit range to be valid, otherwise it needs to be within the limit range to be valid
  4. If the turret isn’t in at a valid rotation, then clamp the turrets rotation and prevent it from firing

The solution worked, at least with initial testing, but every time I thought I’d got it to work as intended I’d come back the next day and discover that it had a fault that I hadn’t found. I got very close to getting it to meet all 5 requirements, but the majority of issues I was having with my solution was with requirement 4. The fact that my solution had to actively account for the rotation of the model (which the turrets were parented on in the Unity object hierarchy) was creating a lot of complex scenarios that I then needed to get my process to account for. I spent hours doing the calculations on paper to visualize the different angle scenarios that my solution needed to account for. But despite my efforts, I’d always found an issue that meant my solution was inadequate. I tried to get the process to work locally, but it would always cause something to break.

I did try (even at the start working on this problem) searching for a better solution online, but every time I did the solution I found didn’t suit, or was not applicable to the nature of my problem. Until I found this: (Unity Universal Turret Rotation, code: Unity Universal Turret Script · GitHub). I hadn’t found this earlier because the video wasn’t that well known (only has 4k+ views at time of writing and the channel only has 2 videos uploaded), but the solution that this user (GG Alsorunning) had showcase was far better than the latest iteration of mine (they even had an improved version:How to make a turret in Unity, code: https://gist.github.com/gengen1988/bc95661693dafc2d3bae486b86ef323a). Their solution completely removed the issues I was experiencing and met all the other requirements for the problem. It was easy to implement by making some modifications to my existing turret class. I still have the old system I made, but it has since become redundant. There were some shortcomings with this new solution:

  • Must use 2 of their “turret controller” scripts to get it to work properly, turrets operate off 2 “mount points” (one for the base and for the gun) where the turret can rotate and each point requires a script
  • Only rotates objects on the Y-axis, so to get full rotational range the gun mount point must be parented under a “hardpoint” object that is rotated 90 degrees on its Z-axis
  • Setup for new turrets requires a manual test to check if the turret is behaving as intended at its orientation

Asides from that, this new solution made it really easy to not only put multiple turrets on an agent with different orientations, but allowed for the creation of customized turrets with multiple guns. This meant I had to modify my Turret class again to account for that, so when the agent is firing a multi-gun-turret it’s firing one round per gun per turret and factoring in ammo limits. It worked so well that I was confident enough to try stress testing it, creating new agents including one I called ALOG (A Lot Of Guns) that had 15 turrets with 2 guns each. With minor testing, it all worked well, except for the performance issue.

Pooling Objects for Performance

The new turret system was a success, but indirectly highlighted an important problem regarding performance. When an agent with lots of guns is firing with a weapon with a high fire rate, it can cause a significant frame rate drop, which is only made worse when there are multiple agents fighting each other. The main causes were the large number of particles used in the VFX for projectile collisions, and how projectiles were used in the game. I reduced the number of particles for these VFX down to a tenth. I also centralized the logic behind the projectiles, combining 4 for their core scripts into one so they wouldn’t need to keep internally referencing each other, and made it easier to view the majority of the logic behind a projectile. 

However this didn’t solve the performance cost of spawning and destroying projectiles. Typically doing it this way is fine, if you’re not firing a massive amount of them. Fortunately the solution was pretty straight forward, use an object pooling system for projectiles and VFX. The concept behind a pooling system is pretty straightforward and is a well-known feature in the industry, but to break it down for those who don’t know, the system I use works as described:

This is the type of solution I used: https://www.youtube.com/watch?v=TQgrifL9DK0, User: Merkury

Objects are created under their own “pool” (parent object). There is one pool for each object, for example:

  • Basic shot -> pool for basic shot 
  • Explosive shot -> pool for explosive shot
  • VFX shot destroy -> pool for VFX shot destroy

After the object has done its operation or met certain criteria (e.g. projectile reaches its range limit or hits something) instead of being destroyed it is disabled. It’s still registered inside the scene of the game, but isn’t able to interact with anything. Then when an object of the same type needs to be created, the pool system gets the relevant pool, checks if there is a disabled pooled object under it, gets one, enables it, and returns a reference to it to utilize. If there isn’t a disabled object to access then another object is created under the pool. All these different pools are tracked and controlled with a Pool Manager, which is a static C# object. 

The solution I sourced and used was pretty easy to implement since all I needed to do was add the relevant component script to each projectile and VFX, set them up with it, and modify my agent controller script (which managers firing the turrets on the agent) to used the system if a project has the “pool object” component. This work almost flawlessly, the only bug I found with it was that the pool manager needed to clear its list of pools (in this case a C# Dictionary) when a new scene loaded. This was because the pool manager would try to access pool objects that didn’t exist anymore since all those pools were deleted when a new scene was loaded (Unity was giving a “Missing Reference Exception").

Creation of the Possessor

At this point in development, the project was really coming together. But I hadn’t yet implemented the main concept that this journey started with, agent possession. I’d been putting it off since I felt skeptical that the original idea wouldn’t work well in this project. Only being able to possess the agent that killed you seemed interesting but was restrictive and presented some gameplay design problems. Mainly that players may just identify the most powerful agent in the scene, intentionally kill themselves against it, and use that agent to kill everything else. Plus I didn’t think the mechanic itself didn’t give the player enough agency to make it engaging rather than frustration or disorienting. I decided to give the player more control over what or who they wanted to possess while still implementing limitations to prevent the ability from being overpowered.

I created the “Possessor Agent”, a player only controlled agent (still has the same backend as other agents, just has a blank AI) that couldn’t attack but also couldn’t be targeted/seen by the enemy agents. The player would be given control over this agent when the one they were using dies. As the Possessor they can see all the agents in the scene as red highlighted objects. When they move close enough to one of these agents that highlight will turn green, meaning the agent can be possessed. The player can use the attack action to possess the agent giving the player control over it. The possessor agent is then disabled until the player dies again. The number of possessions the player can make is limited by a counter that drops by one after each possession. If the player is out of possession attempts and dies, it’s game over.

Some quirks did need ironing out while others needed reworking. I changed the setup for the possessor agent so the player would only be able to possess other agents if the possessor was in the scene. This involved reworking it so the game controller was the one that could spawn the possessor, not the player which meant it was easier to switch the possession mechanic on/off from one place. Overall, this solution could do with some refining, but it seems to get the possession mechanic in the game without being too clunky. Although to truly determine that would require playtesting.

Scan Camera Targeting

I was almost confident enough to make another build to replace the existing one, save for one concern. Turret targeting as the player was way too difficult. Manual crosshairs that changed with the weapons accuracy were perfectly functional, but didn’t work well when you and what you were shooting at were moving around. Leading your shots towards where the enemy was going to be wasn’t any better since it was still less accurate than the predictive targeting the AI had. To put it simply, the enemy is far better at landing shots then the player, and even if the AI was nerfed that still didn’t solve the issue with the player.

I could use a camera lock-on system similar to that in Dark Souls, but that wouldn’t have worked well in a 3D environment with 3D fast paced movement against multiple enemies. Plus swapping between targets would likely make the shifting camera orientation disorienting. Game feel is one of the most important factors for its usability, and I didn’t think a camera lock-on system, or any hard lock-on system would work well. So I came up with a new system called Scan Camera Targeting.

The basic idea is that instead of crosshairs, the player has an outlined space (circle or square) on their UI called a “Target-Space”. Any targetable enemy inside that view space is targeted by the player agent turrets, which (can) also have predictive targeting applied to them. This would leave the controls for manual targeting untouch while also allowing the player to reliably target enemies. Shooting the enemy wouldn’t be a headache for the player, projectile statistics (range, damage, accuracy, fire rate, ammo, etc) would remain as a way to balance attacks, and the Players ability to shoot is closer inline to the AI’s. But it was easier said than done since there were critical requirements that needed to be met to get the system to work properly:

  • The Target Zone needs to apply the its zone check with perspective view, not orthogonal or box space
  • The UI representation and the actual scan space needs to match each other, even when scaled. This is the same with the main player camera perspective
  • The Target Zone needs to account for multiple targets inside it

There were other minor considerations, but the main requirement was how does the system know/detect when a target is inside the target zone. I wasn’t sure how to solve this problem so I started by simplifying it into steps:

  1. Detecting an object with in a defined space
  2. Detecting an object with the space of the camera view
  3. Detecting an object within a specific section of the camera view

First, detecting an object within a space, that can be done with either a collision or a boxcast. Only issue is that neither of those solutions would work appropriately for a perspective view. So I considered that as part of the requirement and looked for detecting objects within a camera view, which led me to discover this:Detect Objects in Your Camera View - Unity Geometry Utilities, video by channel “World of Zero”. This was a significant discovery as it accounted for the perspective view of the camera. It would mean I would have to add an extra script component to every agent prefab I’d made and ensure that it didn’t target the player agent, but that wasn’t a problem. Also it didn’t solve the 3rd step of the requirement, but it did give me the idea on how to. 

The final solution for the Scan Camera Targeting system is pretty straight forward. Simply use a second disabled camera (doesn’t need to be enabled to be used for the solution calculation) and alter its perspective (field of view and view dimensions) to match up with the target zone UI. Modify the player camera UI script to handle scaling the FOV of this “Scan Camera” when either the target zone UI or the main player camera FOV is changed. After some redesigning of the player camera object prefab, and some manual testing, the solution worked excellently. Attacking enemies was no longer a hassle, plus it’s possible to make it so the player can swap between Manual Crosshairs and Scan Camera (haven’t yet done so at time of writing).

The Latest Build (V0.2.5.1) and What Comes Next?

After some final fixes, refinements, and playtest feedback from friends, I was able to make a new build. Compared to the previous build I uploaded (V0.1.1), this was a significant improvement. Combat was far more functional, there is a greater enemy variety, and that variety built upon the new possession mechanic. I was pretty happy with the progress I managed to make. That being said the game still lacks a lot of features before a full proper game demo can be made. This includes:

  • Tutorial 
  • Better game options usability
  • Gameplay balancing
  • AI refinement
  • Ally agents
  • Visuals (artwork, textures, models, etc) and Music

The music, sound, and visuals have been relatively untouched or placeholder since I’m primarily a developer and my focus was on the backend and mechanics for the game. The AI still needs work as their behavior is pretty basic, plus they were designed for enemy AI with no consideration for ally agents. The UI needs further implementation, for example the options menu is only available from the start screen when it should also be accessible from the pause menu. And that isn't even considering the amount of balancing and testing the gameplay will need to go through to ensure everything works as intended. This includes playtests from others which will identify shortcomes that I was unable to see. It would also be worth making a proper game design document that outlines the vision of the game, its scope, the features, any other requirements, the general gameplay loop, and detailing the workings on the existing features already in the game. This has so far been a personal endeavor, but I would be interested in turning this into a proper game one day.

If you’re interested, try out the latest build here: https://fiddury.itch.io/purger 

And special mention to the following:

World of Zero, detecting objects in camera view, https://www.youtube.com/watch?v=XrqesjfcitU

Files

Purger_V0.2.5.1.zip 48 MB
Mar 04, 2024

Get Purger

Leave a comment

Log in with itch.io to leave a comment.