not OP but the character controllers roblox provides to do custom behavior with don’t seem compatible with server authority. I ended up making my own force based character controller which is easier than it sounds and solved basically all my issues relating to weird P2P behavior (my game is heavily based around being able to grab opponents and drag them around, humanoids have a lot of stuff that makes this weird).
Basically all you need for a basic fully custom controller is to calculate a force to keep the HRP from falling over, calculate a hipheight force, calculate movement forces, finally calculate jump. Both keeping the HRP upright and hipheight can be done with a PD controller. I will say that I had to have separate logic for keeping the hipheight constant while standing (PD controller) vs if you fall from a really tall height (I found no matter how I tuned the PD controller it would overcorrect hard and shoot you upwards, now if you have a certain amount of downwards velocity I do a much less aggressive PD controller until you return to within 0.5 studs of the intended hipheight at which point I swap back to standing PD). The movement is pretty basic vector math, you can use IAS to get a Direction2D from WASD which is already in the perfect format, then you just apply said Direction2D to the player as a force. Finally for jump just on button press apply an upwards impulse if the hipheight raycast is finding ground.
There is obviously more you’d have to do to specialize a system like this to your game but it’s much easier to work with in my opinion and gives you full control over player physics
“not OP but the character controllers roblox provides to do custom behavior with don’t seem compatible with server authority. I ended up making my own force based character controller which is easier than it sounds and solved basically all my issues relating to weird P2P behavior (my game is heavily based around being able to grab opponents and drag them around, humanoids have a lot of stuff that makes this weird).” You got a video
Sorry for never getting back to you. Work gets unpredictable.
Attributes and attributes only. I use a HumanoidRootPart to match the R6 convention, but collision is handled by an invisible collider pill. The StarterCharacter is a mesh rig obtained from here.
Also, where do you store authoritative health and handle damage, death, and respawning?
Attributes for the former two; damage + health is predicted. Latter two is handled by the server, decorated by the client outside of simulation (since they’re guaranteed dead if the entity doesn’t rollback their death at the start of the frame). Respawning done by the server.
Yes. That’s the load that my Crunchyroll wrapper bears, not I.
You can simply just do it. If you wire everything up with rollback in mind (client and server agree on the timing and the predicted/simulated end-result), there’s almost nothing special you have to deal with outside of some replication and data access constraints. Just follow the wiki and add your Server Authority bells, whistles, and other doo-dads along the way.
The shaking and jittering occurs when you use a Humanoid alongside the controller; I noticed this by accident when developing my lobby system and accidentally forgot to kill the Humanoid’s effects on the rig. Otherwise, it’s likely related to how you handle inputs on the client versus the server.
Again, I think the reason I’ve encountered the least simulation issues is because I simply decided to never use a Humanoid within my experiences; ever.
They are very compatible. There’s no real “buts” about it, outside of maybe acceleration/deceleration, but that’s nothing your render entity shouldn’t handle:
my state animator needs some work in the rollback department still
Though, maybe I’d advise doing the raycasting yourself for the Ground/Climb/Swim sensors.
Half of these things are done by the physics controller and its components (e.g. MoveSpeed, MoveSpeedFactor, Friction, BalanceRigidityEnabled). Good thinking overall though, would be a solid approach if they didn’t exist. I like the idea.
Your game is so beautiful! I love the design and everything about it. I’m trying to make a similar game inspired by one of my childhood games, Lost Saga. It’s like a Street Fighter-style game. I also sent you a friend request! I work a full-time job, but I dedicate whatever free time I have left to developing my game
Hello… I’m also curious as to how I can make a ControllerManager model without it getting really buggy.
In fact I’m currently in the midst of doing that, including a hybrid approach that takes some of the features of the ControllerManager and it’s child managers, and replaces some features with custom ones.
Though, it’s not as easy as I thought it would, I keep finding myself having a bunch of unwanted rollbacks.
Because your goal isn’t to have ZERO rollbacks. That is impossible unless you’re playing on LAN, and even then, things can get weird on the super-fine edge cases. The goal is to have as little rollbacks as possible, little enough to the point where you can mask up the minor inconsistencies like 0.1stud jitters without worrying about interference from another destructive rollback.
Do not worry about the noise, worry about minimizing it, and tidy-up what’s left.
This is really awesome, it seems like I’ve been using server authority the wrong way for my arena fighter project..
Currently I’m using attributes for server-authoritative and predicted values, and never had to use snapshots. I use roblox’s animator but with custom client rendering with BulkMoveTo, and the client fake models just get interpolated to the real entity, and copies the animations that the real entity plays (i use a Humanoid with EvaluateStateMachine disabled and a custom character controller using physics forces).
I have some questions that I would like to ask, now I have no idea that I was supposed to do any logic in RunService.Rollback or use any snapshots because I had everything stored in attributes, all of the abilities & status effects have their instance in the character model that is also predicted and they can set their own attributes so this leads to a LOT of instances that need to be predicted the more entities there are (yes, i am predicting every entity on the client), all of time-based logic that are predicted (like ability duration or status effect duration) are stored in their instance as an attribute that goes from 0 and is incremented by deltaTime every frame, I don’t know if this is good or not.. As for hit detection, I’m not sure if there’s any fancy stuff that I need to account for like rollbacks during hit detection or validation, I just approach it like a normal hit detection on both client and server, using Bolt. Hit detection happens after a delay in the ability logic (like duration > 0.2s or some arbitrary value depending on the ability itself), and then the hit gets validated. I have no idea if I’m doing it the right way or wrong.
I was wondering, if there’s a more efficient way or right way to go about all of the above?
One last question, I am using roblox’s animation system for now, since I couldn’t figure out how to use crunchyroll or another custom animation solver correctly. Do animations need to get corrected by the server as well, or only re-winded back with time() when a rollback occurs? If it’s the latter then it might be the only reason that I use snapshots. Sorry if I’m using any wrong terms here I’m fairly new to this..
Hi, regarding your first video on server authority, how do you handle the physics? I’m curious because it feels really deterministic like you are in control and not random
Let’s say that I have an attack called “Punch” that plays a “whoosh.ogg” sound effect at frame 15, with some effects at frames sixteen, eighteen, and twenty. You can’t just set an attribute that says “WhooshPlayed” and “Frame[X]Played” because you’ll run out of bandwidth very fast. Another caveat is that sometimes you don’t need data to rollback at all because some are guaranteed to be valid by the time frame X passes by virtue of the attack not being canceled at frame zero (the entity mispredicted getting hit or being in some other invalid state); even then, if the attack is canceled before the effect at frame X or after frame X but before the effect destructs, then you no-op (or in the worst case, early-destruct or reconstruct a dead runner). Using attributes also has its own pitfalls that you have to worry about, and the biggest one is that you can’t control what stays and what goes without taking unnecessarily large debt. My source: I found this out the hard way and had to rewrite two months’ worth of work.
This is where snapshots play their role. Snapshots are used when you have data in which its origin has a deterministic root, but the data (or functionality, also known as the side-effect) resulting from the original data is not deterministic. You have full control over what stays and goes, but that also means that you have more explodey-ammo for a footgun the size of Maine. Snapshots are good for keeping track of attacked entities, entity state transitions, client visuals tied to simulation, the list goes on. Basically, as long as it’s ephemeral, you snapshot it (for like, 99.5% of cases). This is mostly to make sure that the side-effects don’t fire twice because something else rollbacked on the same frame your input actually succeeded.
Also, please, for the sake of me being able to address things without the uncertainty of whether the quote captures your complete thought, use more periods and semicolons.
Set, never increment. Instead of using a timer, just capture the start time and obtain the time elapsed by subtracting it from time() or whatever deterministic method you use. Not only is it less replication, but in a server-authoritative vacuum, incrementing causes one to lose sight of when their side-effects actually started; this is a notable problem that becomes relevant if a player ends up rolling back past when an ability is actually cast.
If you’re using that delay as a workaround for simulation, it’s not the best, but it’ll do. When you add delay, it has to either be as a form of “cast time” or directly integrated into whatever fires the side-effects from the players’ inputs. I’d be more concerned on what happens on skills with low cast times. Overall, if it’s not a problem, it’s not a problem.
I have never used Roblox’s animation system with Server Authority. Since animations are usually tied to some kind of timestamp attribute (or at the minimum, something deterministic), I find there’s no real need to animate on the server unless having precise per-limb hitboxes is a requirement like in a shooter game. In my project, I just rollback the playing animation queue on the client by keeping track of what identifiers are playing (and when they were first played to re-derive the current TimePosition) at a given frame. That, plus some interpolation.
If I wanted to verify whether a predicted hit truly hit a limb on the server, I’d probably just go the full mile and just estimate the limb transformations from the animations that should be running by virtue of the character’s state. Though, in my game, I just use some SAT tomfoolery for the [hit/hurt]boxes since I don’t know enough as to how instance stitching works under-the-hood and that makes me angry (but in reality it’s because of replication and performance costs ).
Physics constraint-movers. During simulation, I write to each constraint-mover every frame (custom knockback paths a la Dragon Ball Budokai/Xenoverse series) when using knockback and forces that move the player during a skill, I snapshot in case of rollback and merge proper snapshot attributes during the occurrence. Skills are never something like “always move forward relative to the mouse,” they are “impulse on frame Startup+N, impulse on frame Startup+M,” which is why many attacks are landing-cancelable if the collider ends up landing.
I also make sure to set the CustomPhysicalProperties to disable elasticity on the character collider to successfully disable that “merge-into-the-floor” bounce as long the entity is not in HKD/SKD. If for some reason a skill does a crazy wall-bounce, Knockback also supports extending the motion to emulate a bounce.
This took me a few hours to write due to quite a few interruptions; I lost my train of thought several times. If I made any mistakes in my explanations, my apologies.
I see. Looks like I wasn’t that off track after all.
I should probably migrate from the Roblox animation system to use a custom solver instead, I was actually over-complicating it the first time. So, just to get things clear, I can use snapshots to store animation data like alpha (or timeposition) speed, weight, etc.. right? Just need to make sure.
And I also had an “Active” attribute which is the active state of an Ability, I can use snapshots to store the states instead of attributes in this case?
Greatly appreciate your reply! And also my apologies for the little grammar mistakes, I’m too used to typing informally
To use the word “should” is a bit much, wouldn’t you say?
If the AnimationTracks given from the Roblox API is enough for your needs, then use the AnimationTracks. Making and/or using a custom animation solver like MyAnimator or Crunchyroll means that you’re remaking the entire animation stack from bottom-to-top. Roblox did a very good job at separating the interconnectedness of a lot of animation tricks that are purely dependent on manipulating the weights of keyframes, which you’ll have to bear yourself. At the same time, it gives you the freedom to make the most powerful and robust animation system to ever be conceived by man.
With over-complicating being your concern, I advise thinking about what you need for your system, not what you want. I need what I need because I’m open-sourcing my work as an easily-accessible, non-opinionated “do-all” framework.
Pretty much everything you’ve said has been my experience over the past two months. Something else I’d throw out there is just, actually invest in debug tools ahead of time. Gizmos and stats as many as possible just track and visualize everything you can. Debugging a misprediction loop is so much more difficult when you don’t even know what’s happening. Especially considering studio testing seems to have a bit of a bug where you’re given a negative offset, causing many mispredictions.
If you want to get around this, there’s only one way, and that’s your great friend: bit flags.
It’s hard for me to understate how unfortunately necessary these are. For reference, Roblox’s Avatar Abilities does the same thing. You just don’t have the bandwidth, and I’m normally anti schema based compression for the purpose of networking, but here it’s surprisingly warranted and imo practically required.