This guide supplements the basic prediction guide by showing how to introduce more complexities to your controls.
Be sure to review the previous guides in this section before reviewing this page.
Guide Goal
Implementing additional features into your prediction is much like you would code in a single player game, only remembering to reconcile anything that could de-synchronize the prediction.
In this guide a psuedo ground check before a jump will be added, as well a sprint function.
Sprinting and Ground Checks
First the ReplicateData needs to be updated to contain our sprint action, which will rely on a stamina mechanic. Not much changed other than we added the Sprint boolean and set it using the constructor.
After updating the ReplicateData we need to poll for the sprint key when creating the data, like you would in most games.
We are re-using methods from our previous guides so much of this should be familiar.
privateReplicateDataCreateReplicateData(){if (!base.IsOwner)returndefault; //Build the replicate data with all inputs which affect the prediction.float horizontal =Input.GetAxisRaw("Horizontal");float vertical =Input.GetAxisRaw("Vertical");/* Sprint if left shift is held. * You do not necessarily have to perform security checks here. * For example, it was mentioned sprint will rely on stamina, we * are not checking the stamina requirement here. You certainly could * as a precaution but this is only building the replicate data, not where * the data is actually executed, which is where we want * the check. */bool sprint =Input.GetKeyDown(KeyCode.LeftShift);ReplicateData md =newReplicateData(_jump, sprint, horizontal, vertical); _jump =false;return md;}
Declare a stamina float in your class.
//Current stamina for the player.privatefloat _stamina;
Now use our new Sprint bool and stamina field to apply sprinting within the replicate method.
[Replicate]privatevoidRunInputs(ReplicateData data,ReplicateState state =ReplicateState.Invalid,Channel channel =Channel.Unreliable){float delta = (float)base.TimeManager.TickDelta; //Regenerate stamina at 3f per second. _stamina += (3f* delta); //How much it cost to use sprint per delta. //This causes sprint to use stamina twice as fast //as the stamina recharges.float sprintCost = (6f* delta);Vector3 forces =newVector3(data.Horizontal,0f,data.Vertical) * _moveRate; //If sprint is held and enough stamina exist then multiple forces.if (data.Sprint&& _stamina >= sprintCost) { //Reduce stamina by cost. _stamina -= sprintCost; //Increase forces by 30%. forces *=1.3f; }/* You should check for any changes in replicate like we do * with stamina. Recall how it was said checking stamina when * gathering the inputs is not so important, but doign so in the replicate * is what grants server authority, as well makes prediction function * properly with corrections and rollbacks. *//* Now check if to jump. IsGrounded() does not exist, we're going to * pretend it uses a raycast or overlap to check. */if (data.Jump&&IsGrounded()) {Vector3 jmpFrc = (Vector3.up* _jumpForce);PredictionRigidbody.AddForce(jmpFrc,ForceMode.Impulse); } //Rest of the code remains the same.}
If a value can affect your prediction do not store it outside the replicate method, unless you are also reconciling the value. An exception applies if you are setting the value inside your replicate method.
This is a very important detail to remember, and is discussed further below.
Reconciling only a rigidbody state is very simple.
[Reconcile]privatevoidReconcileState(ReconcileData data,Channel channel =Channel.Unreliable){ //Call reconcile on your PredictionRigidbody field passing in //values from data.PredictionRigidbody.Reconcile(data.PredictionRigidbody);}
If you are using multiple rigidbodies you at the very least need to reconcile their states as well. You can do so quickly by adding a RigidbodyState for each rigidbody to your reconcile.
If you are also applying forces to these rigidbodies be sure to use PredictionRigidbody with them, and reconcile the PredictionRigidbody instead of RigidbodyState.
Changes To Reconcile
Because objects can reconcile to previous states it's fundamental to also reconcile any values stored outside the replicate method. Imagine if you had 10f stamina, enough to sprint, and did so successfully on the server and owner. After your sprint you only had 1f stamina, not enough to sprint further.
If you were to reconcile without resetting stamina to it's previous values then you would still be at 1f stamina after reconciling. Your replayed inputs, which previously allowed the sprint, would not sprint because you now lacked the needed stamina. In result of this, you would have a de-synchronization which would most likely be seen as jitter.
Including more variables in your prediction is fortunately easy enough. All you have to do is update your reconcile to include states or your new values or variables.
Added to our ReconcileData structure is a Stamina float.