using UnityEngine; using UnityEngine.Assertions; using System.Linq; using Unity.Netcode; public class RopeSimulator : MonoBehaviour { [Header("Solver")] [SerializeField] int solveIterations = 10; Transform ropeStart, ropeEnd; [Header("Rope Settings")] [SerializeField] int subDivision = 20; [SerializeField] float distBetweenRopePoints = 0.2f; [Header("Rope Renderer")] [SerializeField] LineRenderer lineRenderer; [SerializeField] Rope rope; [Header("Networking")] [SerializeField] CircularBuffer stateBuffer; private const int k_stateBufferSize = 128; private int currentTick => NetworkManager.Singleton.NetworkTickSystem.LocalTime.Tick; private void Awake() { stateBuffer = new CircularBuffer(k_stateBufferSize); } private void OnEnable() { GameManager.OnPlayersReady += Init; } private void OnDisable() { GameManager.OnPlayersReady += Init; } private void Update() { rope.points.First().position = ropeStart.position; rope.points.Last().position = ropeEnd.position; Simulate(this.rope, Time.deltaTime, this.distBetweenRopePoints, this.solveIterations); DisplayRope(); } private void Init(GameObject[] players) { this.ropeStart = players[0].transform; this.ropeEnd = players[1].transform; this.rope = RopeSimulator.BuildRope(this.ropeStart, this.ropeEnd, this.subDivision, this.distBetweenRopePoints); } private void DisplayRope() { if (!lineRenderer) return; lineRenderer.positionCount = rope.points.Length; lineRenderer.SetPositions(rope.points.Select(point => point.position).ToArray()); } public static Rope BuildRope(Transform ropeStart, Transform ropeEnd, int subDivision, float distBetweenRopePoints) { Assert.IsNotNull(ropeStart); Assert.IsNotNull(ropeEnd); RopeBuilder builder = new(); builder.AddPoint(new Point(ropeStart.position, locked: true)); for (int i = 0; i < subDivision; i++) { Vector3 pointPos = Vector3.Lerp(ropeStart.position, ropeEnd.position, (float) i / (float) subDivision); builder.AddPoint(new Point(pointPos, locked: false)); } builder.AddPoint(new Point(ropeEnd.position, locked: true)); for (int i = 0; i <= subDivision; i++) { builder.ConnectPointsWithDesiredLength(i, i + 1, desiredLength: distBetweenRopePoints); } return builder.Build(); } public static void Simulate(Rope rope, float dt, float desiredLength, int solveIterations) { Assert.IsNotNull(rope); Assert.IsTrue(dt > 0f); foreach (var point in rope.points) { if (point.locked) continue; // Verlet Vector3 prevPos = point.position; point.position += point.position - point.prevPosition; point.prevPosition = prevPos; } // Stick resolution for (int solveIter = 0; solveIter < solveIterations; solveIter++) { for (int stickIdx = 0; stickIdx < rope.sticks.Length; stickIdx++) { 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) { float distribution = stick.A.locked || stick.B.locked ? 1f : 2f; float distanceToMove = (length - stick.desiredLength) / distribution; if (!stick.A.locked) { Vector3 prevPos = stick.A.position; stick.A.position += stickDir.normalized * distanceToMove; stick.A.prevPosition = prevPos; } if (!stick.B.locked) { Vector3 prevPos = stick.B.position; stick.B.position -= stickDir.normalized * distanceToMove; stick.B.prevPosition = prevPos; } } } } } private void OnDrawGizmos() { Gizmos.color = Color.green; foreach (var p in rope.points) { Gizmos.DrawSphere(p.position, 0.5f); } } }