Fresh start for new rope network shit
Stripped all functionality for rope to implement multiplayer easier. Play RopeOverhaulGame scene to test this
This commit is contained in:
parent
007e762a47
commit
99aa0647f2
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,7 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 27527bab112c238c7b8a95382ab22211
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -12,7 +12,7 @@ public class RopeRumbling : MonoBehaviour
|
||||||
private Gamepad pad;
|
private Gamepad pad;
|
||||||
|
|
||||||
// Rope
|
// Rope
|
||||||
[SerializeField] private RopeSimulator rope;
|
[SerializeField] private RopeSimulatorOld rope;
|
||||||
|
|
||||||
private void Start()
|
private void Start()
|
||||||
{
|
{
|
||||||
|
|
|
@ -32,8 +32,16 @@ public class PlayerInput : MonoBehaviour, IMoveData
|
||||||
|
|
||||||
private void Update()
|
private void Update()
|
||||||
{
|
{
|
||||||
moveData.Movement.x = Input.GetAxisRaw("Horizontal");
|
if (playerNumber == 0)
|
||||||
moveData.Movement.y = Input.GetAxisRaw("Vertical");
|
{
|
||||||
|
moveData.Movement.x = Input.GetAxisRaw("Horizontal");
|
||||||
|
moveData.Movement.y = Input.GetAxisRaw("Vertical");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
moveData.Movement.x = Input.GetAxisRaw("ArrowHorizontal");
|
||||||
|
moveData.Movement.y = Input.GetAxisRaw("ArrowVertical");
|
||||||
|
}
|
||||||
|
|
||||||
whipAttack = Input.GetKey(KeyCode.R);
|
whipAttack = Input.GetKey(KeyCode.R);
|
||||||
|
|
||||||
|
|
|
@ -1,703 +1,144 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Unity.Netcode;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Assertions;
|
using UnityEngine.Assertions;
|
||||||
using UnityUtils;
|
using System.Linq;
|
||||||
|
using Unity.Netcode;
|
||||||
|
|
||||||
public class RopeSimulator : NetworkBehaviour
|
public class RopeSimulator : MonoBehaviour
|
||||||
{
|
{
|
||||||
[SerializeField]
|
[Header("Solver")]
|
||||||
private float gravity = 10;
|
[SerializeField] int solveIterations = 10;
|
||||||
|
Transform ropeStart, ropeEnd;
|
||||||
|
|
||||||
[SerializeField]
|
[Header("Rope Settings")]
|
||||||
private int solveIterations = 10;
|
[SerializeField] int subDivision = 20;
|
||||||
|
[SerializeField] float distBetweenRopePoints = 0.2f;
|
||||||
|
|
||||||
[SerializeField]
|
[Header("Rope Renderer")]
|
||||||
private bool constrainStickMinLength;
|
|
||||||
|
|
||||||
[SerializeField]
|
|
||||||
public RopeJoint start, end;
|
|
||||||
|
|
||||||
[SerializeField]
|
|
||||||
float subDivision = 50f;
|
|
||||||
|
|
||||||
[SerializeField]
|
|
||||||
float collisionCheckDist = 0.5f;
|
|
||||||
|
|
||||||
[SerializeField, Range(0f, 1f)]
|
|
||||||
float distBetweenRopePoints = 0.1f;
|
|
||||||
|
|
||||||
[SerializeField, Range(0.01f, 1f)]
|
|
||||||
float ropeRadius;
|
|
||||||
|
|
||||||
[SerializeField]
|
|
||||||
float ignoreResolveThreshold = 0.08f;
|
|
||||||
|
|
||||||
[SerializeField]
|
|
||||||
LayerMask staticColliderMask;
|
|
||||||
|
|
||||||
[SerializeField, Range(0f, 100f)]
|
|
||||||
float pullForce = 20f;
|
|
||||||
|
|
||||||
[SerializeField]
|
|
||||||
float xyGravityDampScalor = 1f;
|
|
||||||
|
|
||||||
[SerializeField, Range(0f, 20f)]
|
|
||||||
public float ropeExtendSpeed, ropeShrinkSpeed;
|
|
||||||
|
|
||||||
public float squezeDamage = 1f;
|
|
||||||
public AnimationCurve swingSpeedToDamageMultiplier;
|
|
||||||
|
|
||||||
[SerializeField]
|
|
||||||
public float ropeMaxLength, ropeMinLength;
|
|
||||||
|
|
||||||
[Header("Rope Colliders")]
|
|
||||||
[SerializeField] GameObject colliderPrefab;
|
|
||||||
[SerializeField] string colliderTag = "Rope";
|
|
||||||
[SerializeField] Transform ropeCollidersParent;
|
|
||||||
|
|
||||||
[Header("Rendering")]
|
|
||||||
[SerializeField] LineRenderer lineRenderer;
|
[SerializeField] LineRenderer lineRenderer;
|
||||||
|
|
||||||
[Header("Animaion")]
|
[SerializeField] Rope rope;
|
||||||
[SerializeField] float pullAnimationOvershootThreshold = 0.2f;
|
|
||||||
|
|
||||||
[Header("Netcode")]
|
[Header("Networking")]
|
||||||
private const float k_serverTickRate = 60f;
|
[SerializeField] CircularBuffer<RopeState> stateBuffer;
|
||||||
private const int k_rngSeed = 6969;
|
private const int k_stateBufferSize = 128;
|
||||||
public float k_ropeReconciliateThreshold = 1f;
|
|
||||||
private System.Random rng = new System.Random(k_rngSeed);
|
|
||||||
private int currentTick => NetworkManager.Singleton.NetworkTickSystem.LocalTime.Tick;
|
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;
|
|
||||||
|
|
||||||
public float Overshoot => rope.CalculateLengthOvershoot();
|
|
||||||
public bool InSwingMode => start.locked || end.locked;
|
|
||||||
|
|
||||||
public Rope rope;
|
|
||||||
|
|
||||||
Dictionary<Collider2D, float> colliderToSquezeForce = new();
|
|
||||||
|
|
||||||
public static RopeSimulator instance;
|
|
||||||
|
|
||||||
private bool IsInitialized => start != null || end != null;
|
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
if (instance == null)
|
stateBuffer = new CircularBuffer<RopeState>(k_stateBufferSize);
|
||||||
{
|
|
||||||
instance = this;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Destroy(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
stateBuffer = new(k_bufferSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
GameManager.OnPlayersReady += PlayersReady;
|
GameManager.OnPlayersReady += Init;
|
||||||
|
|
||||||
if (NetworkManager.Singleton != null)
|
|
||||||
NetworkManager.Singleton.NetworkTickSystem.Tick += NetworkTick;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDisable()
|
private void OnDisable()
|
||||||
{
|
{
|
||||||
GameManager.OnPlayersReady -= PlayersReady;
|
GameManager.OnPlayersReady += Init;
|
||||||
|
|
||||||
if (NetworkManager.Singleton != null)
|
|
||||||
NetworkManager.Singleton.NetworkTickSystem.Tick -= NetworkTick;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PlayersReady(GameObject[] players)
|
|
||||||
{
|
|
||||||
BuildRope(players[0].GetComponent<RopeJoint>(), players[1].GetComponent<RopeJoint>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void BuildRope(RopeJoint start, RopeJoint end)
|
|
||||||
{
|
|
||||||
Assert.IsNotNull(start);
|
|
||||||
Assert.IsNotNull(end);
|
|
||||||
|
|
||||||
// Sanity check if rope simulator was initialized before - we are re-building the rope
|
|
||||||
if (this.start != null)
|
|
||||||
{
|
|
||||||
this.start.playerInput.ropeLengthExtend -= ExtendRope;
|
|
||||||
this.start.playerInput.ropeLengthShrinken -= ShrinkenRope;
|
|
||||||
}
|
|
||||||
if (this.end != null)
|
|
||||||
{
|
|
||||||
this.end.playerInput.ropeLengthExtend -= ExtendRope;
|
|
||||||
this.end.playerInput.ropeLengthShrinken -= ShrinkenRope;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.start = start;
|
|
||||||
this.end = end;
|
|
||||||
|
|
||||||
Rebuild();
|
|
||||||
|
|
||||||
this.start.playerInput.ropeLengthShrinken += ShrinkenRope;
|
|
||||||
this.end.playerInput.ropeLengthShrinken += ShrinkenRope;
|
|
||||||
|
|
||||||
this.start.playerInput.ropeLengthExtend += ExtendRope;
|
|
||||||
this.end.playerInput.ropeLengthExtend += ExtendRope;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShrinkenRope(int playerNumber)
|
|
||||||
{
|
|
||||||
int prevSubDivision = (int)subDivision;
|
|
||||||
subDivision -= ropeShrinkSpeed * Time.deltaTime;
|
|
||||||
subDivision = Mathf.Clamp(subDivision, ropeMinLength, ropeMaxLength);
|
|
||||||
|
|
||||||
// Only shrinken if the numeric value has changed
|
|
||||||
if (prevSubDivision - (int)subDivision <= 0) return;
|
|
||||||
|
|
||||||
// Shrink from rope point after start rope joint
|
|
||||||
List<Point> newPoints = new(rope.points.Length - 1);
|
|
||||||
for (int i = 0; i < (rope.points.Length - 1); i++)
|
|
||||||
{
|
|
||||||
newPoints.Add(rope.points[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var builder = new RopeBuilder(newPoints, new List<Stick>());
|
|
||||||
|
|
||||||
// Re-gen sticks
|
|
||||||
for (int i = 0; i < (int)subDivision; i++)
|
|
||||||
{
|
|
||||||
builder.ConnectPointsWithDesiredLength(i, i + 1, distBetweenRopePoints);
|
|
||||||
}
|
|
||||||
rope = builder.Build();
|
|
||||||
|
|
||||||
RebuildRopeColliders();
|
|
||||||
CreateOrderArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExtendRope(int playerNumber)
|
|
||||||
{
|
|
||||||
int prevSubDivision = (int)subDivision;
|
|
||||||
subDivision += ropeExtendSpeed * Time.deltaTime;
|
|
||||||
subDivision = Mathf.Clamp(subDivision, ropeMinLength, ropeMaxLength);
|
|
||||||
|
|
||||||
// Only extend if the numeric value has changed
|
|
||||||
if (prevSubDivision - (int)subDivision >= 0) return;
|
|
||||||
|
|
||||||
// Extend from rope point after start rope point
|
|
||||||
List<Point> newPoints = new(rope.points.Length + 1);
|
|
||||||
newPoints.Add(new Point(rope.points[1].position));
|
|
||||||
for (int i = 1; i < rope.points.Length; i++)
|
|
||||||
{
|
|
||||||
newPoints.Add(rope.points[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var builder = new RopeBuilder(newPoints, new List<Stick>());
|
|
||||||
|
|
||||||
// Re-gen sticks
|
|
||||||
for (int i = 0; i < (int)subDivision; i++)
|
|
||||||
{
|
|
||||||
//Debug.Log($"Reg-gen stick. from: {i} to {i + 1}, with dist: {distBetweenRopePoints}");
|
|
||||||
builder.ConnectPointsWithDesiredLength(i, i + 1, distBetweenRopePoints);
|
|
||||||
}
|
|
||||||
rope = builder.Build();
|
|
||||||
|
|
||||||
RebuildRopeColliders();
|
|
||||||
CreateOrderArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnDestroy()
|
|
||||||
{
|
|
||||||
base.OnDestroy();
|
|
||||||
|
|
||||||
// May never have been initialized
|
|
||||||
if (!IsInitialized) return;
|
|
||||||
|
|
||||||
start.playerInput.ropeLengthShrinken -= ShrinkenRope;
|
|
||||||
end.playerInput.ropeLengthShrinken -= ShrinkenRope;
|
|
||||||
|
|
||||||
start.playerInput.ropeLengthExtend -= ExtendRope;
|
|
||||||
end.playerInput.ropeLengthExtend -= ExtendRope;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Rebuild()
|
|
||||||
{
|
|
||||||
Debug.Log("rebuild");
|
|
||||||
|
|
||||||
RopeBuilder builder = new RopeBuilder();
|
|
||||||
builder.AddPoint(new Point(start.position, locked: true));
|
|
||||||
|
|
||||||
// Build rope points
|
|
||||||
for (int i = 1; i < (int)subDivision; i++)
|
|
||||||
{
|
|
||||||
Vector3 pointPos = Vector3.Lerp(start.position, end.position, (float)i / Mathf.Floor(subDivision));
|
|
||||||
Debug.DrawRay(pointPos, (end.position - start.position).normalized);
|
|
||||||
builder.AddPoint(new Point(pointPos));
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.AddPoint(new Point(end.position, locked: true));
|
|
||||||
|
|
||||||
// Connect rope points
|
|
||||||
for (int i = 0; i < (int)subDivision; i++)
|
|
||||||
{
|
|
||||||
builder.ConnectPointsWithDesiredLength(i, i + 1, desiredLength: distBetweenRopePoints);
|
|
||||||
}
|
|
||||||
rope = builder.Build();
|
|
||||||
|
|
||||||
RebuildRopeColliders();
|
|
||||||
|
|
||||||
CreateOrderArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RebuildRopeColliders()
|
|
||||||
{
|
|
||||||
for (int i = 0; i < ropeCollidersParent.childCount; i++)
|
|
||||||
{
|
|
||||||
Destroy(ropeCollidersParent.GetChild(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var point in rope.points)
|
|
||||||
{
|
|
||||||
GameObject ropeCollider = Instantiate(colliderPrefab);
|
|
||||||
ropeCollider.transform.parent = ropeCollidersParent;
|
|
||||||
|
|
||||||
ropeCollider.transform.position = point.position;
|
|
||||||
ropeCollider.tag = colliderTag;
|
|
||||||
ropeCollider.layer = LayerMask.NameToLayer("Rope");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawRope()
|
|
||||||
{
|
|
||||||
// Update line renderer
|
|
||||||
List<Vector3> positions = new List<Vector3>(this.rope.points.Length);
|
|
||||||
for (int i = 0; i < this.rope.points.Length; i++)
|
|
||||||
{
|
|
||||||
positions.Add(this.rope.points[i].position);
|
|
||||||
}
|
|
||||||
lineRenderer.positionCount = positions.Count;
|
|
||||||
lineRenderer.SetPositions(positions.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Rpc(SendTo.NotServer)]
|
|
||||||
private void ServerToClientGameStateRpc(GameState serverState)
|
|
||||||
{
|
|
||||||
this.lastReceivedServerGameState = serverState;
|
|
||||||
|
|
||||||
Debug.Log($"Received server state. Server tick: {serverState.tick}, client: {currentTick}");
|
|
||||||
|
|
||||||
// Not enough information
|
|
||||||
if (stateBuffer.Get(serverState.tick).Equals(default(GameState))) return;
|
|
||||||
|
|
||||||
// TODO: investigate why this is zero at start of game sometimes
|
|
||||||
if (stateBuffer.Get(serverState.tick).nrope.positions.Length == 0) return;
|
|
||||||
Debug.Log($"client len: {stateBuffer.Get(serverState.tick).nrope.positions.Length}, server len {serverState.nrope.positions.Length}");
|
|
||||||
|
|
||||||
Rope serverRope = Rope.FromNetworkRope(serverState.nrope, distBetweenRopePoints);
|
|
||||||
Rope oldLocalRope = Rope.FromNetworkRope(stateBuffer.Get(serverState.tick).nrope, distBetweenRopePoints);
|
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
stateBuffer.Add(ProcessGame(), currentTick);
|
|
||||||
|
|
||||||
// Send to clients if is server
|
|
||||||
if (IsServer)
|
|
||||||
ServerToClientGameStateRpc(stateBuffer.Get(currentTick));
|
|
||||||
}
|
|
||||||
|
|
||||||
private GameState ProcessGame()
|
|
||||||
{
|
|
||||||
GameState localState = new()
|
|
||||||
{
|
|
||||||
tick = currentTick,
|
|
||||||
nrope = Rope.ToNetworkRope(this.rope),
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Update()
|
private void Update()
|
||||||
{
|
{
|
||||||
if (!IsInitialized)
|
rope.points.First().position = ropeStart.position;
|
||||||
return;
|
rope.points.Last().position = ropeEnd.position;
|
||||||
|
Simulate(this.rope, Time.deltaTime, this.distBetweenRopePoints, this.solveIterations);
|
||||||
|
|
||||||
if (isReconciliating)
|
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++)
|
||||||
{
|
{
|
||||||
this.rope.points.First().position = start.position;
|
Vector3 pointPos = Vector3.Lerp(ropeStart.position, ropeEnd.position, (float) i / (float) subDivision);
|
||||||
this.rope.points.First().prevPosition = start.position;
|
builder.AddPoint(new Point(pointPos, locked: false));
|
||||||
|
|
||||||
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();
|
builder.AddPoint(new Point(ropeEnd.position, locked: true));
|
||||||
|
|
||||||
rope.points.First().position = start.position;
|
for (int i = 0; i <= subDivision; i++)
|
||||||
rope.points.Last().position = end.position;
|
|
||||||
|
|
||||||
float ropeDiff = Simulate(Time.fixedDeltaTime);
|
|
||||||
|
|
||||||
// Update the rope collider positions
|
|
||||||
for (int i = 0; i < rope.points.Length; i++)
|
|
||||||
{
|
{
|
||||||
ropeCollidersParent.GetChild(i).position = rope.points[i].position;
|
builder.ConnectPointsWithDesiredLength(i, i + 1, desiredLength: distBetweenRopePoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle static colliders
|
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)
|
foreach (var point in rope.points)
|
||||||
{
|
{
|
||||||
if (point.locked) continue;
|
if (point.locked) continue;
|
||||||
|
|
||||||
HandleStaticCollidersOfPoint(point);
|
// Verlet
|
||||||
|
Vector3 prevPos = point.position;
|
||||||
|
point.position += point.position - point.prevPosition;
|
||||||
|
point.prevPosition = prevPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constrain start transform based on overshoot
|
// Stick resolution
|
||||||
float overshoot = rope.CalculateLengthOvershoot();
|
for (int solveIter = 0; solveIter < solveIterations; solveIter++)
|
||||||
PlayerPullAnimation(overshoot);
|
|
||||||
PullPlayers(overshoot);
|
|
||||||
|
|
||||||
// Handle squeze kills
|
|
||||||
foreach (var collider in colliderToSquezeForce)
|
|
||||||
{
|
{
|
||||||
ISquezeDamageReceiver squezeDamageReceiver = collider.Key.transform.root.GetComponent<ISquezeDamageReceiver>();
|
for (int stickIdx = 0; stickIdx < rope.sticks.Length; stickIdx++)
|
||||||
if (squezeDamageReceiver == null)
|
{
|
||||||
squezeDamageReceiver = collider.Key.GetComponent<ISquezeDamageReceiver>();
|
var stick = rope.sticks[stickIdx];
|
||||||
|
Vector3 stickDir = (stick.B.position - stick.A.position);
|
||||||
if (squezeDamageReceiver == null) continue;
|
Vector3 stickCentre = stick.A.position + stickDir / 2f;
|
||||||
|
float length = stickDir.magnitude;
|
||||||
float swingMultiplier = InSwingMode ? swingSpeedToDamageMultiplier.Evaluate((start.locked ? end : start).body.velocity.magnitude) : 1f;
|
if (length > stick.desiredLength)
|
||||||
squezeDamageReceiver.TakeSquezeDamage(collider.Value * squezeDamage * swingMultiplier);
|
{
|
||||||
}
|
float distribution = stick.A.locked || stick.B.locked ? 1f : 2f;
|
||||||
|
float distanceToMove = (length - stick.desiredLength) / distribution;
|
||||||
// Handle xy dampening on z gravity
|
if (!stick.A.locked)
|
||||||
foreach (var point in rope.points)
|
{
|
||||||
{
|
Vector3 prevPos = stick.A.position;
|
||||||
if (point.position.z >= 0f) continue;
|
stick.A.position += stickDir.normalized * distanceToMove;
|
||||||
|
stick.A.prevPosition = prevPos;
|
||||||
Vector2 newXYPos = Vector2.MoveTowards(new Vector2(point.position.x, point.position.y), new Vector2(point.prevPosition.x, point.prevPosition.y), Mathf.Abs(point.position.z * xyGravityDampScalor));
|
}
|
||||||
point.position.Set(newXYPos.x, newXYPos.y, 0f);
|
if (!stick.B.locked)
|
||||||
}
|
{
|
||||||
|
Vector3 prevPos = stick.B.position;
|
||||||
DrawRope();
|
stick.B.position -= stickDir.normalized * distanceToMove;
|
||||||
}
|
stick.B.prevPosition = prevPos;
|
||||||
|
}
|
||||||
private void PlayerPullAnimation(float overshoot)
|
}
|
||||||
{
|
}
|
||||||
|
|
||||||
//if (overshoot > pullAnimationOvershootThreshold)
|
|
||||||
//{
|
|
||||||
// float startDot = Vector2.Dot((start.position - rope.points[1].position).normalized, start.playerInput.movement);
|
|
||||||
// if (startDot > 0.35f)
|
|
||||||
// {
|
|
||||||
// start.playerAnimationHandler?.animator.SetBool("IsPulling", true);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// float endDot = Vector2.Dot((end.position - rope.points[rope.points.Count - 2].position).normalized, end.playerInput.movement);
|
|
||||||
// if (endDot > 0.35f)
|
|
||||||
// {
|
|
||||||
// end.playerAnimationHandler?.animator.SetBool("IsPulling", true);
|
|
||||||
// }
|
|
||||||
|
|
||||||
//}
|
|
||||||
//else
|
|
||||||
//{
|
|
||||||
// start.playerAnimationHandler?.animator.SetBool("IsPulling", false);
|
|
||||||
// end.playerAnimationHandler?.animator.SetBool("IsPulling", false);
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PullPlayers(float overshoot)
|
|
||||||
{
|
|
||||||
if (overshoot <= 0f) return;
|
|
||||||
|
|
||||||
//start.position = prevStartPos;
|
|
||||||
float divider = !start.locked && !end.locked ? 2f : 1f;
|
|
||||||
|
|
||||||
if (!start.locked)
|
|
||||||
{
|
|
||||||
Vector2 pullDirection = (rope.points[1].position - start.position).normalized;
|
|
||||||
Vector2 force = pullDirection * overshoot * (pullForce / divider);
|
|
||||||
start.body.AddForce(force);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
start.body.velocity *= 0;
|
|
||||||
}
|
|
||||||
if (!end.locked)
|
|
||||||
{
|
|
||||||
Vector2 pullDirection = (rope.points[rope.points.Length - 2].position - end.position).normalized;
|
|
||||||
Vector2 force = pullDirection * overshoot * (pullForce / divider);
|
|
||||||
end.body.AddForce(force);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
end.body.velocity *= 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDrawGizmos()
|
private void OnDrawGizmos()
|
||||||
{
|
{
|
||||||
if (!IsInitialized) return;
|
|
||||||
if (!Application.isPlaying) return;
|
|
||||||
|
|
||||||
// Local rope
|
|
||||||
Gizmos.color = Color.green;
|
Gizmos.color = Color.green;
|
||||||
foreach (var point in rope.points)
|
foreach (var p in rope.points)
|
||||||
{
|
{
|
||||||
Gizmos.DrawSphere(point.position, ropeRadius);
|
Gizmos.DrawSphere(p.position, 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Last received server rope
|
|
||||||
if (lastReceivedServerGameState.Equals(default(GameState))) return;
|
|
||||||
|
|
||||||
Gizmos.color = Color.red;
|
|
||||||
foreach (var point in Rope.FromNetworkRope(lastReceivedServerGameState.nrope, distBetweenRopePoints).points)
|
|
||||||
{
|
|
||||||
Gizmos.DrawSphere(point.position, ropeRadius);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float Simulate(float dt)
|
|
||||||
{
|
|
||||||
float diff = 0f;
|
|
||||||
foreach (Point p in rope.points)
|
|
||||||
{
|
|
||||||
if (!p.locked)
|
|
||||||
{
|
|
||||||
Vector3 positionBeforeUpdate = p.position;
|
|
||||||
p.position += p.position - p.prevPosition;
|
|
||||||
p.position.z -= gravity * dt * dt;
|
|
||||||
diff += Mathf.Abs(Vector3.Distance(p.prevPosition, p.position));
|
|
||||||
p.prevPosition = positionBeforeUpdate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < solveIterations; i++)
|
|
||||||
{
|
|
||||||
for (int s = 0; s < rope.sticks.Length; s++)
|
|
||||||
{
|
|
||||||
Stick stick = rope.sticks[order[s]];
|
|
||||||
if (stick.dead)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Vector3 stickCentre = (stick.A.position + stick.B.position) / 2;
|
|
||||||
Vector3 stickDir = (stick.A.position - stick.B.position).normalized;
|
|
||||||
float length = Vector2.Distance(stick.A.position, stick.B.position);
|
|
||||||
|
|
||||||
if (length > stick.desiredLength || constrainStickMinLength)
|
|
||||||
{
|
|
||||||
if (!stick.A.locked)
|
|
||||||
{
|
|
||||||
TryMovePointToPosition(stick.A, stickCentre + stickDir * stick.desiredLength / 2);
|
|
||||||
}
|
|
||||||
if (!stick.B.locked)
|
|
||||||
{
|
|
||||||
TryMovePointToPosition(stick.B, stickCentre - stickDir * stick.desiredLength / 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return diff;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TryMovePointToPosition(Point point, Vector3 position)
|
|
||||||
{
|
|
||||||
Vector2 moveDir = new Vector2(position.x, position.y) - new Vector2(point.position.x, point.position.y);
|
|
||||||
int stepsRequired = (int)Mathf.Ceil(moveDir.magnitude / collisionCheckDist);
|
|
||||||
moveDir.Normalize();
|
|
||||||
|
|
||||||
Vector2 initialPos = new Vector2(point.position.x, point.position.y);
|
|
||||||
bool shouldBreak = false;
|
|
||||||
for (int i = 0; i < stepsRequired; i++)
|
|
||||||
{
|
|
||||||
Vector2 newPos = Vector2.MoveTowards(new Vector2(point.position.x, point.position.y), new Vector2(position.x, position.y), collisionCheckDist);
|
|
||||||
point.position.Set(newPos.x, newPos.y, point.position.z);
|
|
||||||
|
|
||||||
foreach (var collider in Physics2D.OverlapCircleAll(point.position, ropeRadius, staticColliderMask))
|
|
||||||
{
|
|
||||||
if (collider == null) continue;
|
|
||||||
if (collider.isTrigger) continue;
|
|
||||||
|
|
||||||
// A static collider was met, dont move any further
|
|
||||||
Vector2 resolvedPos = collider.ClosestPoint(initialPos);
|
|
||||||
|
|
||||||
if (Vector2.Distance(initialPos, resolvedPos) < ignoreResolveThreshold) continue;
|
|
||||||
|
|
||||||
Vector2 penetrationDir = (resolvedPos - new Vector2(point.position.x, point.position.y)).normalized;
|
|
||||||
Vector2 finalPos = resolvedPos - penetrationDir * ropeRadius;
|
|
||||||
//Debug.Log($"resolved pos: {point.position}->{finalPos}");
|
|
||||||
point.position.Set(finalPos.x, finalPos.y, point.position.z);
|
|
||||||
shouldBreak = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (shouldBreak)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move z position
|
|
||||||
point.position.z = position.z;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleStaticCollidersOfPoint(Point p)
|
|
||||||
{
|
|
||||||
foreach (var hitCollider in Physics2D.OverlapCircleAll(p.position, ropeRadius * 1.1f, staticColliderMask))
|
|
||||||
{
|
|
||||||
if (hitCollider == null) continue;
|
|
||||||
if (hitCollider.isTrigger) continue;
|
|
||||||
|
|
||||||
// Register the squeze force this rope particle is squezing the collider
|
|
||||||
Vector2 pointPos = new Vector2(p.position.x, p.position.y);
|
|
||||||
Vector2 resolvedPos = hitCollider.ClosestPoint(pointPos);
|
|
||||||
Vector2 penetration = resolvedPos - pointPos;
|
|
||||||
Vector2 finalPos = resolvedPos - penetration.normalized * ropeRadius;
|
|
||||||
|
|
||||||
float squezeForce;
|
|
||||||
if (!colliderToSquezeForce.TryGetValue(hitCollider, out squezeForce))
|
|
||||||
colliderToSquezeForce.Add(hitCollider, squezeForce + penetration.magnitude);
|
|
||||||
else
|
|
||||||
colliderToSquezeForce[hitCollider] = squezeForce + penetration.magnitude;
|
|
||||||
|
|
||||||
p.position.Set(finalPos.x, finalPos.y, p.position.z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CreateOrderArray()
|
|
||||||
{
|
|
||||||
order = new int[rope.sticks.Length];
|
|
||||||
for (int i = 0; i < order.Length; i++)
|
|
||||||
{
|
|
||||||
order[i] = i;
|
|
||||||
}
|
|
||||||
ShuffleArray(order, rng);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static T[] ShuffleArray<T>(T[] array, System.Random prng)
|
|
||||||
{
|
|
||||||
int elementsRemainingToShuffle = array.Length;
|
|
||||||
int randomIndex = 0;
|
|
||||||
|
|
||||||
while (elementsRemainingToShuffle > 1)
|
|
||||||
{
|
|
||||||
// Choose a random element from array
|
|
||||||
randomIndex = prng.Next(0, elementsRemainingToShuffle);
|
|
||||||
T chosenElement = array[randomIndex];
|
|
||||||
|
|
||||||
// Swap the randomly chosen element with the last unshuffled element in the array
|
|
||||||
elementsRemainingToShuffle--;
|
|
||||||
array[randomIndex] = array[elementsRemainingToShuffle];
|
|
||||||
array[elementsRemainingToShuffle] = chosenElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
return array;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,2 @@
|
||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 27ac133d9e10e544ba603e07122e3359
|
guid: 7ac1e223c3333472e9cd7374cf7323d5
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -0,0 +1,703 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Unity.Netcode;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Assertions;
|
||||||
|
using UnityUtils;
|
||||||
|
|
||||||
|
public class RopeSimulatorOld : NetworkBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField]
|
||||||
|
private float gravity = 10;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private int solveIterations = 10;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
private bool constrainStickMinLength;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
public RopeJoint start, end;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
float subDivision = 50f;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
float collisionCheckDist = 0.5f;
|
||||||
|
|
||||||
|
[SerializeField, Range(0f, 1f)]
|
||||||
|
float distBetweenRopePoints = 0.1f;
|
||||||
|
|
||||||
|
[SerializeField, Range(0.01f, 1f)]
|
||||||
|
float ropeRadius;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
float ignoreResolveThreshold = 0.08f;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
LayerMask staticColliderMask;
|
||||||
|
|
||||||
|
[SerializeField, Range(0f, 100f)]
|
||||||
|
float pullForce = 20f;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
float xyGravityDampScalor = 1f;
|
||||||
|
|
||||||
|
[SerializeField, Range(0f, 20f)]
|
||||||
|
public float ropeExtendSpeed, ropeShrinkSpeed;
|
||||||
|
|
||||||
|
public float squezeDamage = 1f;
|
||||||
|
public AnimationCurve swingSpeedToDamageMultiplier;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
public float ropeMaxLength, ropeMinLength;
|
||||||
|
|
||||||
|
[Header("Rope Colliders")]
|
||||||
|
[SerializeField] GameObject colliderPrefab;
|
||||||
|
[SerializeField] string colliderTag = "Rope";
|
||||||
|
[SerializeField] Transform ropeCollidersParent;
|
||||||
|
|
||||||
|
[Header("Rendering")]
|
||||||
|
[SerializeField] LineRenderer lineRenderer;
|
||||||
|
|
||||||
|
[Header("Animaion")]
|
||||||
|
[SerializeField] float pullAnimationOvershootThreshold = 0.2f;
|
||||||
|
|
||||||
|
[Header("Netcode")]
|
||||||
|
private const float k_serverTickRate = 60f;
|
||||||
|
private const int k_rngSeed = 6969;
|
||||||
|
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;
|
||||||
|
|
||||||
|
public float Overshoot => rope.CalculateLengthOvershoot();
|
||||||
|
public bool InSwingMode => start.locked || end.locked;
|
||||||
|
|
||||||
|
public Rope rope;
|
||||||
|
|
||||||
|
Dictionary<Collider2D, float> colliderToSquezeForce = new();
|
||||||
|
|
||||||
|
public static RopeSimulatorOld instance;
|
||||||
|
|
||||||
|
private bool IsInitialized => start != null || end != null;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
if (instance == null)
|
||||||
|
{
|
||||||
|
instance = this;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Destroy(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
stateBuffer = new(k_bufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
GameManager.OnPlayersReady += PlayersReady;
|
||||||
|
|
||||||
|
if (NetworkManager.Singleton != null)
|
||||||
|
NetworkManager.Singleton.NetworkTickSystem.Tick += NetworkTick;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
GameManager.OnPlayersReady -= PlayersReady;
|
||||||
|
|
||||||
|
if (NetworkManager.Singleton != null)
|
||||||
|
NetworkManager.Singleton.NetworkTickSystem.Tick -= NetworkTick;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PlayersReady(GameObject[] players)
|
||||||
|
{
|
||||||
|
BuildRope(players[0].GetComponent<RopeJoint>(), players[1].GetComponent<RopeJoint>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BuildRope(RopeJoint start, RopeJoint end)
|
||||||
|
{
|
||||||
|
Assert.IsNotNull(start);
|
||||||
|
Assert.IsNotNull(end);
|
||||||
|
|
||||||
|
// Sanity check if rope simulator was initialized before - we are re-building the rope
|
||||||
|
if (this.start != null)
|
||||||
|
{
|
||||||
|
this.start.playerInput.ropeLengthExtend -= ExtendRope;
|
||||||
|
this.start.playerInput.ropeLengthShrinken -= ShrinkenRope;
|
||||||
|
}
|
||||||
|
if (this.end != null)
|
||||||
|
{
|
||||||
|
this.end.playerInput.ropeLengthExtend -= ExtendRope;
|
||||||
|
this.end.playerInput.ropeLengthShrinken -= ShrinkenRope;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.start = start;
|
||||||
|
this.end = end;
|
||||||
|
|
||||||
|
Rebuild();
|
||||||
|
|
||||||
|
this.start.playerInput.ropeLengthShrinken += ShrinkenRope;
|
||||||
|
this.end.playerInput.ropeLengthShrinken += ShrinkenRope;
|
||||||
|
|
||||||
|
this.start.playerInput.ropeLengthExtend += ExtendRope;
|
||||||
|
this.end.playerInput.ropeLengthExtend += ExtendRope;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShrinkenRope(int playerNumber)
|
||||||
|
{
|
||||||
|
int prevSubDivision = (int)subDivision;
|
||||||
|
subDivision -= ropeShrinkSpeed * Time.deltaTime;
|
||||||
|
subDivision = Mathf.Clamp(subDivision, ropeMinLength, ropeMaxLength);
|
||||||
|
|
||||||
|
// Only shrinken if the numeric value has changed
|
||||||
|
if (prevSubDivision - (int)subDivision <= 0) return;
|
||||||
|
|
||||||
|
// Shrink from rope point after start rope joint
|
||||||
|
List<Point> newPoints = new(rope.points.Length - 1);
|
||||||
|
for (int i = 0; i < (rope.points.Length - 1); i++)
|
||||||
|
{
|
||||||
|
newPoints.Add(rope.points[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new RopeBuilder(newPoints, new List<Stick>());
|
||||||
|
|
||||||
|
// Re-gen sticks
|
||||||
|
for (int i = 0; i < (int)subDivision; i++)
|
||||||
|
{
|
||||||
|
builder.ConnectPointsWithDesiredLength(i, i + 1, distBetweenRopePoints);
|
||||||
|
}
|
||||||
|
rope = builder.Build();
|
||||||
|
|
||||||
|
RebuildRopeColliders();
|
||||||
|
CreateOrderArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExtendRope(int playerNumber)
|
||||||
|
{
|
||||||
|
int prevSubDivision = (int)subDivision;
|
||||||
|
subDivision += ropeExtendSpeed * Time.deltaTime;
|
||||||
|
subDivision = Mathf.Clamp(subDivision, ropeMinLength, ropeMaxLength);
|
||||||
|
|
||||||
|
// Only extend if the numeric value has changed
|
||||||
|
if (prevSubDivision - (int)subDivision >= 0) return;
|
||||||
|
|
||||||
|
// Extend from rope point after start rope point
|
||||||
|
List<Point> newPoints = new(rope.points.Length + 1);
|
||||||
|
newPoints.Add(new Point(rope.points[1].position));
|
||||||
|
for (int i = 1; i < rope.points.Length; i++)
|
||||||
|
{
|
||||||
|
newPoints.Add(rope.points[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new RopeBuilder(newPoints, new List<Stick>());
|
||||||
|
|
||||||
|
// Re-gen sticks
|
||||||
|
for (int i = 0; i < (int)subDivision; i++)
|
||||||
|
{
|
||||||
|
//Debug.Log($"Reg-gen stick. from: {i} to {i + 1}, with dist: {distBetweenRopePoints}");
|
||||||
|
builder.ConnectPointsWithDesiredLength(i, i + 1, distBetweenRopePoints);
|
||||||
|
}
|
||||||
|
rope = builder.Build();
|
||||||
|
|
||||||
|
RebuildRopeColliders();
|
||||||
|
CreateOrderArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnDestroy()
|
||||||
|
{
|
||||||
|
base.OnDestroy();
|
||||||
|
|
||||||
|
// May never have been initialized
|
||||||
|
if (!IsInitialized) return;
|
||||||
|
|
||||||
|
start.playerInput.ropeLengthShrinken -= ShrinkenRope;
|
||||||
|
end.playerInput.ropeLengthShrinken -= ShrinkenRope;
|
||||||
|
|
||||||
|
start.playerInput.ropeLengthExtend -= ExtendRope;
|
||||||
|
end.playerInput.ropeLengthExtend -= ExtendRope;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Rebuild()
|
||||||
|
{
|
||||||
|
Debug.Log("rebuild");
|
||||||
|
|
||||||
|
RopeBuilder builder = new RopeBuilder();
|
||||||
|
builder.AddPoint(new Point(start.position, locked: true));
|
||||||
|
|
||||||
|
// Build rope points
|
||||||
|
for (int i = 1; i < (int)subDivision; i++)
|
||||||
|
{
|
||||||
|
Vector3 pointPos = Vector3.Lerp(start.position, end.position, (float)i / Mathf.Floor(subDivision));
|
||||||
|
Debug.DrawRay(pointPos, (end.position - start.position).normalized);
|
||||||
|
builder.AddPoint(new Point(pointPos));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AddPoint(new Point(end.position, locked: true));
|
||||||
|
|
||||||
|
// Connect rope points
|
||||||
|
for (int i = 0; i < (int)subDivision; i++)
|
||||||
|
{
|
||||||
|
builder.ConnectPointsWithDesiredLength(i, i + 1, desiredLength: distBetweenRopePoints);
|
||||||
|
}
|
||||||
|
rope = builder.Build();
|
||||||
|
|
||||||
|
RebuildRopeColliders();
|
||||||
|
|
||||||
|
CreateOrderArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RebuildRopeColliders()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < ropeCollidersParent.childCount; i++)
|
||||||
|
{
|
||||||
|
Destroy(ropeCollidersParent.GetChild(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var point in rope.points)
|
||||||
|
{
|
||||||
|
GameObject ropeCollider = Instantiate(colliderPrefab);
|
||||||
|
ropeCollider.transform.parent = ropeCollidersParent;
|
||||||
|
|
||||||
|
ropeCollider.transform.position = point.position;
|
||||||
|
ropeCollider.tag = colliderTag;
|
||||||
|
ropeCollider.layer = LayerMask.NameToLayer("Rope");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawRope()
|
||||||
|
{
|
||||||
|
// Update line renderer
|
||||||
|
List<Vector3> positions = new List<Vector3>(this.rope.points.Length);
|
||||||
|
for (int i = 0; i < this.rope.points.Length; i++)
|
||||||
|
{
|
||||||
|
positions.Add(this.rope.points[i].position);
|
||||||
|
}
|
||||||
|
lineRenderer.positionCount = positions.Count;
|
||||||
|
lineRenderer.SetPositions(positions.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Rpc(SendTo.NotServer)]
|
||||||
|
private void ServerToClientGameStateRpc(GameState serverState)
|
||||||
|
{
|
||||||
|
this.lastReceivedServerGameState = serverState;
|
||||||
|
|
||||||
|
Debug.Log($"Received server state. Server tick: {serverState.tick}, client: {currentTick}");
|
||||||
|
|
||||||
|
// Not enough information
|
||||||
|
if (stateBuffer.Get(serverState.tick).Equals(default(GameState))) return;
|
||||||
|
|
||||||
|
// TODO: investigate why this is zero at start of game sometimes
|
||||||
|
if (stateBuffer.Get(serverState.tick).nrope.positions.Length == 0) return;
|
||||||
|
Debug.Log($"client len: {stateBuffer.Get(serverState.tick).nrope.positions.Length}, server len {serverState.nrope.positions.Length}");
|
||||||
|
|
||||||
|
Rope serverRope = Rope.FromNetworkRope(serverState.nrope, distBetweenRopePoints);
|
||||||
|
Rope oldLocalRope = Rope.FromNetworkRope(stateBuffer.Get(serverState.tick).nrope, distBetweenRopePoints);
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
stateBuffer.Add(ProcessGame(), currentTick);
|
||||||
|
|
||||||
|
// Send to clients if is server
|
||||||
|
if (IsServer)
|
||||||
|
ServerToClientGameStateRpc(stateBuffer.Get(currentTick));
|
||||||
|
}
|
||||||
|
|
||||||
|
private GameState ProcessGame()
|
||||||
|
{
|
||||||
|
GameState localState = new()
|
||||||
|
{
|
||||||
|
tick = currentTick,
|
||||||
|
nrope = Rope.ToNetworkRope(this.rope),
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
rope.points.Last().position = end.position;
|
||||||
|
|
||||||
|
float ropeDiff = Simulate(Time.fixedDeltaTime);
|
||||||
|
|
||||||
|
// Update the rope collider positions
|
||||||
|
for (int i = 0; i < rope.points.Length; i++)
|
||||||
|
{
|
||||||
|
ropeCollidersParent.GetChild(i).position = rope.points[i].position;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle static colliders
|
||||||
|
foreach (var point in rope.points)
|
||||||
|
{
|
||||||
|
if (point.locked) continue;
|
||||||
|
|
||||||
|
HandleStaticCollidersOfPoint(point);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constrain start transform based on overshoot
|
||||||
|
float overshoot = rope.CalculateLengthOvershoot();
|
||||||
|
PlayerPullAnimation(overshoot);
|
||||||
|
PullPlayers(overshoot);
|
||||||
|
|
||||||
|
// Handle squeze kills
|
||||||
|
foreach (var collider in colliderToSquezeForce)
|
||||||
|
{
|
||||||
|
ISquezeDamageReceiver squezeDamageReceiver = collider.Key.transform.root.GetComponent<ISquezeDamageReceiver>();
|
||||||
|
if (squezeDamageReceiver == null)
|
||||||
|
squezeDamageReceiver = collider.Key.GetComponent<ISquezeDamageReceiver>();
|
||||||
|
|
||||||
|
if (squezeDamageReceiver == null) continue;
|
||||||
|
|
||||||
|
float swingMultiplier = InSwingMode ? swingSpeedToDamageMultiplier.Evaluate((start.locked ? end : start).body.velocity.magnitude) : 1f;
|
||||||
|
squezeDamageReceiver.TakeSquezeDamage(collider.Value * squezeDamage * swingMultiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle xy dampening on z gravity
|
||||||
|
foreach (var point in rope.points)
|
||||||
|
{
|
||||||
|
if (point.position.z >= 0f) continue;
|
||||||
|
|
||||||
|
Vector2 newXYPos = Vector2.MoveTowards(new Vector2(point.position.x, point.position.y), new Vector2(point.prevPosition.x, point.prevPosition.y), Mathf.Abs(point.position.z * xyGravityDampScalor));
|
||||||
|
point.position.Set(newXYPos.x, newXYPos.y, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawRope();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlayerPullAnimation(float overshoot)
|
||||||
|
{
|
||||||
|
|
||||||
|
//if (overshoot > pullAnimationOvershootThreshold)
|
||||||
|
//{
|
||||||
|
// float startDot = Vector2.Dot((start.position - rope.points[1].position).normalized, start.playerInput.movement);
|
||||||
|
// if (startDot > 0.35f)
|
||||||
|
// {
|
||||||
|
// start.playerAnimationHandler?.animator.SetBool("IsPulling", true);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// float endDot = Vector2.Dot((end.position - rope.points[rope.points.Count - 2].position).normalized, end.playerInput.movement);
|
||||||
|
// if (endDot > 0.35f)
|
||||||
|
// {
|
||||||
|
// end.playerAnimationHandler?.animator.SetBool("IsPulling", true);
|
||||||
|
// }
|
||||||
|
|
||||||
|
//}
|
||||||
|
//else
|
||||||
|
//{
|
||||||
|
// start.playerAnimationHandler?.animator.SetBool("IsPulling", false);
|
||||||
|
// end.playerAnimationHandler?.animator.SetBool("IsPulling", false);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PullPlayers(float overshoot)
|
||||||
|
{
|
||||||
|
if (overshoot <= 0f) return;
|
||||||
|
|
||||||
|
//start.position = prevStartPos;
|
||||||
|
float divider = !start.locked && !end.locked ? 2f : 1f;
|
||||||
|
|
||||||
|
if (!start.locked)
|
||||||
|
{
|
||||||
|
Vector2 pullDirection = (rope.points[1].position - start.position).normalized;
|
||||||
|
Vector2 force = pullDirection * overshoot * (pullForce / divider);
|
||||||
|
start.body.AddForce(force);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
start.body.velocity *= 0;
|
||||||
|
}
|
||||||
|
if (!end.locked)
|
||||||
|
{
|
||||||
|
Vector2 pullDirection = (rope.points[rope.points.Length - 2].position - end.position).normalized;
|
||||||
|
Vector2 force = pullDirection * overshoot * (pullForce / divider);
|
||||||
|
end.body.AddForce(force);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
end.body.velocity *= 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDrawGizmos()
|
||||||
|
{
|
||||||
|
if (!IsInitialized) return;
|
||||||
|
if (!Application.isPlaying) return;
|
||||||
|
|
||||||
|
// Local rope
|
||||||
|
Gizmos.color = Color.green;
|
||||||
|
foreach (var point in rope.points)
|
||||||
|
{
|
||||||
|
Gizmos.DrawSphere(point.position, ropeRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last received server rope
|
||||||
|
if (lastReceivedServerGameState.Equals(default(GameState))) return;
|
||||||
|
|
||||||
|
Gizmos.color = Color.red;
|
||||||
|
foreach (var point in Rope.FromNetworkRope(lastReceivedServerGameState.nrope, distBetweenRopePoints).points)
|
||||||
|
{
|
||||||
|
Gizmos.DrawSphere(point.position, ropeRadius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float Simulate(float dt)
|
||||||
|
{
|
||||||
|
float diff = 0f;
|
||||||
|
foreach (Point p in rope.points)
|
||||||
|
{
|
||||||
|
if (!p.locked)
|
||||||
|
{
|
||||||
|
Vector3 positionBeforeUpdate = p.position;
|
||||||
|
p.position += p.position - p.prevPosition;
|
||||||
|
p.position.z -= gravity * dt * dt;
|
||||||
|
diff += Mathf.Abs(Vector3.Distance(p.prevPosition, p.position));
|
||||||
|
p.prevPosition = positionBeforeUpdate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < solveIterations; i++)
|
||||||
|
{
|
||||||
|
for (int s = 0; s < rope.sticks.Length; s++)
|
||||||
|
{
|
||||||
|
Stick stick = rope.sticks[order[s]];
|
||||||
|
if (stick.dead)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Vector3 stickCentre = (stick.A.position + stick.B.position) / 2;
|
||||||
|
Vector3 stickDir = (stick.A.position - stick.B.position).normalized;
|
||||||
|
float length = Vector2.Distance(stick.A.position, stick.B.position);
|
||||||
|
|
||||||
|
if (length > stick.desiredLength || constrainStickMinLength)
|
||||||
|
{
|
||||||
|
if (!stick.A.locked)
|
||||||
|
{
|
||||||
|
TryMovePointToPosition(stick.A, stickCentre + stickDir * stick.desiredLength / 2);
|
||||||
|
}
|
||||||
|
if (!stick.B.locked)
|
||||||
|
{
|
||||||
|
TryMovePointToPosition(stick.B, stickCentre - stickDir * stick.desiredLength / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryMovePointToPosition(Point point, Vector3 position)
|
||||||
|
{
|
||||||
|
Vector2 moveDir = new Vector2(position.x, position.y) - new Vector2(point.position.x, point.position.y);
|
||||||
|
int stepsRequired = (int)Mathf.Ceil(moveDir.magnitude / collisionCheckDist);
|
||||||
|
moveDir.Normalize();
|
||||||
|
|
||||||
|
Vector2 initialPos = new Vector2(point.position.x, point.position.y);
|
||||||
|
bool shouldBreak = false;
|
||||||
|
for (int i = 0; i < stepsRequired; i++)
|
||||||
|
{
|
||||||
|
Vector2 newPos = Vector2.MoveTowards(new Vector2(point.position.x, point.position.y), new Vector2(position.x, position.y), collisionCheckDist);
|
||||||
|
point.position.Set(newPos.x, newPos.y, point.position.z);
|
||||||
|
|
||||||
|
foreach (var collider in Physics2D.OverlapCircleAll(point.position, ropeRadius, staticColliderMask))
|
||||||
|
{
|
||||||
|
if (collider == null) continue;
|
||||||
|
if (collider.isTrigger) continue;
|
||||||
|
|
||||||
|
// A static collider was met, dont move any further
|
||||||
|
Vector2 resolvedPos = collider.ClosestPoint(initialPos);
|
||||||
|
|
||||||
|
if (Vector2.Distance(initialPos, resolvedPos) < ignoreResolveThreshold) continue;
|
||||||
|
|
||||||
|
Vector2 penetrationDir = (resolvedPos - new Vector2(point.position.x, point.position.y)).normalized;
|
||||||
|
Vector2 finalPos = resolvedPos - penetrationDir * ropeRadius;
|
||||||
|
//Debug.Log($"resolved pos: {point.position}->{finalPos}");
|
||||||
|
point.position.Set(finalPos.x, finalPos.y, point.position.z);
|
||||||
|
shouldBreak = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (shouldBreak)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move z position
|
||||||
|
point.position.z = position.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleStaticCollidersOfPoint(Point p)
|
||||||
|
{
|
||||||
|
foreach (var hitCollider in Physics2D.OverlapCircleAll(p.position, ropeRadius * 1.1f, staticColliderMask))
|
||||||
|
{
|
||||||
|
if (hitCollider == null) continue;
|
||||||
|
if (hitCollider.isTrigger) continue;
|
||||||
|
|
||||||
|
// Register the squeze force this rope particle is squezing the collider
|
||||||
|
Vector2 pointPos = new Vector2(p.position.x, p.position.y);
|
||||||
|
Vector2 resolvedPos = hitCollider.ClosestPoint(pointPos);
|
||||||
|
Vector2 penetration = resolvedPos - pointPos;
|
||||||
|
Vector2 finalPos = resolvedPos - penetration.normalized * ropeRadius;
|
||||||
|
|
||||||
|
float squezeForce;
|
||||||
|
if (!colliderToSquezeForce.TryGetValue(hitCollider, out squezeForce))
|
||||||
|
colliderToSquezeForce.Add(hitCollider, squezeForce + penetration.magnitude);
|
||||||
|
else
|
||||||
|
colliderToSquezeForce[hitCollider] = squezeForce + penetration.magnitude;
|
||||||
|
|
||||||
|
p.position.Set(finalPos.x, finalPos.y, p.position.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateOrderArray()
|
||||||
|
{
|
||||||
|
order = new int[rope.sticks.Length];
|
||||||
|
for (int i = 0; i < order.Length; i++)
|
||||||
|
{
|
||||||
|
order[i] = i;
|
||||||
|
}
|
||||||
|
ShuffleArray(order, rng);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T[] ShuffleArray<T>(T[] array, System.Random prng)
|
||||||
|
{
|
||||||
|
int elementsRemainingToShuffle = array.Length;
|
||||||
|
int randomIndex = 0;
|
||||||
|
|
||||||
|
while (elementsRemainingToShuffle > 1)
|
||||||
|
{
|
||||||
|
// Choose a random element from array
|
||||||
|
randomIndex = prng.Next(0, elementsRemainingToShuffle);
|
||||||
|
T chosenElement = array[randomIndex];
|
||||||
|
|
||||||
|
// Swap the randomly chosen element with the last unshuffled element in the array
|
||||||
|
elementsRemainingToShuffle--;
|
||||||
|
array[randomIndex] = array[elementsRemainingToShuffle];
|
||||||
|
array[elementsRemainingToShuffle] = chosenElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 27ac133d9e10e544ba603e07122e3359
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -0,0 +1,26 @@
|
||||||
|
using Unity.Netcode;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
[System.Serializable]
|
||||||
|
public struct RopeState : INetworkSerializable
|
||||||
|
{
|
||||||
|
public int tick;
|
||||||
|
public NetworkRope nrope;
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return tick;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
GameState other = (GameState) obj;
|
||||||
|
return this.GetHashCode() == obj.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
||||||
|
{
|
||||||
|
serializer.SerializeValue(ref tick);
|
||||||
|
nrope.NetworkSerialize(serializer);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 93a14defd8bec0de8b7ef48d5ea4c03a
|
|
@ -32,7 +32,7 @@ public class Upgrader : MonoBehaviour
|
||||||
public GameObject player2;
|
public GameObject player2;
|
||||||
|
|
||||||
public BloodComputeShader bloodManager;
|
public BloodComputeShader bloodManager;
|
||||||
public RopeSimulator rope;
|
public RopeSimulatorOld rope;
|
||||||
|
|
||||||
public Upgrades upgrades { get; private set; }
|
public Upgrades upgrades { get; private set; }
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ public class RopeSounding : MonoBehaviour
|
||||||
{
|
{
|
||||||
[SerializeField] private float PlayTime = 1f;
|
[SerializeField] private float PlayTime = 1f;
|
||||||
|
|
||||||
RopeSimulator rope;
|
RopeSimulatorOld rope;
|
||||||
|
|
||||||
AudioSource AS;
|
AudioSource AS;
|
||||||
float playTimeLeft = 0;
|
float playTimeLeft = 0;
|
||||||
|
@ -44,4 +44,4 @@ public class RopeSounding : MonoBehaviour
|
||||||
AS.Pause();
|
AS.Pause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,8 @@ InputManager:
|
||||||
m_Name: Horizontal
|
m_Name: Horizontal
|
||||||
descriptiveName:
|
descriptiveName:
|
||||||
descriptiveNegativeName:
|
descriptiveNegativeName:
|
||||||
negativeButton: left
|
negativeButton:
|
||||||
positiveButton: right
|
positiveButton:
|
||||||
altNegativeButton: a
|
altNegativeButton: a
|
||||||
altPositiveButton: d
|
altPositiveButton: d
|
||||||
gravity: 3
|
gravity: 3
|
||||||
|
@ -25,8 +25,8 @@ InputManager:
|
||||||
m_Name: Vertical
|
m_Name: Vertical
|
||||||
descriptiveName:
|
descriptiveName:
|
||||||
descriptiveNegativeName:
|
descriptiveNegativeName:
|
||||||
negativeButton: down
|
negativeButton:
|
||||||
positiveButton: up
|
positiveButton:
|
||||||
altNegativeButton: s
|
altNegativeButton: s
|
||||||
altPositiveButton: w
|
altPositiveButton: w
|
||||||
gravity: 3
|
gravity: 3
|
||||||
|
@ -517,4 +517,36 @@ InputManager:
|
||||||
type: 0
|
type: 0
|
||||||
axis: 5
|
axis: 5
|
||||||
joyNum: 8
|
joyNum: 8
|
||||||
|
- serializedVersion: 3
|
||||||
|
m_Name: ArrowHorizontal
|
||||||
|
descriptiveName:
|
||||||
|
descriptiveNegativeName:
|
||||||
|
negativeButton: left
|
||||||
|
positiveButton: right
|
||||||
|
altNegativeButton:
|
||||||
|
altPositiveButton:
|
||||||
|
gravity: 1000
|
||||||
|
dead: 0.001
|
||||||
|
sensitivity: 1000
|
||||||
|
snap: 0
|
||||||
|
invert: 0
|
||||||
|
type: 0
|
||||||
|
axis: 5
|
||||||
|
joyNum: 8
|
||||||
|
- serializedVersion: 3
|
||||||
|
m_Name: ArrowVertical
|
||||||
|
descriptiveName:
|
||||||
|
descriptiveNegativeName:
|
||||||
|
negativeButton: down
|
||||||
|
positiveButton: up
|
||||||
|
altNegativeButton:
|
||||||
|
altPositiveButton:
|
||||||
|
gravity: 1000
|
||||||
|
dead: 0.001
|
||||||
|
sensitivity: 1000
|
||||||
|
snap: 0
|
||||||
|
invert: 0
|
||||||
|
type: 0
|
||||||
|
axis: 5
|
||||||
|
joyNum: 8
|
||||||
m_UsePhysicalKeys: 1
|
m_UsePhysicalKeys: 1
|
||||||
|
|
Loading…
Reference in New Issue