diff --git a/Assets/Prefabs/Networked/NetworkedPlayer.prefab b/Assets/Prefabs/Networked/NetworkedPlayer.prefab index d223073..6c1cb8b 100644 --- a/Assets/Prefabs/Networked/NetworkedPlayer.prefab +++ b/Assets/Prefabs/Networked/NetworkedPlayer.prefab @@ -473,7 +473,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 2899378164 + GlobalObjectIdHash: 4009941625 InScenePlacedSourceGlobalObjectIdHash: 0 AlwaysReplicateAsRoot: 0 SynchronizeTransform: 1 diff --git a/Assets/Prefabs/Rope.prefab b/Assets/Prefabs/Rope.prefab index 3187be6..2f9e97c 100644 --- a/Assets/Prefabs/Rope.prefab +++ b/Assets/Prefabs/Rope.prefab @@ -48,7 +48,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 15502151 + GlobalObjectIdHash: 168437488 InScenePlacedSourceGlobalObjectIdHash: 0 AlwaysReplicateAsRoot: 0 SynchronizeTransform: 1 @@ -118,6 +118,11 @@ MonoBehaviour: ropeCollidersParent: {fileID: 144529238244638330} lineRenderer: {fileID: 901761791259710742} pullAnimationOvershootThreshold: 0.2 + k_ropeReconciliateThreshold: 2 + stateBuffer: + buffer: [] + bufferSize: 0 + reconciliateDuration: 0.3 rope: points: [] sticks: [] diff --git a/Assets/Scripts/Enemy/EnemySpawner.cs b/Assets/Scripts/Enemy/EnemySpawner.cs index 6df06ab..1ba632a 100644 --- a/Assets/Scripts/Enemy/EnemySpawner.cs +++ b/Assets/Scripts/Enemy/EnemySpawner.cs @@ -133,7 +133,6 @@ public class EnemySpawner : NetworkBehaviour void SpawnEnemy(GameObject enemyPrefab) { GameObject enemy = Instantiate(enemyPrefab, GetRandomPointOnCircle(SpawnRadius), Quaternion.identity, SpawnedEnenmyHolder.transform); - enemy.GetComponent().InitialID = idCounter++; enemy.GetComponent().Spawn(); enemy.GetComponent().targets = players.Select(x => x.transform).ToArray(); diff --git a/Assets/Scripts/Rope/GameState.cs b/Assets/Scripts/Rope/GameState.cs index cffecae..bb6868e 100644 --- a/Assets/Scripts/Rope/GameState.cs +++ b/Assets/Scripts/Rope/GameState.cs @@ -1,12 +1,25 @@ using Unity.Netcode; using UnityEngine; +// FIXME: NetworkObject.NetworkObjectId are ulong but we're sending them as int. potential problem + [System.Serializable] public struct GameState : INetworkSerializable { public int tick; public NetworkRope nrope; - public Vector2[] enemyPositions; + + /// + /// x & y are location coords. z component is the + /// of the enemy. + /// + public Vector3[] enemyPositions; + + /// + /// x & y are location coords. z component is the + /// of the player. + /// + public Vector3[] playerPositions; public override int GetHashCode() { @@ -23,6 +36,7 @@ public struct GameState : INetworkSerializable { serializer.SerializeValue(ref tick); serializer.SerializeValue(ref enemyPositions); + serializer.SerializeValue(ref playerPositions); nrope.NetworkSerialize(serializer); } } diff --git a/Assets/Scripts/Rope/RopeSimulator.cs b/Assets/Scripts/Rope/RopeSimulator.cs index 4078fee..d4ec2af 100644 --- a/Assets/Scripts/Rope/RopeSimulator.cs +++ b/Assets/Scripts/Rope/RopeSimulator.cs @@ -67,12 +67,16 @@ public class RopeSimulator : NetworkBehaviour [Header("Netcode")] private const float k_serverTickRate = 60f; private const int k_rngSeed = 6969; - public float k_ropeReconciliateThreshold = 10f; + public float k_ropeReconciliateThreshold = 1f; private System.Random rng = new System.Random(k_rngSeed); private int currentTick => NetworkManager.Singleton.NetworkTickSystem.LocalTime.Tick; [SerializeField] private CircularBuffer stateBuffer; private GameState lastReceivedServerGameState; private const int k_bufferSize = 512; + private bool isReconciliating = false; + private GameState currentRewindingState; + [SerializeField] private float reconciliateDuration = 0.1f; + private float rewindCounter = 0f; private int[] order; @@ -271,10 +275,10 @@ public class RopeSimulator : NetworkBehaviour private void DrawRope() { // Update line renderer - List positions = new List(ropeCollidersParent.childCount); - for (int i = 0; i < ropeCollidersParent.childCount; i++) + List positions = new List(this.rope.points.Length); + for (int i = 0; i < this.rope.points.Length; i++) { - positions.Add(ropeCollidersParent.GetChild(i).position); + positions.Add(this.rope.points[i].position); } lineRenderer.positionCount = positions.Count; lineRenderer.SetPositions(positions.ToArray()); @@ -297,8 +301,76 @@ public class RopeSimulator : NetworkBehaviour Rope serverRope = Rope.FromNetworkRope(serverState.nrope, distBetweenRopePoints); Rope oldLocalRope = Rope.FromNetworkRope(stateBuffer.Get(serverState.tick).nrope, distBetweenRopePoints); - float serverLocalRopeDiff = Rope.CalcDiff(serverRope, oldLocalRope); - Debug.Log($"Server to client sync error: {serverLocalRopeDiff}"); + float serverLocalRopeError = Rope.CalcDiff(serverRope, oldLocalRope); + + if (serverLocalRopeError >= k_ropeReconciliateThreshold) + { + SimpleReconcile(serverState); + } + Debug.Log($"Server to client sync error: {serverLocalRopeError}"); + } + + private void SimpleReconcile(GameState rewindState) + { + Debug.LogWarning("Reconciliate"); + this.rewindCounter = 0f; + this.isReconciliating = true; + this.currentRewindingState = rewindState; + } + + private void Reconciliate() + { + Physics2D.simulationMode = SimulationMode2D.Script; + + Debug.LogWarning("Reconciliate"); + Debug.Break(); + // Reset client to server state + GameState serverState = this.lastReceivedServerGameState; + this.rope = Rope.FromNetworkRope(serverState.nrope, distBetweenRopePoints); + Dictionary enemyByIds = new(); + Dictionary playerByIds = new(); + IEnumerable enemies = GameObject.FindObjectsByType(FindObjectsSortMode.None); + IEnumerable players = GameObject.FindObjectsByType(FindObjectsSortMode.None); + + foreach (var enemy in enemies) + enemyByIds.Add(enemy.GetComponent().NetworkObjectId, enemy.gameObject); + + foreach (var player in players) + playerByIds.Add(player.GetComponent().NetworkObjectId, player.gameObject); + + + // Re-simulate up to this client's tick + Debug.Log($"Needs to re-sim {currentTick - serverState.tick} steps"); + for (int tick = serverState.tick; tick <= currentTick; tick++) + { + GameState intermediateState = tick == serverState.tick ? serverState : stateBuffer.Get(tick); + + foreach (var enemyPos in intermediateState.enemyPositions) + { + // Find corresponding client enemy with id (z-component) + ulong enemyID = (ulong) enemyPos.z; + GameObject enemy = enemyByIds.GetValueOrDefault(enemyID); + Assert.IsNotNull(enemy, $"Server enemy with id: {enemyID} could not be found on client!"); + + enemy.transform.position = new Vector2(enemyPos.x, enemyPos.y); + } + + foreach (var playerPos in intermediateState.playerPositions) + { + // Find corresponding client player with id (z-component) + ulong playerID = (ulong) playerPos.z; + GameObject player = playerByIds.GetValueOrDefault(playerID); + Assert.IsNotNull(player, $"Server player with id: {playerID} could not be found on client!"); + + player.transform.position = new Vector2(playerPos.x, playerPos.y); + } + + this.Simulate(Time.fixedDeltaTime); + + Physics2D.Simulate(Time.fixedDeltaTime); + } + + Physics2D.simulationMode = SimulationMode2D.FixedUpdate; } private void NetworkTick() @@ -315,7 +387,8 @@ public class RopeSimulator : NetworkBehaviour GameState localState = new() { tick = currentTick, nrope = Rope.ToNetworkRope(this.rope), - enemyPositions = GameObject.FindObjectsByType(FindObjectsSortMode.None).Select(e => new Vector2(e.transform.position.x, e.transform.position.y)).ToArray() + enemyPositions = GameObject.FindObjectsByType(FindObjectsSortMode.None).Select(e => new Vector3(e.transform.position.x, e.transform.position.y, e.GetComponent().NetworkObjectId)).ToArray(), + playerPositions = GameObject.FindObjectsByType(FindObjectsSortMode.None).Select(p => new Vector3(p.transform.position.x, p.transform.position.y, p.GetComponent().NetworkObjectId)).ToArray() }; return localState; @@ -326,6 +399,42 @@ public class RopeSimulator : NetworkBehaviour if (!IsInitialized) return; + if (isReconciliating) + { + this.rope.points.First().position = start.position; + this.rope.points.First().prevPosition = start.position; + + this.rope.points.Last().position = end.position; + this.rope.points.Last().prevPosition = end.position; + + Rope rewindRope = Rope.FromNetworkRope(currentRewindingState.nrope, distBetweenRopePoints); + rewindCounter += Time.deltaTime; + float t = rewindCounter / reconciliateDuration; + for (int point = 1; point < rope.points.Length - 1; point++) + { + Vector3 newPos = Vector3.Slerp(this.rope.points[point].position, rewindRope.points[point].position, t); + + this.rope.points[point].position = newPos; + this.rope.points[point].prevPosition = newPos; + } + + Simulate(Time.deltaTime); + foreach (var point in rope.points) + { + // point.prevPosition = point.position; + } + + DrawRope(); + + if (t >= 1f) + { + rewindCounter = 0f; + isReconciliating = false; + currentRewindingState = default(GameState); + } + return; + } + colliderToSquezeForce.Clear(); rope.points.First().position = start.position; diff --git a/Assets/Scripts/Utilities/NetworkID.cs b/Assets/Scripts/Utilities/NetworkID.cs deleted file mode 100644 index e1a020c..0000000 --- a/Assets/Scripts/Utilities/NetworkID.cs +++ /dev/null @@ -1,18 +0,0 @@ -using UnityEngine; -using Unity.Netcode; - -public class NetworkID : NetworkBehaviour -{ - // Set from enemy spawner - public int InitialID = -1; - - private NetworkVariable nID = new(); - - public int ID => nID.Value; - - public override void OnNetworkSpawn() - { - if (IsServer) - nID.Value = InitialID; - } -} diff --git a/Assets/Scripts/Utilities/NetworkID.cs.meta b/Assets/Scripts/Utilities/NetworkID.cs.meta deleted file mode 100644 index 0e2ef9d..0000000 --- a/Assets/Scripts/Utilities/NetworkID.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: e071dd818c5a905209e31c2d66ea6390 \ No newline at end of file diff --git a/Assets/Scripts/Utilities/NetworkID_Inspector.cs b/Assets/Scripts/Utilities/NetworkID_Inspector.cs deleted file mode 100644 index e31c8ed..0000000 --- a/Assets/Scripts/Utilities/NetworkID_Inspector.cs +++ /dev/null @@ -1,16 +0,0 @@ -#if UNITY_EDITOR -using UnityEngine; -using UnityEditor; - -[CustomEditor(typeof(NetworkID))] -public class NetworkID_Inspector : Editor -{ - public override void OnInspectorGUI() - { - base.OnInspectorGUI(); - - EditorGUILayout.LabelField($"ID: {(target as NetworkID).ID}"); - } -} - -#endif diff --git a/Assets/Scripts/Utilities/NetworkID_Inspector.cs.meta b/Assets/Scripts/Utilities/NetworkID_Inspector.cs.meta deleted file mode 100644 index 6398067..0000000 --- a/Assets/Scripts/Utilities/NetworkID_Inspector.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: e70a0a80d88c1620c955d7bdc1efe3ee \ No newline at end of file