Oct 2025: Charge! Public Playtesting is Live on Steam!
Click here to check out the steam page, and sign up!
Click here to check out the steam page, and sign up!
Also, feel free to check out our archive of game announcements here, and here's a reel of some team gameplay from July:
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/ Lead Programmer
Enabled online 3v3 matches, using Unreal RPCs and replication to create a variety of networked gameplay systems like our extensible weapon system with predictive client VFX, and modular team game mode support.
Facillitated multiple refinements to the gameplay vision, collaborating on design, and prototyping features like our light behaviour-based weapon abilities, 3+ gamemodes, player mantling movement, player HUD, and player bots.
Created our character animation pipeline, using blendspaces for wider animation fidelity, and montages/ data assets for easier implementation and tuning.
Remotely collaborated with leads and delegated tasks to ensure we reached our MVP by our public playtest build deadline of Sept 25th, which is now live.
Wrote documentation like our Unreal/ Perforce setup instructions, which has helped 7+ teammates set up the project quickly, and on their own.
~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
In this project, I got to wear a variety of hats throughout our phases of production; programming, design, and a fair amount otherwise.
Starting out as the 1st programmer on the project, and after we decided to use Unreal, I had the chance to test new practices I had learned, and also do a lot of learning about network programming, as it was my responsibility to set up a lot of the fps mechanic prototypes and networked systems.
One practice I applied that I had learned earlier that year was using modules to separate C++ code, for faster compile times, and for potential smaller player updates.
Separating C++ code based on category, we ended up having 5 modules in addition to the base Charge one, and I would highly reccomend starting projects with this practice in mind. In addition to helping with good code separation, as you have to intentionally include other modules in the module's build.cs file to access their code, C++ compile times have always been good, and I have seen in the Unreal console that when recompiling during an editor session, only the changed modules are compiled.
And, while learning the needed networked programming princpals, which came largely down to learning Unreal RPCs (remote procedure calls) and replication, as well as confirming how we wanted to go about client server and/ or peer-to-peer connections, I came across this video. If you want to learn networked programming, I would highly, highly reccomend it.
The way I generally approach learning a new way of doing things is to learn what the the available tools/ techniques are, including the niche ones when possible, and why each should be used. Then, I go on to practicing a small example, using my knowlege of the right tools for that task. If this method sounds somewhat useful to you, then in addition learning about client-server and peer-to-peer mutliplayer models, which I'd reccomend first, this contains, at least to me, the rest of the topics needed for getting started with multiplayer programming in Unreal. It has a base overview of supporting features like net connections, and the main features you will be actively using, which are the 3 types of RPCs, and replication. It also includes client-server use case examples at the end.
The team expanded further during this time too, and with new teammates joining fairly frequently, and art aspects like weapon models and animations gaining speed, I got opportunities to write documentation to help new members get set up, and become more familiar with Unreal art pipelines to help create ours.
In the process of documenting, and 1.5 years later, the document I've written that's saved us the most time so far has been the project setup steps.
I created this because I realized that the instructions would be repeated a lot, and also to record common problems. After a few iterations based on questions asked while using it, the last 7+ people that have used it have needed very little additional help, which is good on them too, and I have appreciated the smoothness on both ends.
For the animation pipeline, the need for different 1st person and 3rd person animations came up, as our animators wanted more control to make things look better in each view. To accomplish this, I combined each perspective's animation blueprints into one generic animation blueprint, and used Data Assets, organized in a Data Table, to enable different animations for each.
Beyond this, there was quite a lot of iteration on our core mechanics, and here are a couple of the features I prototyped and refined along the way.
Player bots for helping make an impact on matches so we could do more consistent playtests even with a few players.
Light-based weapon abilities like the prism passthrough, which refracts and splits your shots through walls.
Menus featuring moving spotlights and themed button animations that came from collaboration with our UI artist and other teammates. I made the menu itself by creating reusable base widgets to support the variations of buttons, sliders, and more.
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++, 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.
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 base functionality saved a lot of time, and then adding my own base functionality, this 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 new prototypes much quicker.
Give the player any gamemode specific weapons/ user interface, set up win/ loose messages, fill a match with bots, and more, which has been added to as needed.
The other two base classes, the game mode and game state base, are more focused on updating information to be available for all players, as these 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.
To prototype our territory control mode, once the specific objective objects were created, there were 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 objectives were complete
and have the defenders win when time ran out.
For the other areas:
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, having info like which team a player is on already available and replicated made the process much quicker.
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/ added right away.
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!
Then, 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 editing weapon variables would be key. So, to help everyone on the team, iterate on the feel of weapons, and potentially even testers I created a networked in-game stat editor for our game's weapons.
In addition to letting stats be edited at runtime, and during online games, making it quicker to test changes, I used a composite data table so that we could have multiple versions of our weapon stats, letting multiple people test and save stat edits even with perforce/ Unreal exclusive file locking.
It works for each weapon, which is helped by the fact that they all 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 makes it editable in the menu!
I first use the UStruct and Uproperty reflection data to get the names of all the stats members (currently I only make editors for float an int type variables).
Then I loop through the names and make an editor for each one with a stat editor sub-widget, and add it to the list of my main weapon variable 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 each box.
The main stat editor widget implements an interface for setting values, which is a general function that each stat editor calls 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 client and server, and use RPCs for sending immediate changes to all 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, but 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 more features in C++ so they are easier to access in C++ for other systems. Also, especially with the tedium of organizing nodes, and some use cases that would be trivial in C++, but are a pain without plugins in blueprints, like sorting an array, can make using C++ more useful even at a prototyping stage. But, especially for certain networked mechanics where the syntax of making events is faster to write in blueprints, and because in profiling, the performance impacts of blueprints haven't been too significant, using this hybrid approach made certain mechanics quite quick to add and modify, with minimal downsides. So, while they aren't as cruciual to use as I originally expected, since as a tool to decrease the complexity/ friction of editing prototypes, I think blueprints have good benefits when used intentionally.
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