2024-02-02 21:03:44 +01:00
|
|
|
using UnityEngine;
|
2024-02-27 19:54:10 +01:00
|
|
|
using UnityEngine.Assertions;
|
2024-05-26 21:15:38 +02:00
|
|
|
using System.Linq;
|
|
|
|
using Unity.Netcode;
|
2024-02-02 21:03:44 +01:00
|
|
|
|
2024-05-27 12:19:16 +02:00
|
|
|
public class RopeSimulator : NetworkBehaviour
|
2024-02-02 21:03:44 +01:00
|
|
|
{
|
2024-05-26 21:15:38 +02:00
|
|
|
[Header("Solver")]
|
|
|
|
[SerializeField] int solveIterations = 10;
|
|
|
|
Transform ropeStart, ropeEnd;
|
2024-02-03 02:47:40 +01:00
|
|
|
|
2024-05-26 21:15:38 +02:00
|
|
|
[Header("Rope Settings")]
|
|
|
|
[SerializeField] int subDivision = 20;
|
|
|
|
[SerializeField] float distBetweenRopePoints = 0.2f;
|
2024-02-03 16:21:32 +01:00
|
|
|
|
2024-05-26 21:15:38 +02:00
|
|
|
[Header("Rope Renderer")]
|
2024-02-03 01:25:34 +01:00
|
|
|
[SerializeField] LineRenderer lineRenderer;
|
|
|
|
|
2024-05-26 21:15:38 +02:00
|
|
|
[SerializeField] Rope rope;
|
2024-02-04 06:27:35 +01:00
|
|
|
|
2024-05-26 21:15:38 +02:00
|
|
|
[Header("Networking")]
|
|
|
|
[SerializeField] CircularBuffer<RopeState> stateBuffer;
|
2024-05-27 12:19:16 +02:00
|
|
|
[SerializeField] float reconciliateThreshold = 1f;
|
2024-05-26 21:15:38 +02:00
|
|
|
private const int k_stateBufferSize = 128;
|
2024-03-17 17:55:35 +01:00
|
|
|
private int currentTick => NetworkManager.Singleton.NetworkTickSystem.LocalTime.Tick;
|
2024-02-28 20:25:01 +01:00
|
|
|
|
2024-02-15 01:32:08 +01:00
|
|
|
private void Awake()
|
|
|
|
{
|
2024-05-27 12:19:16 +02:00
|
|
|
this.rope = null;
|
2024-05-26 21:15:38 +02:00
|
|
|
stateBuffer = new CircularBuffer<RopeState>(k_stateBufferSize);
|
2024-02-15 01:32:08 +01:00
|
|
|
}
|
|
|
|
|
2024-02-28 14:50:14 +01:00
|
|
|
private void OnEnable()
|
2024-02-02 21:03:44 +01:00
|
|
|
{
|
2024-05-26 21:15:38 +02:00
|
|
|
GameManager.OnPlayersReady += Init;
|
2024-02-27 21:17:44 +01:00
|
|
|
}
|
2024-02-14 14:37:29 +01:00
|
|
|
|
2024-02-27 21:17:44 +01:00
|
|
|
private void OnDisable()
|
|
|
|
{
|
2024-05-26 21:15:38 +02:00
|
|
|
GameManager.OnPlayersReady += Init;
|
2024-05-27 12:19:16 +02:00
|
|
|
|
|
|
|
if (NetworkManager.Singleton != null)
|
|
|
|
NetworkManager.Singleton.NetworkTickSystem.Tick -= NetworkTick;
|
2024-02-14 14:37:29 +01:00
|
|
|
}
|
|
|
|
|
2024-05-26 21:15:38 +02:00
|
|
|
private void Update()
|
2024-02-14 14:37:29 +01:00
|
|
|
{
|
2024-05-27 12:19:16 +02:00
|
|
|
if (rope == null) return;
|
|
|
|
|
2024-05-26 21:15:38 +02:00
|
|
|
rope.points.First().position = ropeStart.position;
|
|
|
|
rope.points.Last().position = ropeEnd.position;
|
|
|
|
Simulate(this.rope, Time.deltaTime, this.distBetweenRopePoints, this.solveIterations);
|
2024-02-27 19:54:10 +01:00
|
|
|
|
2024-05-26 21:15:38 +02:00
|
|
|
DisplayRope();
|
2024-02-03 16:21:32 +01:00
|
|
|
}
|
|
|
|
|
2024-05-26 21:15:38 +02:00
|
|
|
private void Init(GameObject[] players)
|
2024-02-03 16:21:32 +01:00
|
|
|
{
|
2024-05-26 21:15:38 +02:00
|
|
|
this.ropeStart = players[0].transform;
|
|
|
|
this.ropeEnd = players[1].transform;
|
2024-02-03 16:58:14 +01:00
|
|
|
|
2024-05-26 21:15:38 +02:00
|
|
|
this.rope = RopeSimulator.BuildRope(this.ropeStart, this.ropeEnd, this.subDivision, this.distBetweenRopePoints);
|
2024-05-27 12:19:16 +02:00
|
|
|
NetworkManager.Singleton.NetworkTickSystem.Tick += NetworkTick;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void NetworkTick()
|
|
|
|
{
|
|
|
|
RopeState ropeState = new()
|
|
|
|
{
|
|
|
|
tick = currentTick,
|
|
|
|
nrope = Rope.ToNetworkRope(this.rope)
|
|
|
|
};
|
|
|
|
|
|
|
|
stateBuffer.Add(ropeState, currentTick);
|
|
|
|
|
|
|
|
if (IsServer)
|
|
|
|
{
|
|
|
|
ServerToClientRopeStateRpc(ropeState);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[Rpc(SendTo.NotServer)]
|
|
|
|
private void ServerToClientRopeStateRpc(RopeState serverState)
|
|
|
|
{
|
|
|
|
RopeState clientState = this.stateBuffer.Get(serverState.tick);
|
|
|
|
Rope serverRope = Rope.FromNetworkRope(serverState.nrope, this.distBetweenRopePoints);
|
|
|
|
Rope previousClientRope = Rope.FromNetworkRope(clientState.nrope, this.distBetweenRopePoints);
|
|
|
|
float diff = Rope.CalcDiff(previousClientRope, serverRope);
|
|
|
|
Debug.Log($"Diff: {diff}, ({serverState.tick})/({clientState.tick})");
|
|
|
|
|
|
|
|
if (diff > reconciliateThreshold)
|
|
|
|
{
|
|
|
|
this.rope = serverRope;
|
|
|
|
}
|
2024-02-03 16:21:32 +01:00
|
|
|
}
|
|
|
|
|
2024-05-26 21:15:38 +02:00
|
|
|
private void DisplayRope()
|
2024-02-03 16:21:32 +01:00
|
|
|
{
|
2024-05-26 21:15:38 +02:00
|
|
|
if (!lineRenderer) return;
|
2024-02-03 20:01:12 +01:00
|
|
|
|
2024-05-26 21:15:38 +02:00
|
|
|
lineRenderer.positionCount = rope.points.Length;
|
|
|
|
lineRenderer.SetPositions(rope.points.Select(point => point.position).ToArray());
|
2024-02-03 16:21:32 +01:00
|
|
|
}
|
|
|
|
|
2024-05-26 21:15:38 +02:00
|
|
|
public static Rope BuildRope(Transform ropeStart, Transform ropeEnd, int subDivision, float distBetweenRopePoints)
|
2024-02-03 16:21:32 +01:00
|
|
|
{
|
2024-05-26 21:15:38 +02:00
|
|
|
Assert.IsNotNull(ropeStart);
|
|
|
|
Assert.IsNotNull(ropeEnd);
|
2024-02-28 20:25:01 +01:00
|
|
|
|
2024-05-26 21:15:38 +02:00
|
|
|
RopeBuilder builder = new();
|
|
|
|
builder.AddPoint(new Point(ropeStart.position, locked: true));
|
2024-02-15 01:32:08 +01:00
|
|
|
|
2024-05-26 21:15:38 +02:00
|
|
|
for (int i = 0; i < subDivision; i++)
|
2024-02-03 00:15:07 +01:00
|
|
|
{
|
2024-05-26 21:15:38 +02:00
|
|
|
Vector3 pointPos = Vector3.Lerp(ropeStart.position, ropeEnd.position, (float) i / (float) subDivision);
|
|
|
|
builder.AddPoint(new Point(pointPos, locked: false));
|
2024-02-03 00:15:07 +01:00
|
|
|
}
|
|
|
|
|
2024-05-26 21:15:38 +02:00
|
|
|
builder.AddPoint(new Point(ropeEnd.position, locked: true));
|
2024-02-03 00:15:07 +01:00
|
|
|
|
2024-05-26 21:15:38 +02:00
|
|
|
for (int i = 0; i <= subDivision; i++)
|
2024-02-03 00:15:07 +01:00
|
|
|
{
|
|
|
|
builder.ConnectPointsWithDesiredLength(i, i + 1, desiredLength: distBetweenRopePoints);
|
|
|
|
}
|
2024-03-27 20:13:30 +01:00
|
|
|
|
2024-05-26 21:15:38 +02:00
|
|
|
return builder.Build();
|
2024-03-17 17:55:35 +01:00
|
|
|
}
|
|
|
|
|
2024-05-26 21:15:38 +02:00
|
|
|
public static void Simulate(Rope rope, float dt, float desiredLength, int solveIterations)
|
2024-03-17 17:55:35 +01:00
|
|
|
{
|
2024-05-26 21:15:38 +02:00
|
|
|
Assert.IsNotNull(rope);
|
|
|
|
Assert.IsTrue(dt > 0f);
|
2024-03-17 17:55:35 +01:00
|
|
|
|
2024-02-03 00:15:07 +01:00
|
|
|
foreach (var point in rope.points)
|
|
|
|
{
|
|
|
|
if (point.locked) continue;
|
|
|
|
|
2024-05-26 21:15:38 +02:00
|
|
|
// Verlet
|
|
|
|
Vector3 prevPos = point.position;
|
|
|
|
point.position += point.position - point.prevPosition;
|
|
|
|
point.prevPosition = prevPos;
|
2024-02-03 00:15:07 +01:00
|
|
|
}
|
|
|
|
|
2024-05-26 21:15:38 +02:00
|
|
|
// Stick resolution
|
|
|
|
for (int solveIter = 0; solveIter < solveIterations; solveIter++)
|
2024-02-03 01:14:34 +01:00
|
|
|
{
|
2024-05-26 21:15:38 +02:00
|
|
|
for (int stickIdx = 0; stickIdx < rope.sticks.Length; stickIdx++)
|
2024-02-02 21:03:44 +01:00
|
|
|
{
|
2024-05-26 21:15:38 +02:00
|
|
|
var stick = rope.sticks[stickIdx];
|
|
|
|
Vector3 stickDir = (stick.B.position - stick.A.position);
|
|
|
|
Vector3 stickCentre = stick.A.position + stickDir / 2f;
|
|
|
|
float length = stickDir.magnitude;
|
|
|
|
if (length > stick.desiredLength)
|
2024-02-02 21:03:44 +01:00
|
|
|
{
|
2024-05-26 21:15:38 +02:00
|
|
|
float distribution = stick.A.locked || stick.B.locked ? 1f : 2f;
|
|
|
|
float distanceToMove = (length - stick.desiredLength) / distribution;
|
2024-02-02 21:03:44 +01:00
|
|
|
if (!stick.A.locked)
|
|
|
|
{
|
2024-05-26 21:15:38 +02:00
|
|
|
Vector3 prevPos = stick.A.position;
|
|
|
|
stick.A.position += stickDir.normalized * distanceToMove;
|
|
|
|
stick.A.prevPosition = prevPos;
|
2024-02-02 21:03:44 +01:00
|
|
|
}
|
|
|
|
if (!stick.B.locked)
|
|
|
|
{
|
2024-05-26 21:15:38 +02:00
|
|
|
Vector3 prevPos = stick.B.position;
|
|
|
|
stick.B.position -= stickDir.normalized * distanceToMove;
|
|
|
|
stick.B.prevPosition = prevPos;
|
2024-02-02 21:03:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-26 21:15:38 +02:00
|
|
|
private void OnDrawGizmos()
|
2024-02-02 21:03:44 +01:00
|
|
|
{
|
2024-05-26 21:15:38 +02:00
|
|
|
Gizmos.color = Color.green;
|
|
|
|
foreach (var p in rope.points)
|
2024-02-02 21:03:44 +01:00
|
|
|
{
|
2024-05-26 21:15:38 +02:00
|
|
|
Gizmos.DrawSphere(p.position, 0.5f);
|
2024-02-02 21:03:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|