using FishNet; using FishNet.Object; using FishNet.Object.Prediction; using FishNet.Transporting; using UnityEngine; /* * * See TransformPrediction.cs for more detailed notes. * */ namespace FishNet.Example.Prediction.CharacterControllers { public class CharacterControllerPrediction : NetworkBehaviour { #region Types. public struct MoveData : IReplicateData { public float Horizontal; public float Vertical; 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 Quaternion Rotation; public ReconcileData(Vector3 position, Quaternion rotation) { Position = position; Rotation = rotation; _tick = 0; } private uint _tick; public void Dispose() { } public uint GetTick() => _tick; public void SetTick(uint value) => _tick = value; } #endregion #region Serialized. [SerializeField] private float _moveRate = 5f; #endregion #region Private. private CharacterController _characterController; #endregion private void Awake() { InstanceFinder.TimeManager.OnTick += TimeManager_OnTick; _characterController = GetComponent(); } public override void OnStartClient() { base.OnStartClient(); _characterController.enabled = (base.IsServer || base.IsOwner); } private void OnDestroy() { if (InstanceFinder.TimeManager != null) { InstanceFinder.TimeManager.OnTick -= TimeManager_OnTick; } } private void TimeManager_OnTick() { if (base.IsOwner) { Reconciliation(default, false); CheckInput(out MoveData md); Move(md, false); } if (base.IsServer) { Move(default, true); ReconcileData rd = new ReconcileData(transform.position, transform.rotation); Reconciliation(rd, true); } } private void CheckInput(out MoveData md) { md = default; float horizontal = Input.GetAxisRaw("Horizontal"); float vertical = Input.GetAxisRaw("Vertical"); if (horizontal == 0f && vertical == 0f) return; md = new MoveData() { Horizontal = horizontal, Vertical = vertical }; } [Replicate] private void Move(MoveData md, bool asServer, Channel channel = Channel.Unreliable, bool replaying = false) { Vector3 move = new Vector3(md.Horizontal, 0f, md.Vertical).normalized + new Vector3(0f, Physics.gravity.y, 0f); _characterController.Move(move * _moveRate * (float)base.TimeManager.TickDelta); } [Reconcile] private void Reconciliation(ReconcileData rd, bool asServer, Channel channel = Channel.Unreliable) { transform.position = rd.Position; transform.rotation = rd.Rotation; } } }