Awesome
Latest tag change notes
v2.0.6 new features
- Added back transformation buff.
- Added support for Atk+InvSlotC input pattern.
- Added BomberGoblin and SpearWoman.
- Added gauge interpolation.
v2.0.4 new features
- Enhanced timed-bomb implementation.
- Enhanced zero-back-vision NPC handling.
- Added FinishedLvOption.
- Implemented multi-color hpbar for hp cap > 100
v2.0.0 new features
- Drastically reduced memory and CPU usage per battle, with enhanced rollback-compatible determinism of collision system and more versatile game mechanics
- Auto-rejoin after accidental TCP disconnection
- Added flying NPCs and jumper trap
- Reduced static trap memory footprint in game mechanics stepping
- Refactored trigger mechanism for clearance and easy of configuration
- Added render-only player position interpolation for smoother graphics during resync
- Added gauged inventory slot for super skills
v1.6.7 new features
- Improved frontend lockstep handling.
- Improved backend downsync nonblocking handling.
- Added use of
chaserRenderFrameIdLowerBound
. - Added
potential graphical inconsistency indicators
onNetworkDoctorInfo
panel. - Enhanced jump holding mechanics for easier netcode prediction.
- Applied jump startup for slipping and air-jump, such that
input delay = 2 frames i.e. ~32ms
has fast graphical response (i.e. startup animation) but slow collision system response (i.e. after startup frames). - Improved damaged-anim handling.
- Added support for
localExtraInputDelayFrames
.- Rule of thumb: the same
renderFrameId
MUST take the sameinputFrameId
for all peers regardless of their individualinputDelay
s. - Therefore, if individual
inputDelay
s are different, the trick would be oninputFrameId
generation, i.e. for each peer if itsinputDelay
suddenly goes up frominputDelayStd=2
toinputDelayDynamic=4
, then its generatedinputFrameId
atrenderFrameId=242
should go up fromtoGenerateInputFrameIdOld = (renderFrameId >> 2) = 60
totoGenerateInputFrameIdNew = ((renderFrameId+(inputDelayDynamic-inputDelayStd)) >> 2)=61
-- whilelocalRequiredInputFrameId = ((renderFrameId-inputDelayStd) >> 2)
is unchanged, hencetoGenerateInputFrameIdNew
is to be used by a later renderFrame, making a feel of "larger input-to-impact-delay".
- Rule of thumb: the same
- Added support for "no player input overwriting" on backend and option for "useFreezingLockStep" on frontend.
Please checkout the demo video on YouTube(basic ops, field tests) or BaiduNetDisk(basic ops, field tests) (network setup was 4g Android v.s. Wifi PC via internet while UDP peer-to-peer holepunch failed, input delay = 2 frames i.e. ~32ms).
What's this project?
It's a Unity version of DelayNoMore, a Multiplayer Platformer game demo on websocket with delayed-input Rollback Netcode inspired by GGPO -- but with the backend also rebuilt in C#.
(battle between 4g Android v.s. Wifi Android via internet while UDP peer-to-peer holepunch failed, input delay = 2 frames i.e. ~32ms, the following GIF is converted from this video, please also checkout these older version videos, this dedicated slope dynamics video and this dedicated multi-enemy-head-walk video)
(demo for freezer buff since v1.2.7, original video here)
Notable Features (by far, would add more in the future)
- Automatic correction for "slow ticker", especially "active slow ticker" which is well-known to be a headache for input synchronization
- Peer-to-peer UDP holepunching whenever possible, and will fallback to use the backend as a UDP relay/tunnel if holepunching failed for any participant (kindly note that UDP is always used along side with WebSocket, where the latter is a golden source of frame info)
- Rollback compatible NPC patrolling and vision reaction
- Rollback compatible static and dynamic traps, including a WYSIWYG notation support in Tiled editor (since v1.1.4)
- Rollback compatible monodirectional platform which also supports slip-down operation
- Simple slope dynamics
- Standing and walking on multiple enemy-heads
(a typical framelog comparison from 2 peers)
(where to find framelog files)
How does it work to synchronize across multiple players?
(how input delay roughly works)
(how rollback-and-chase in this project roughly works)
(though using C# for both backend & frontend now, the idea to avoid floating err remains the same as shown below)
1. Building & running
1.1 Tools to install
Backend
proj-root/backend> dotnet run
Frontend
Open OfflineScene
to try out basic operations.
Open LoginScene
after launching the backend to try out multiplayer mode. Available test accounts are listed in DevEnvResources.sqlite. The steps are very similar to that of DelayNoMore CocosCreator version.
Unit test
Referencing this document from Microsoft by far.
2. Thanks
- To dravenx for providing the spikey-stuff.
3. Changing endpoints of UnityHub download and changing its http(s) proxy
Kindly note that the proxy setting is not very helpful here when download is slow (alternatively, sometimes the download is just timed out due to DNS issue, you might also wanna have a try on changing DNS only), changing the endpoints from https
to http
is critical.
References
4. How to properly measure input-prediction performance in a reproducible manner?
It's always non-trivial to mock fluctuating network behaviours, and in this game we might be interested in testing the performance of different input-prediction algorithms, therefore we'd like to mock DETERMINISTIC inputs for a single player including
a)
the initial map setup (from tmx), andb)
the initial character choices of all players, andc)
receivedRoomDownsyncFrame
s,InputDownsyncFrame
s (from websocket) andInputUpsyncFrame
s (from UDP peers) at EXACTLY THE SAME TIMINGS for different runs of different algorithms in test.
The first two, i.e. a)
& b)
are easy to mock and c)
is possible by mocking OnlineMapController.pollAndHandleWsRecvBuffer and OnlineMapController.pollAndHandleUdpRecvBuffer.
I should've provided an example of this type of test for the alleged good performance of my algorithm, especially for
- UpdateInputFrameInPlaceUponDynamics, and
- processInertiaWalking , but the performance by far is so nice even in unsuccessful UDP hole-punching cases, thus it's left out as a future roadmap item :)
Logging performance concern
String.Format(...)
can be a serious performance issue when used too frequently. Please remove/comment them when you notice a lag or CPU spike possibly coupled with an intense logging period (it's always recommended to profile beforehand for proof).
Is it possible to remove all "forceConfirmation"s if player input overwriting is unwanted?
Yes it's possible to remove/disable both "type#1" and "type#3" in backend/Battle/Room.cs
. However, it's highly recommended that you reserve the backend dynamics and downsync the RoomDownsyncFrame calculated by backend to all frontends periodically -- the frontend AbstractMapController.onRoomDownsyncFrame
can handle correction of historic render frames without issue.
The root cause of the need for such periodic RoomDownsyncFrame downsync is that the physics engine uses floating point numbers, and I'm not a fan of determinisitc floating point approach (i.e. there're tradeoffs). If this project is an old style fighting game, then I can rewrite its physics to use rectilinear rectangles only, thus integer only (including snapping) -- yet I want slopes and rotations in the game :)
How to find all spots of input predictions?
proj-root> grep -ri "shouldPredictBtnAHold" --color ./frontend/Assets/Scripts/
proj-root> grep -ri "shouldPredictBtnAHold" --color ./backend/
proj-root> grep -ri "shouldPredictBtnAHold" --color ./shared/
FAQ
Please refer to FAQ.md.