NAIR
Fighting Game with Rollback Netcode
NAIR is an exercise in intentional, minimalist game design and controlled scope.
Originally developed and released in Summer, 2019, I have continually added to the game in short sprints, turning it into what it is today.
The game boasts top of the line netcode, over 3000 players across the world, and frequent tournaments with community-donated prizes.
NAIR makes frequent appearances at local trade shows, has featured in the news, is currently reviewed 95% positively on Steam, and has gone viral several times.
Below, you'll find a rough project timeline, code snippets, & testimonials.
Video provided by Abook, a player in our Discord.
Rough Timeline:
NAIR has grown and changed over time.
July 2019 : Original game is developed and released August 1st on Itch.io. This version has had its source code released.
January 2020 : Work on Unity enhanced port begins. Project dropped at the onset of COVID-19.
May 2021 : Work on Unity port resumes, this time with a focus on rollback netcode. Released in june on Itch.io for free.
August 2021 : Game and netcode is ported to Steam, released mid-September.
August 2022 : NAIR is featured at Super Smash Con 2022, one of the largest fighting game events around.
December 2022 : Sequel, DAIR, announced.
August 2023 : DAIR, released. NAIR updated to support cross-platform multiplayer with both DAIR and itch.io users. Soundtrack added, composed by the talented Funbil. Both games are featured again at Super Smash Con 2023.
August 2024 : 5 year anniversary update, featuring user-driven custom content and game modes. Game runs its largest ever tournament at SuperNova (formerly Super Smash Con). Sequel, GRAB, announced.
Throughout the Steam release and since, I've used Trello to keep myself focused and organized.
Development has been ongoing with regular content updates, such as:
• Cross-platform online play
• Data-driven, player modifiable custom game modes that also work online, even if only 1 player has the files
• Controller rebinding, macros, etc
• Gameplay and balance tweaks leading to a faster, more aggressive (and fun) meta
Reception:
NAIR has been played by thousands of people, has had 2 successful releases, and currently holds a 95% Very Positive rating on Steam. The game has received almost universal praise for its movement, pace, fun factor, art style, and everything else.
For more information, check out:
The original trailer
This review by YouTuber Big Yellow
This news article about the launch
This interview I gave shortly after launch
This video interview from a Game Fest in 2022
One of the game's achievements going viral
Tech:
NAIR was originally made in Monogame, and later ported to Unity to save time. Unity was chosen becuase of its shared programming language, C#.
For more information about the game's programming, you can check out the original version's source code.
The netcode utilizes GGPO, a C++ rollback library, and a Unity-focused C# wrapper. Both of these needed to be modified by me to support external networking services, such as Steam, and to keep
it up and running at all. GGPO ships with built-in UDP code, which needed to be scrapped. Instead, I define my own networking calls in NAIR's network manager, and pass function pointers through the wrapper, which has also been modified.
On the gameplay side, let me talk about one of my favorite small optimizations I made to get NAIR running online.
The original version of NAIR allowed full analog control of the character with your stick, allowing the player to control
things like aerial drift, run speeds, airdodge and wavedash lengths, and more. However, since I had to transmit the player's inputs over the wire, storing it as two floats didn't leave much room for buttons. I managed to compress these values into two bytes.
I do this by representing the x and y values as ranges 0-200, with 0-100 representing -1.00 to 0.00 and 100-200 representing 0.00 to 1.00. I lossily throw out anything beyond 2 decimals of precision, and this still represents a wide enough
range to not have any observable impact on gameplay. If it did, though, this change also does not go into effect on offline matches. The code looks like this:
"restartInput" and "restartDetected" are two bools I used to track if either player wanted to start a new match- there's a small amount of lag at the start of a new GGPO session while it syncs with the opponent. Most fighting games cover this up with a cutscene, but I didn't have that luxury, nor did I want players to have to wait for new games as my game's matches can be fast. I instead opted to make the entire game state restartable within the game loop. I ended up needing to rollback-proof bits of the UI itself, as well as the restart and quit requests. This was one of the hardest things to get feeling right, and I didn't get it flawless until update 3.1.0, with some help from a beta tester on the game Rivals of Aether. The key ended up being to separate the restart "inputs" from separate trackers in the game state class, since GGPO syncs these two data sets differently. The code looks like this:
Regarding the text if-statement, when a player disconnects online, I place the remaining player in an offline match against a dummy. Despite closing GGPO, this match is in the exact state the online match was left in, and the transition is generally seamless. I frequently ignore calls to change the message when this happens, as I don't want the player believing they are still in a match.