
MEME FIGHTERS
January - April 2024 | College Project
2D fighting game using existing IP such as Among Us, and The Muppets made over a period of 4 months at Sheridan College in collaboration with 4 other teammates.
I served as the main gameplay designer, as well as main gameplay programmer for this project.

Background
This project was developed over the course of 2 semesters with a team of 5 people including myself as part of our Design Practice course which serves to place us in an environment resembling an actual working studio. The project was mostly self-guided with a couple milestone check-ins to make sure production was keeping up with our deadlines.​​​
​
Since I had leading, design, and programmer roles in this project, I had to think about what kinds of moves these characters would be using, how I would express these ideas to the artists, and how any of this would work on the technical side, suffice to say it was a learning experience. Luckily, I had a team of incredibly talented people working with me who helped me bring my vision to life.​​
GAME DESIGN
This was my first time designing for a fighting game, a genre that I've always had an interest in from the perspective of a player, but that no one from the entire team had any experience designing for. Since I was the one with the most amount of knowledge surrounding this genre in the entire team, I took it upon myself to learn as much as possible to make the game as good as it could be.​​​​​


We knew from the start that we wanted this game to be approachable to newcomers. Fighting games are notoriously hard to get into due to the time investment that is required to master them and we wanted to avoid that trap from the start. At the same time, we didn't want to stray too far from what makes these games enjoyable to players, so our entire design philosophy was based on ease of entry.
​
Input​
Looking at existing games in the genre like Street Fighter IV I noticed they like to split their controls into actions such as punch and kick, and then have different intensities for those actions (light, medium, heavy), performing moves is then a combination of stick inputs and specific types/intensities of attacks.
This system fit quite well within our design philosophy, so we adopted it for our game, though we limited the amount of combinations for the sake of simplicity and scope. The control scheme I landed on uses the 4 face buttons as attacks, 2 for punches, and 2 for kicks, these are split into light and heavy intensities.

Movesets
Movesets were designed to be easy to learn while still allowing characters to be unique through their attacks. Based on this, I created a moveset template which all characters would adhere to, similar to how Super Smash Bros. handles its movesets, this was done to create consistency between all characters, allowing players to memorize attack patterns and then carry that knowledge when using new characters to promote experimentation.

For each attack type you get the following moves:
-
Neutral Punch​
-
Crouching Punch
-
Aerial Punch
-
Special Punch
-
Neutral Kick
-
Crouching Kick
-
Aerial Kick
-
Special Kick
​However, moves have a light and heavy version, usually heavy attacks will take a little longer to come out and are overall more powerful with a few exceptions. This doubles the options that players have while not overloading them with information.
​
Stick Input​
Special considerations were made when adding stick inputs into the game. I noticed that many fighting games which follow similar design philosophies as we did often times decide to omit stick inputs. I recognize that these can be a pain point for newcomers having struggled with these myself in the past, but they are nonetheless engrained into the DNA of these games, so I chose to keep them.
​
I found out through experimentation that quarter circle seemed to be the easiest stick input to perform so both of the special moves are performed using that input, just in opposite directions. I also made sure to make the timing on these quite generous, and the lack of other stick inputs means that players can't accidentally input a different attack.
​​
Unique Light-Heavy Differences
I mentioned previously that some attacks have unique differences between their light and heavy versions, most of these are reserved for special attacks, let's look at 2 of these as examples. The Among Us crewmate's special kick allows him to go into a vent and teleport towards his opponent; the light version teleports him in front of his enemy, while the heavy version teleports behind. Similarly, performing Kermit's special kick will throw him across the stage, but your input will decide whether he gets thrown low or high, giving players the ability to mix up their opponents.








Results
After multiple playtests sessions, with a variety of both experienced and non experienced players, we were able to determine that my design did in fact help newcomers get into the genre a lot more easily while not alienating more experienced players which was my goal with the project from the start.



PROGRAMMING
As the main gameplay programmer for this project, I had quite a big task ahead of me. We didn't know exactly how many characters we were going to have in this project, nor what kinds of movesets they would have, so I knew the systems needed to be flexible and expandable to accommodate for every possibility.​​
​​
Locomotion
I started by coding a basic 2D locomotion system for our characters using Rigidbodies so I could add forces on hit later on. In this basic prototype players could only move and jump, they would also automatically turn to look at their opponents at all times.



Nothing too fancy going on here, but the system here is actually quite flexible and allowed us to create very different feeling characters simply by shifting some values which allowed our pipeline to be very agile.
Very importantly there are events set up for multiple possible scenarios which allowed characters to react differently in these events, making the system even more expandable. As an example, there is an OnCrouch() method which usually disables movement, but if we needed to implement a character that could crawl that instruction could be easily bypassed. We didn't make use of these for character specific things, but they came in very handy when implementing sound.
public void OnCrouch()
{
if (!IsGrounded || isCrouched) return;
audioHandler.PlaySound(audioDataSO.crouchSFX);
DisableMovement();
isCrouched = true;
}
Combat
When implementing combat I kept in mind that different characters would be performing different kinds of attacks, so I created a base 'Moveset' class which contained all the different moves a player could perform along with some base functionality, then characters inherited from that script to override specific attacks.
public virtual void LightPunchInput()
{
audioHandler.attackType = "Light";
//Decide attack type
if (CheckForSpecialInput(1)) SpecialLightPunch();
else if (!locomotionScript.IsGrounded()) AerialLightPunch();
else if (locomotionScript.isCrouched) CrouchLightPunch();
else LightPunch();
}
public virtual void LightPunch()
{
audioHandler.currentClip = audioDataSO.punchSFX;
UpdateHitboxValues(hitboxScript, attackData.neutralLightPunch);
_animationsScript.PlayAttackAnimation(_animationsSO.LightNeutralPunch);
}
As an example, here's the Among Us crewmate's aerial punch. It has the special functionality of spawning a projectile as well as freezing the character in place for the length of the animation. Additionally, the projectile has a different trajectory based on whether the attack is light or heavy:

Here's how this is handled in code:
//Starts attack
public override void AerialLightPunch()
{
base.AerialLightPunch();
rb.constraints = RigidbodyConstraints2D.FreezeAll;
knifeDir = lightKnifeDirection;
knifeHitboxStat = attackData.aerialLightPunch;
}
//Gets called at the end of the attack animation
public void KnifeThrow()
{
//Create knife object
GameObject knife = Instantiate(knifePrefab, transform.position, quaternion.identity);
knife.tag = gameObject.tag;
if(faceEnemyScript.facingDirection < 0) knife.GetComponent<SpriteRenderer>().flipX = true;
//Assign hitbox values
Hitbox knifeHitbox = knife.GetComponent<Hitbox>();
knifeHitbox.audioHandler = audioHandler;
UpdateHitboxValues(knifeHitbox, knifeHitboxStat);
​ //Apply force to knife
Vector2 forceToApply = new Vector2 (knifeDir.x * faceEnemyScript.facingDirection, knifeDir.y);
knife.GetComponent<Rigidbody2D>().AddForce(forceToApply * knifeSpeed, ForceMode2D.Impulse);
//Give player control back
rb.constraints = RigidbodyConstraints2D.FreezeRotation;
}
This, again, made the process of adding new characters incredibly quick. The base of this project was so well set up that when making any additions, I was allowed to focus on the important stuff rather than worry about how to hook it up to the existing system or thinking of unintended consequences the additions might have.
Attack Data
While hitboxes themselves were driven entirely by animation we still needed to worry about the effect that hitboxes would have on enemies including:
-
Damage
-
Knockback (on hit, and on shield)
-
Hitsun (on hit, and on shield)
-
Hitspot (on hit, and on shield)
-
Shield ignore
​
At first, I handled all this through an 'AttackData' scriptable object which stored all the above listed data for every individual attack, which quickly became unsustainable.

This is when I decided it would be much more convenient to have all this data laid out in an Excel sheet. I had never tried to make a spreadsheet importer, or dealt with importing data from outside Unity for that matter, but it wasn't too hard to figure out. The data from Excel populates the scriptable object which the code can actually communicate with.

I then added buttons to import (or create) individual character's attack data from Unity. Adding attack data for a new character is now simply a matter of filling out their table in Excel and clicking a button rather than awkwardly expanding and collapsing structs in Unity to fill out their data there.
[MenuItem("Import Attack Data/ Import All")]
static void Import()
{
var excel = new ExcelImporter("Attack Data/Moveset Data/Attack Data.xlsx");
var attackAssets = DataHelper.GetAllAssetsOfType<AttackData>();
ImportAttack("AmongUs", excel, attackAssets);
ImportAttack("Kermit", excel, attackAssets);
ImportAttack("CatGirl", excel, attackAssets);
}
[MenuItem("Import Attack Data/ Import Among Us")]
static void ImportAmongUs()
{
var excelPath = new ExcelImporter("Attack Data/Moveset Data/Attack Data.xlsx");
var attackAssets = DataHelper.GetAllAssetsOfType<AttackData>();
ImportAttack("AmongUs", excelPath, attackAssets);
}
