Dec 2025: Charge! Is Available on Steam!
Click here to check out the steam page if you're interested!
Click here to check out the steam page if you're interested!
Also, please feel free to check out our archive of progress announcements on indiedb here, and this is our final teaser trailer below.
Creating a first person shooter with fresh, casually fun gameplay through prioritizing the multiplayer experience, combining both older fun ideas from classic FPS games and specific experimental mechanics, and utilizing laser-tag like themes and sports inspirations.
Team:
Gameplay Programmer, Systems Designer, Tech Lead
Enabled stable online 3v3 matches, using Unreal RPCs and replication to create networked player and gamemode features for movement, weapons, gamemodes, and more.
Refined the mechanics of our casual arena shooter, collaborating in design discussions to refine our MVP vision, making various prototypes of our light-based weapon mechanics, and iterating from feedback.
Collaborated with 4 animators to refine the animation pipeline, using data tables, blendspaces, and more; recording/ teaching steps for easier autonomy.
Remotely collaborated with leads and delegated tasks to ensure we reached our public playtest build deadline of Sept 25th, and be on track for our Steam release.
Wrote documentation like our Unreal/ Perforce setup instructions, which has helped 7+ teammates set up the project quickly, and mostly on their own.
Profiled for performance with Unreal Insights and communicated to find solutions, implementing vertex animation textures for our 150+ spinning objective nodes and collaborating with our art lead to optimize lighting, improving our framerate by 20%.
~1.5 yrs ~12
Multiplayer First Person Shooter
PC Unreal 5
Networked gameplay programming, Unreal Engine 5, team leadership, rapid prototyping, interdisciplinary collaboration, delegation, systems design, game AI programming, UI programming
This project with our startup-style all remote team has been an opportunity to wear a variety of hats throughout our phases of production; programming, design, production and otherwise.
I am writing about aspects of the cycle like learning networked programming, practices like documentation, and lessons from them on this blog page (Click Here), and here are a few features I'd like to share that I've made in the development of Charge!, working on the networked gameplay, player mechanics, UI, and more.
Player bots that can engage in fights and complete the objectives, to make enough of an impact on matches that we could do more consistent playtests even with a few players.
Weapon modifiers like the prism passthrough, which refracts and splits your shots through walls using physics-like refraction. Designed to support both simple fun and skillful opportunites.
Menus featuring moving spotlights and themed button animations. After communicating with our UI artist and other designers as the assets were made, I implemented the menu by creating reusable base widgets to support the variations of buttons, sliders, and more.
Networked weapon system, made scalable with child blueprints for creating new weapons fast, and supporting hitscan shots, traveling projectiles, and melee attacks.
Pregame lobby, where you can wait for players, test out mechanics like the weapon modifiers (rebound modifier shown), and manage the teams before you begin!
In approaching making the networked setup, gameplay, and other systems for this project, I aimed to use practices learned from working on Remanence, as well as to test how long the benefits lasted of using blueprints vs C++. So, to fully explore this, I used a hybrid of C++ and blueprints to try and leverage the quick prototyping and iteration blueprints can support, even in some cases where I would have preferred C++.
Taking a practice we used on Remanence, I would usually create a C++ class of whatever Unreal type suited a current mechanic (AActor, AActorComponent, etc.), and then derive it into blueprints, letting me try variety of prototyping aspects using the blueprint class and see how far that got me, but still leverage the flexibility and features of C++ when it seemed more useful.
One example of a gameplay feature where blueprints were plenty for what I needed was making an extendable team game mode class:
In finding what our main game mode would be, we started out with deathmatch, but wanted to try multiple options, and after prototyping deathmatch, learning what I could utilize from Unreal's default AGameMode class (Unreal 5 documentation link), and adding features such as sorting players into teams and having a win condition/ win messages, I realized that a lot of this functionality could be generalized, and used for most any game mode. So, I reused the team support I had created, and as much other functionality as I could into my own team-supporting extensions of the 3 main Unreal game mode classes: a game mode blueprint called GM_TeamModeBase, a game state blueprint called GS_TeamModeBase, and a player state blueprint called PS_TeamModeBase, which all our prototypes and our current game mode derive from.
By approaching it modularly, where I made as much code generically reusable as possible, and made clearly overridable functions to help derived game modes handle key differences, I minimized the code needed to make new game modes, while still having flexibility, which has supported the various prototypes we've tried.
Gamemode prototypes included...
Deathmatch
"Key Hunter" Idea
Capture The Flag
By default, the base game mode class provides server authoritative (meaning clients don't run this) functionality for things like player spawning, starting, and restarting the game.
Building on this in-engine support saved a lot of time, and then adding my own base functionality and using inheritance through blueprints, I made each new game mode be able to:
Automaticaly sort players into teams, and use defined team colors.
Have an easily changed win condition, which a lot of times is the main thing needed to make a new game mode work at first, and made prototyping game modes much quicker.
Give the player any gamemode specific weapons/ user interface, set up win/ loose messages, fill a match with bots, and more.
Game State Variables
Player State Variables
The other two base classes, the team game mode and game state bases, are more focused on updating information that needs to be available for all players, as these classes are not server authorititive, and are replicated to all player's games. For instance, the game state keeps the team colors and names, and has getter functions for easy access, and the player state allows for accessing what team a player is on, and helps update features when a player's color is changed through a delegate that UI and other features subscribe to.
To prototype our territory control mode, once the specific objective objects were created, there were only a few main game mode changes needed to get the prototype working. Mainly, I needed to:
Add a match timer
Override the "Check Team Won" function, to have the attackers win when all objectives were complete,
and have the defenders win when time ran out.
Finishing up the prototype:
I also needed to send some of the information like the match time and current scores to the game state, to make the current game status visible in each player's UI (this is a polished version), and add showing which team players were on. Though, the PS_TeamModeBase made the process much quicker because it already made info like what team a player was on replicated and available for the client UI to use.
Finally, my base team mode automatically put players on teams, and the team colors, weapons we wanted players to have, win and loose messages, and more, were able to be customized right away using the variables provided from the base team game mode.
And, after getting the initial prototype and testing it in matches, we liked how it was playing, so it was on to iteration from there!
Path Command Initial Prototype (All teams on attack)
Path Command Semi-Final Version (Attack vs Defense)
The above case mostly benefitted from using blueprints, deriving from the base game mode C++ classes and enabling the new game mode code to be quickly adjusted at runtime. And, in cases where either it became clear I needed to use features in C++, like for serializing/ deserializing the stats for making our networked weapon stat editor, or that C++ would be more effective, I would use the C++ base class more, or refactor into C++.
When nailing down the core gunplay was the main focus in December of 2024, I knew frequently editing weapon variables would be key. So, to help everyone on the team iterate on the feel of weapons, and potentially even testers do so as well, I created a networked in-game stat editor for our game's weapons.
In addition to letting stats be edited at runtime during online games, making it quicker to test changes with remote teammates, I used a composite data table so that we could have multiple versions of our weapon stats,enabling people to test and save stat edits at the same time even with perforce/ Unreal exclusive file locking.
It works for each weapon, which is helped by the fact that weapons are children of a base weapon, and pull from the same base stat information structure.
And, as a bonus, using Unreal's UStruct and UProperty reflection system, the editor menu items are generated directly from the members of the weapon stat struct, so adding in a new stat variable to the weapon data struct automatically adds it to the editing menu!
I first use the UStruct and Uproperty reflection data from the weapon struct to get the names of all the stats members (currently I only make editors for float and int type variables). I have since experimented to make a generic version of this function that works for all structs, but I kept it simple for the first implementation.
I then loop through the names and make a stat editor sub-widget for each one, which are added to a vertical box that serves as a list of all the editors in the main weapon stat editor widget.
This uses a GetWeaponStatF function I made that gets the value of the struct's property by name, so that the editor starts with the correct value for the current weapon in each stat's box.
The main stat editor widget implements an interface for setting values, which is a general function that each stat editor calls on it's parent whenever its value is changed. Since each stat box just needs to pass the name and value of the stat it represents to the parent widget, this decouples the editor UI logic from the game logic of going and setting the weapon stat, and means we can fairly easily add new types of boxes for different types of stats, if needed.
Here is the "On Commited" event for each stat's input box, which sends the new value to the parent widget through the "Set Stat Float" interface function
To make abilities like our reflective and refractive shots, I tested out Unreal's Gameplay Ability (GAS) to System to see if it would be a good fit for our project. While the scalable base ability class and built in networked ability activations seemed quite useful for making different abilities, it was structured to only call ability activations on the player that owned it, and the server, relying on their own replicated attribute system, and gameplay cues stat changes, to send changes to the other clients in the game. Our system was set up to use our own stat structures on the clients and server, and use RPCs for sending immediate changes to ability related stats on clients, so weighing the benefits of refactoring to use GAS, I decided to stick with our current setup based on the scope of what we needed the abilities for. However, I drew from GAS's generic functionality to make our own extendable ability class.
After prototyping the abilities in blueprints to determine what we'd need for them, I made a base class in C++ for abilities to derive from.
I then made child blueprint classes for each ability, overriding the necessary functions for each ability, and allowing for future abilities to be slotted in, and have the system automatically activate them for the server and clients.
Now being more familiar with the benefits and limitations of blueprints, and how the set of unreal features is quite similar in blueprints and C++, I would move out of blueprints quicker for certain mechanics, and start the development of more features in C++ so they are easier to access in C++ for other systems down the road. Also, especially with the tedium of organizing nodes, and some use cases that would be trivial in C++, but can be a pain without plugins in blueprints, like sorting an array, C++ can be more useful in certain situations, even at a prototyping stage. But, with the reliable quick compilation at editor runtime, blueprints were still quite helpful, especially for certain networked mechanics where the syntax of making events is much faster to write and adjust in blueprints, and because in profiling, the performance impacts of blueprints haven't been too significant. And with this, using the hybrid approach of creating C++ classes and deriving into a blueprint made certain mechanics quite quick to add and modify, with minimal downsides. So, while blueprints aren't as crucial to use as I originally expected, as a tool to decrease the complexity/ friction of editing mechanics, I think blueprints have meaningful benefits when used for prototyping, and on general gameplay components that are edited frequently.
Making the prototype of Charge!, we made a lot of the functionality necessary or helpful for a first person shooter, such as:
modular weapons for easily adding new ones
making the gamemodes, shooting, UI, etc. in a networked way to support multiplayer
various character animation for each weapon type (1st person for you, 3rd for opponents)
making both hitscan lasers and projectiles that can reflect or refract when hitting certain materials in-game, which we thought would be the interesting twist our game brought to combat.
However, through testing the game, reflections as a main mechanic hasn't worked as well as we hoped, and after trying a few different solutions, we determined with the time it had taken, we needed to pivot to more of the basics. I have details in the reflection below on the reasons I think our development has led to this state, and ways I think would have helped us to better achieve our gameplay goals sooner, but currently, we are shifting to nailing down the gunplay through revisiting the core feel target, and prioritizing testing, before any other higher scope details are considered.
I am currently away from the project focusing on my senior year of DigiPen, and the Remanence project also on this website, but I did my best to set the team up for progressing on the core gameplay while I'm gone by implementing the above stat editor so that weapons can be easily refined by anyone, a capture the flag spinoff gamemode as potentially our key gameplay twist, fleshing out missing implementation documentation, and advising a bit when I can.
In having a bit of a skewed focus towards implementation over doing more of the design and testing early, focusing on a few to many aspects of the game at once, and issues with finding a vision we all could picture well, we have had troubles getting towards a core prototype we all feel is fun, and represents a complete loop of gameplay that satisfies our project goals, not just basic FPS gameplay. We have been able to make a full basic loop of networked deathmatch & capture the flag, with pipelines we've used for implementing melee and ranged weapon models, 1st and 3rd person animations, easy to create weapons, and even an additional place-able mirror mechanic which we tried out when default mirrors weren't working. However, coming to a full experience where we can clearly find the specific fun has been our current main issue. Realistically, we should have uncovered these problems much sooner, by starting testing much sooner, but through a combination of things, most important of which I believe have been the need for a clearer MVP, so that we could have had a more reasonable target for the game's state that was required before testing began, mitigating the player count required for playtesting, as with our target of 5v5, testing was much more difficult, and having a clearer the overall vision for what we wanted to achieve, would have all helped us to reach this sooner, and these have led me to realize things I can do in the future to reach the core fun of a game sooner:
Clearer MVP: When waiting to test Charge! I kept trying to get us to gameplay that felt representative, first making the basic laser weapons shoot reflective shots in blueprints, and making a function library for reflections and refractions, then learning multiplayer shooting and gamemode implementation through unreal's Replication, Blueprint RPCs, and sessions, and then how to combine that with unreal's gamemode system. However, even after doing this, and reaching a functional deathmatch, we still had considerations for other game mechanics, like melee, throwables, and other things, and I continued to prioritize making things new or better, vs testing what we already had, even if it wasn't 100 of what our vision was. I realize now that without the core experience, none of that other stuff can be helpful to us, and if I were to do it over, I'd make sure we were 100% clear on what base Charge! needed, and once we had that, go all in on testing, to bring out those issues with reflections as a main mechanic, and have more time to find solutions. And, during this time, I would also establish an understanding that we should avoid talking about implementing new things until the core prototype is solid. In balancing a more tech leadership focused role for the first time, I tried to be available to establish the art pipeline, and implement the new assets that were being created, even if they weren't core feature related, so that art could still progress while core mechanics were being fleshed out, but I realize now that this still took too much attention away that I couldn't properly finish the MVP in a timely manner. It was just me as a programmer for a while, so this may be more feasible when a team starts out with more programmers, but I know that if we could have made the concessions necessary to be able to focus on finishing our MVP for unique gameplay, then take stock at what we could achive from there, we could get a lot farther in making a uniquely fun game with the scrappy power of our team.
Realism of making 5v5 combat: Given our team size, in hindsight, 5v5 combat is quite the task, and looking at it now, I likely should have had us make a more concrete plan for how we'd be able to test that, given our team size, and situation, being an online team in the summertime. 5v5 isn't impossible, but it requires a lot more resources for testing, having an ideal player count of 10 players, and especially when the player count matters more in a collaboration-focused game. So, it would have been worth it to consider more closely whether this player count was necessary to our goals, or if we could cut down on players, and achieved the same effect. To help with this recently, I made a playtesting discord to organize times to test with a larger group, and this was helpful, but doing this sooner would have helped us gain a lot more momentum, seeing the game in action sooner, and identifying ways to make it better from a realistic set of players. Also, if I was to do this again, and we still wanted 5v5, I would make implementing AI player bots a much higher priority. We did start this, but by the time we did, I was already more busy with school, and we had needed to be playtesting much sooner. Plus, having even simple AI that can shoot and complete an objective would have been a great help for those working on level design, to more easily see how combat plays out in their space.
Scope and clearer vision: If real estate is all about location, location, location, this project has further cemented that game development is vision, vison, vison. So, even if this is a lot like point 1, I'll say it this way too. Whenever we have had a clear vision of what we wanted, we've been so much more effective in utilizing the capabilities of our team, and collaborating, such as when we implemented the first arena level and got reflections working on it, implemented basic character animation, or other things that we knew we wanted, where we could make clear tasks to collaborate on, and ultimately, feel they were complete. However, with mixed ideas on how a basic match of Charge! should play out (slow and tactical vs medium speed and high pace, all about teamwork or can an individual carry a match?, etc.), things dragged out more, and this affected other aspects of the development cycle, such as our ability to say we were ready for playtests, or confidently implement more features. In hindsight, scoping down on the features we were willing to consider (throwables, melee weapons, potential gamemodes, player count, etc.), and the fidelity of art features we wanted to make a pipeline for (full 3rd person blendspaces for opponents, 1st person ones for you, different character models, etc.), would have helped immenesly in letting us get past that milestone of being confident in the viability of our MVP. It's not that implementing the blendspaces, 1st and 3rd person animations, melee weapons, and all that, were an immense challenge, but needing to consider all that while trying to hammer down a core fun experience meant I and eventually the 1 other programmer, only had so much bandwidth, and couldn't focus as well on just refining the reflection-based shooting, identifying the issues with it, and finding a solution/ deciding to pivot much earlier. In being the lead programmer, I felt bad when I could clearly see the artists were doing work fast, and needing pipeline help to get their work in game, and so I split my attention to try and do it all, but as I've stepped away and been able to focus on other school projects, I've been reminded of just how important focus is for making the best thing I can, and also being able to work fast. So, if I was to do this in the future, I would make sure that we can our clear vision of what the core gameplay, but then be stalwart in giving my team's gameplay programmers, provided it had a reasonable deadline, a headspace free of other polish considerations, so that we can focus on making the core gameplay work, and then when the team and testers are happy with that, then open conversations for how we can polish it, and add better art features, from there. And, even if we would have to make some concessions, at least we'd have a solid game foundation to build on, so that the assets we would have the scope to implement would matter more.
Older screenshots featuring certain prototype mechanics, and an older WIP level