This commit is contained in:
BOTAlex 2024-03-27 20:13:34 +01:00
commit a86576068f
9 changed files with 138 additions and 49 deletions

View File

@ -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

View File

@ -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: []

View File

@ -133,7 +133,6 @@ public class EnemySpawner : NetworkBehaviour
void SpawnEnemy(GameObject enemyPrefab)
{
GameObject enemy = Instantiate(enemyPrefab, GetRandomPointOnCircle(SpawnRadius), Quaternion.identity, SpawnedEnenmyHolder.transform);
enemy.GetComponent<NetworkID>().InitialID = idCounter++;
enemy.GetComponent<NetworkObject>().Spawn();
enemy.GetComponent<EnemyPathFinding>().targets = players.Select(x => x.transform).ToArray();

View File

@ -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;
/// <summary>
/// x & y are location coords. z component is the <see cref="NetworkID.ID"/>
/// of the enemy.
/// </summary>
public Vector3[] enemyPositions;
/// <summary>
/// x & y are location coords. z component is the <see cref="NetworkID.ID"/>
/// of the player.
/// </summary>
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);
}
}

View File

@ -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<GameState> 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<Vector3> positions = new List<Vector3>(ropeCollidersParent.childCount);
for (int i = 0; i < ropeCollidersParent.childCount; i++)
List<Vector3> positions = new List<Vector3>(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<ulong, GameObject> enemyByIds = new();
Dictionary<ulong, GameObject> playerByIds = new();
IEnumerable<EnemyPathFinding> enemies = GameObject.FindObjectsByType<EnemyPathFinding>(FindObjectsSortMode.None);
IEnumerable<PlayerMovement> players = GameObject.FindObjectsByType<PlayerMovement>(FindObjectsSortMode.None);
foreach (var enemy in enemies)
enemyByIds.Add(enemy.GetComponent<NetworkObject>().NetworkObjectId, enemy.gameObject);
foreach (var player in players)
playerByIds.Add(player.GetComponent<NetworkObject>().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<EnemyPathFinding>(FindObjectsSortMode.None).Select(e => new Vector2(e.transform.position.x, e.transform.position.y)).ToArray()
enemyPositions = GameObject.FindObjectsByType<EnemyPathFinding>(FindObjectsSortMode.None).Select(e => new Vector3(e.transform.position.x, e.transform.position.y, e.GetComponent<NetworkObject>().NetworkObjectId)).ToArray(),
playerPositions = GameObject.FindObjectsByType<PlayerMovement>(FindObjectsSortMode.None).Select(p => new Vector3(p.transform.position.x, p.transform.position.y, p.GetComponent<NetworkObject>().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;

View File

@ -1,18 +0,0 @@
using UnityEngine;
using Unity.Netcode;
public class NetworkID : NetworkBehaviour
{
// Set from enemy spawner
public int InitialID = -1;
private NetworkVariable<int> nID = new();
public int ID => nID.Value;
public override void OnNetworkSpawn()
{
if (IsServer)
nID.Value = InitialID;
}
}

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: e071dd818c5a905209e31c2d66ea6390

View File

@ -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

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: e70a0a80d88c1620c955d7bdc1efe3ee