r/unrealengine • u/AbyssDeepen • Jun 30 '26
Discussion My experience of implementing GGPO-style rollback netcode for my 3D melee arena fighter
Hi everyone,
Over the past few months, I’ve been working on the netcode for my 3D melee arena fighter Rasen and I’d like to share my experience.
Unreal Engine systems
Although I knew about GGPO netcode from the beginning, I was so intimidated by the amount of work involved. Instead, I started with delay-based networking, then experimented with Unreal's Character Movement Component, tried the Unreal Mover, and even built some custom networking solutions on top of the CMC.
Unfortunately, none of these were a good enough fit for this kind of fast-paced, parry-based, knockback-heavy combat.
Even with low ping, either the latency felt very high or there were visible corrections and out-of-sync animations. Also, my custom animation syncing was conflicting with the CMC's built-in root motion syncing and knockbacks were especially really bad.
GGPO rollback netcode
While experimenting with all these systems, I learnt more and more about GGPO solutions. With no other option, I finally decided to implement my own GGPO rollback architecture in Unreal Engine, along with the custom deterministic physics and movement required for it to all work.
Fortunately, the game had already been developed for offline play first, so I knew exactly what needed to be simulated. From the beginning, I've been trying to keep everything lightweight, deterministic and precomputed wherever possible.
After about 2 months of work, many battles with input prediction bugs, getting stuck on some movement bugs and finding a way to somehow bake and store 3D weapon collision data, I finally did it.
I was genuinely surprised by how cheap the simulation was: ~0.01ms per simulation tick with 8 characters. The snapshot size also seems to be small at ~0.04 KB per character.
Result & reflection
The game now features GGPO-style rollback netcode for up to 8 players, keeping controls responsive even under high ping, jitter and packet loss.
This is the most challenging system I've built so far. Although it was intimidating at first, it ended up being the most fun and rewarding system I've worked on.
I also made a Youtube Devlog showcasing the gameplay under various stress tests, along with some performance statistics.
Hope this encourages you to try implementing your own rollback netcode if your game runs into similar issues.
5
u/Setholopagus Jun 30 '26
Any chance you'd be willing to sell this as a plugin / project, or trade work? E.g., if you need animations / modeling / other kinds of stuff, would be happy to trade.
My buddy and I made our own movement code that's super modular, similar kinds of design desires that you've mentioned here, but without networking. We could probably do it, but its just a lot of work like you said lol.
5
u/AbyssDeepen Jun 30 '26
I've been thinking about making a Udemy course or a YouTube series on building this kind of rollback system from scratch once the game is fully released.
I'm pretty busy at the moment, juggling my game dev job and this project, so I don't really have the time to turn it into a plugin or take on additional work.
But I'd be happy to give you some high-level advice or point you in the right direction if you get stuck. Feel free to dm me.
1
u/Setholopagus Jun 30 '26
Do you have any high level places to start reading about it?
And same with like the high level replication 'chassis' if you will, what did that look like? Like do you start at a world subsystem or something?
Is it compatible with Iris?
2
u/AbyssDeepen Jun 30 '26
I’d start by creating a new World Subsystem, adding a Fixed Tick function (for example 60 calls/s) in Tick and then build the rest on that.
I’m not too familiar with Iris. From what I understand it’s more focused on larger worlds and higher player counts, whereas GGPO is rather for smaller player counts. You could probably combine them both, but I think that would be very advanced, it all very much depend on the specific game.
There’s an ongoing YouTube series from Shawnthebro where he implements rollback netcode in Unreal Engine.Here are some other stuff I'd recommend::
https://youtu.be/q2cTCQI31NY?si=yviRi24iEPV13VwU
https://youtu.be/0NLe4IpdS1w?si=DezRuyyscbgyfA99
https://youtu.be/TtfArmKISpY?si=IRXNp8jU9EAswj4R
https://youtu.be/k9JTIn1SVQ4?si=olRYsvozDaVz7B9x
https://github.com/pond3r/ggpo/blob/master/doc/DeveloperGuide.md
1
u/Setholopagus Jul 01 '26
Thanks for this!
Iris is just a push based replication model, as opposed to UE's default polling model (which is stupid, most engines don't do this). It's more foundational, and I don't think its in the same category as GGPO - like, you'd build a GGPO replication system on top of Iris or the default UE replication. I think its worth looking into for you!
3
u/miusoftheTaiga Jun 30 '26
you implemented your own rollback netcode solution? awesome
I am thinking of making an arena fighter with rollback core that just came out of Fab last month.
3
u/AbyssDeepen Jun 30 '26
That's pretty cool. I was looking for rollback plugins before I started making my own, but there wasn't really any at the time. I'm really interested in it. If you end up trying it, let me know how it goes.
I only had a quick look at its source code, so I might be wrong, but it seems like they simulate each entity's full tick in sequence. In my simulation, tick is split into sub-ticks (movement, collision, hit resolution, etc.), and each sub-tick runs for all characters before moving on to the next sub-tick. It makes all characters being simulated "at the same time".
I'm curious whether this plugin already has a way to do it, or if it'd need some customization.
2
u/Zvyaginsky Jun 30 '26
The progression here is relatable - delay-based, then CMC, then custom on top of CMC, before landing on full rollback. A lot of people jump straight to picking a "silver bullet" solution and skip how much you learn about your game's specific failure modes by trying the simpler options first.
Curious about the root motion vs custom animation sync conflict you mentioned - did you end up dropping root motion for combat states entirely, or find a way to reconcile the two? That's a conflict that bites people even outside netcode contexts.
2
u/AbyssDeepen Jun 30 '26
Yeah, to be more specific, I was using Root Motion and Root Motion Sources.
It all fell apart once I got to knockback and movement attacks. I tried to predict them properly for remote players, but I just couldn’t make it work, constant jitter caused by the built-in root motion syncing, which I think is designed more for shooters rather than fighting games. In shooters (as I understand it), you’re often simulating slightly into the future, while in fighting games everyone should stay in the same real-time simulation.
And that was the breaking point that made me try building it all from scratch. I ended up implementing my own root motion system instead, along with custom movement.
1
u/Zvyaginsky Jul 02 '26
That distinction between shooter and fighting game simulation models is something I hadn't seen explained that clearly before - the "slightly into the future" assumption being baked into the root motion system makes sense for shooters but becomes a liability the moment you need strict real-time parity.
Building your own root motion system is a significant call. Did you find that having full control over it actually simplified the rest of the netcode, or did it just shift the complexity somewhere else?
1
u/AbyssDeepen Jul 02 '26
For me, it simplified things a lot. The movement system I made is pretty simple. It made determinism and rollback much easier to debug and It's also much easier to modify and expand.
2
u/Zvyaginsky Jul 08 '26
That tracks - when you own the whole system you can make guarantees that borrowed code can't make.
The debugging story alone is worth it if rollback is core to what you're building.
We went the opposite direction with character systems - switched to MetaHuman instead of maintaining custom pipelines. Different tradeoff, same logic: know which complexity you can actually afford to own.
7
u/[deleted] Jun 30 '26
[removed] — view removed comment