Created
November 21, 2023 19:27
-
-
Save WolframGroetsch/018a127820fe11867e1816dc3fb08608 to your computer and use it in GitHub Desktop.
FishNet prediction movement code for KCC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| public struct PlayerCharacterInputs // This struct only handles the local input and is used to pass it to SetInput method | |
| { | |
| public float MoveAxisForward; | |
| public float MoveAxisRight; | |
| public Vector3 CameraRotationEuler; | |
| public bool JumpDown; | |
| public bool JumpHeld; | |
| public bool CrouchDown; | |
| public bool CrouchUp; | |
| public bool CrouchHeld; | |
| public bool NoClipDown; | |
| } | |
| public struct MoveData : IReplicateData | |
| { | |
| public PlayerCharacterInputs Inputs; | |
| public MoveData(PlayerCharacterInputs inputs) | |
| { | |
| Inputs = inputs; | |
| _tick = 0; | |
| } | |
| //FishNet | |
| private uint _tick; | |
| public void Dispose() { } | |
| public uint GetTick() => _tick; | |
| public void SetTick(uint value) => _tick = value; | |
| } | |
| public struct ReconcileData : IReconcileData | |
| { | |
| public Vector3 Position; | |
| public Vector3 RotationEuler; | |
| public Vector3 BaseVelocity; | |
| public bool MustUnground; | |
| public float MustUngroundTime; | |
| public bool LastMovementIterationFoundAnyGround; | |
| public CharacterTransientGroundingReport GroundingStatus; | |
| public Vector3 AttachedRigidbodyVelocity; | |
| public bool JumpConsumed; | |
| public bool DoubleJumpConsumed; | |
| public CharacterState CurrentCharacterState; | |
| public ClimbingState CurrentClimbingState; | |
| public float AnchoringTimer; | |
| public ReconcileData(Vector3 position, Vector3 rotationEuler, Vector3 velocity, | |
| bool mustUnground, float mustUngroundTime, bool lastMovementIterationFoundAnyGround, | |
| CharacterTransientGroundingReport groundingStatus, Vector3 attachedRigidbodyVelocity, | |
| bool jumpConsumed, bool doubleJumpConsumed, | |
| CharacterState currentCharacterState, ClimbingState currentClimbingState, | |
| float anchoringTimer) | |
| { | |
| Position = position; | |
| RotationEuler = rotationEuler; | |
| BaseVelocity = velocity; | |
| MustUnground = mustUnground; | |
| MustUngroundTime = mustUngroundTime; | |
| LastMovementIterationFoundAnyGround = lastMovementIterationFoundAnyGround; | |
| GroundingStatus = groundingStatus; | |
| AttachedRigidbodyVelocity = attachedRigidbodyVelocity; | |
| JumpConsumed = jumpConsumed; | |
| DoubleJumpConsumed = doubleJumpConsumed; | |
| CurrentCharacterState = currentCharacterState; | |
| CurrentClimbingState = currentClimbingState; | |
| AnchoringTimer = anchoringTimer; | |
| _tick = 0; | |
| } | |
| //FishNet | |
| private uint _tick; | |
| public void Dispose() { } | |
| public uint GetTick() => _tick; | |
| public void SetTick(uint value) => _tick = value; | |
| } | |
| public override void OnStartNetwork() | |
| { | |
| base.OnStartNetwork(); | |
| base.TimeManager.OnTick += TimeManager_OnTick; | |
| base.TimeManager.OnPostTick += TimeManager_OnPostTick; | |
| KinematicCharacterSystem.Settings.AutoSimulation = false; | |
| KinematicCharacterSystem.Settings.Interpolate = false; | |
| } | |
| public override void OnStopNetwork() | |
| { | |
| base.OnStopNetwork(); | |
| if (base.TimeManager != null) | |
| { | |
| base.TimeManager.OnTick -= TimeManager_OnTick; | |
| base.TimeManager.OnPostTick -= TimeManager_OnPostTick; | |
| } | |
| } | |
| private void TimeManager_OnTick() | |
| { | |
| Move(BuildMoveData()); | |
| // Run server sided checks | |
| if (base.IsServerStarted) | |
| { | |
| CheckForFellOffIsland(); | |
| CheckForFallDamage(); | |
| } | |
| } | |
| private void TimeManager_OnPostTick() | |
| { | |
| if (IsServerStarted && base.TimeManager.Tick % 1 == 0) // reconcile as server only every 5th tick | |
| { | |
| KinematicCharacterMotorState state = Motor.GetState(); | |
| ReconcileData rd = new ReconcileData(state.Position, state.Rotation.eulerAngles, state.BaseVelocity, | |
| state.MustUnground, state.MustUngroundTime, state.LastMovementIterationFoundAnyGround, | |
| state.GroundingStatus, state.AttachedRigidbodyVelocity, | |
| _jumpConsumed, _doubleJumpConsumed, | |
| CurrentCharacterState, _internalClimbingState, | |
| _anchoringTimer); | |
| Reconciliation(rd); | |
| } | |
| } | |
| private MoveData BuildMoveData() | |
| { | |
| if (!base.IsOwner) | |
| return default; | |
| _playerInput.GetMoveInputs(out PlayerCharacterInputs inputs); | |
| MoveData md = new MoveData(inputs); | |
| return md; | |
| } | |
| [ReplicateV2] | |
| private void Move(MoveData md, ReplicateState state = ReplicateState.Invalid, Channel channel = Channel.Unreliable) | |
| { | |
| if (!base.IsOwner && !base.IsServerStarted) | |
| { | |
| //If new data then set lastmovedata. | |
| if (state == ReplicateState.UserCreated || state == ReplicateState.ReplayedUserCreated) | |
| { | |
| _lastMoveData = md; | |
| _lastMoveData.Inputs.MoveAxisRight = md.Inputs.MoveAxisRight * predictedMovementFalloff; | |
| _lastMoveData.Inputs.MoveAxisForward = md.Inputs.MoveAxisForward * predictedMovementFalloff; | |
| } | |
| //If not new data then replay last inputs until new data arrives. | |
| else | |
| { | |
| //Last data must be set. We check here camera rotation, because if it is zero it is definitely unset. | |
| if (_lastMoveData.Inputs.CameraRotationEuler != Vector3.zero) | |
| { | |
| /* The tick will increase even if the data is unset. | |
| * Cache the tick, set md to last data, then reapply the tick. */ | |
| uint tick = md.GetTick(); | |
| md = _lastMoveData; | |
| md.SetTick(tick); | |
| } | |
| } | |
| } | |
| SetInputs(md.Inputs); | |
| float deltaTime = (float)TimeManager.TickDelta; | |
| AdriftKCCUpdater.SimulateSingleCharacter(this.Motor, deltaTime); | |
| } | |
| [ReconcileV2] | |
| private void Reconciliation(ReconcileData rd, Channel channel = Channel.Unreliable) | |
| { | |
| CurrentCharacterState = rd.CurrentCharacterState; | |
| _internalClimbingState = rd.CurrentClimbingState; | |
| KinematicCharacterMotorState state = Motor.GetState(); | |
| state.Position = rd.Position; | |
| state.Rotation = Quaternion.Euler(rd.RotationEuler.x, rd.RotationEuler.y, rd.RotationEuler.z); | |
| state.BaseVelocity = rd.BaseVelocity; | |
| state.MustUnground = rd.MustUnground; | |
| state.MustUngroundTime = rd.MustUngroundTime; | |
| state.LastMovementIterationFoundAnyGround = rd.LastMovementIterationFoundAnyGround; | |
| state.GroundingStatus = rd.GroundingStatus; | |
| state.AttachedRigidbodyVelocity = rd.AttachedRigidbodyVelocity; | |
| Motor.ApplyState(state, true); | |
| _jumpConsumed = rd.JumpConsumed; | |
| _doubleJumpConsumed = rd.DoubleJumpConsumed; | |
| _anchoringTimer = rd.AnchoringTimer; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment