- Home /
How can I allow the rope to correctly interact with Box and Polygon colliders?
In order to build 2d rope physics in my game, I download the github project from Jongallant in this link. In my game, I want the rope to collide with objects with different types of colliders such as box collider or polygon collider (the algorithm he provided only works with circle colliders).
I made modification to the code of the project file. In Rope script, I have the following lines :
using System.Collections.Generic;
using UnityEngine;
public class Rope : MonoBehaviour
{
LineRenderer LineRenderer;
Vector3[] LinePositions;
private List<RopeNode> RopeNodes = new List<RopeNode>();
private float NodeDistance = 0.2f;
private int TotalNodes = 50;
private float RopeWidth = 0.1f;
Camera Camera;
int LayerMask = 1;
ContactFilter2D ContactFilter;
RaycastHit2D[] RaycastHitBuffer = new RaycastHit2D[10];
RaycastHit2D[] RaycastHitBuffer1 = new RaycastHit2D[10];
Collider2D[] ColliderHitBuffer = new Collider2D[10];
Vector3 Gravity = new Vector2(0f, -5f);
Vector2 Node1Lock;
void Start()
{
Camera = Camera.main;
ContactFilter = new ContactFilter2D
{
layerMask = LayerMask,
useTriggers = false,
};
LineRenderer = this.GetComponent<LineRenderer>();
// Generate some rope nodes based on properties
Vector3 startPosition = Vector2.zero;
for (int i = 0; i < TotalNodes; i++)
{
RopeNode node = (GameObject.Instantiate(Resources.Load("RopeNode") as GameObject)).GetComponent<RopeNode>();
node.transform.position = startPosition;
node.PreviousPosition = startPosition;
RopeNodes.Add(node);
startPosition.y -= NodeDistance;
}
// for line renderer data
LinePositions = new Vector3[TotalNodes];
}
void Update()
{
// Attach rope end to mouse click position
if (Input.GetMouseButtonDown(0))
{
Node1Lock = Camera.ScreenToWorldPoint(Input.mousePosition);
}
DrawRope();
}
private void FixedUpdate()
{
Simulate();
// Higher iteration results in stiffer ropes and stable simulation
for (int i = 0; i < 80; i++)
{
ApplyConstraint();
// Playing around with adjusting collisions at intervals - still stable when iterations are skipped
if (i % 2 == 1)
AdjustCollisions();
}
}
private void Simulate()
{
// step each node in rope
for (int i = 0; i < TotalNodes; i++)
{
// derive the velocity from previous frame
Vector3 velocity = RopeNodes[i].transform.position - RopeNodes[i].PreviousPosition;
RopeNodes[i].PreviousPosition = RopeNodes[i].transform.position;
// calculate new position
Vector3 newPos = RopeNodes[i].transform.position + velocity;
newPos += Gravity * Time.fixedDeltaTime;
Vector3 direction = RopeNodes[i].transform.position - newPos;
// cast ray towards this position to check for a collision
int result = -1;
result = Physics2D.CircleCast(RopeNodes[i].transform.position, RopeNodes[i].transform.localScale.x / 2f, -direction.normalized, ContactFilter, RaycastHitBuffer, direction.magnitude);
if (result > 0)
{
for (int n = 0; n < result; n++)
{
if (RaycastHitBuffer[n].collider.gameObject.layer == 9 || RaycastHitBuffer[n].collider.gameObject.layer == 10)
{
Vector2 hitPos = RaycastHitBuffer[n].point;
newPos = hitPos;
break;
}
}
}
RopeNodes[i].transform.position = newPos;
}
}
private void AdjustCollisions()
{
// Loop rope nodes and check if currently colliding
for (int i = 0; i < TotalNodes - 1; i++)
{
RopeNode node = RopeNodes[i];
int result = -1;
result = Physics2D.OverlapCircleNonAlloc(node.transform.position, node.transform.localScale.x / 2f, ColliderHitBuffer);
if (result > 0)
{
for (int n = 0; n < result; n++)
{
if (ColliderHitBuffer[n].gameObject.layer != 8)
{
if(ColliderHitBuffer[n].gameObject.layer == 9) {
// Adjust the rope node position to be outside collision
Vector3 collidercenter = ColliderHitBuffer[n].transform.position;
Vector3 collisionDirection = node.transform.position - collidercenter;
Vector3 hitPos = collidercenter + collisionDirection.normalized * ((ColliderHitBuffer[n].transform.localScale.x / 2f) + (node.transform.localScale.x / 2f));
node.transform.position = hitPos;
break;
} else if (ColliderHitBuffer[n].gameObject.layer == 10) {
Vector3 velocity = RopeNodes[i].transform.position - RopeNodes[i].PreviousPosition;
Vector3 newPos = RopeNodes[i].transform.position + velocity;
newPos += Gravity * Time.fixedDeltaTime;
Vector3 direction = RopeNodes[i].transform.position - newPos;
int result1 = -1;
result1 = Physics2D.CircleCast(RopeNodes[i].transform.position, RopeNodes[i].transform.localScale.x / 2f, -direction.normalized, ContactFilter, RaycastHitBuffer1, direction.magnitude);
if (result1 > 0)
{
for (int m = 0; m < result1; m++)
{
if (RaycastHitBuffer1[m].collider.gameObject.layer == 10)
{
Vector3 collidercenter = ColliderHitBuffer[n].transform.position;
Vector3 collisionDirection = node.transform.position - collidercenter;
Vector3 raycastHit2DPoint = new Vector3(RaycastHitBuffer1[n].point.x, RaycastHitBuffer1[n].point.y, 0);
Vector3 hitPos1 = raycastHit2DPoint + collisionDirection.normalized * (node.transform.localScale.x / 2f);
node.transform.position = hitPos1;
break;
}
}
}
}
}
}
}
}
}
private void ApplyConstraint()
{
// Check if the first node is clamped to the scene or is follwing the mouse
if (Node1Lock != Vector2.zero)
{
RopeNodes[0].transform.position = Node1Lock;
}
else
{
RopeNodes[0].transform.position = Camera.main.ScreenToWorldPoint(Input.mousePosition);
}
for (int i = 0; i < TotalNodes - 1; i++)
{
RopeNode node1 = RopeNodes[i];
RopeNode node2 = RopeNodes[i + 1];
// Get the current distance between rope nodes
float currentDistance = (node1.transform.position - node2.transform.position).magnitude;
float difference = Mathf.Abs(currentDistance - NodeDistance);
Vector2 direction = Vector2.zero;
// determine what direction we need to adjust our nodes
if (currentDistance > NodeDistance)
{
direction = (node1.transform.position - node2.transform.position).normalized;
}
else if (currentDistance < NodeDistance)
{
direction = (node2.transform.position - node1.transform.position).normalized;
}
// calculate the movement vector
Vector3 movement = direction * difference;
// apply correction
node1.transform.position -= (movement * 0.5f);
node2.transform.position += (movement * 0.5f);
}
}
private void DrawRope()
{
LineRenderer.startWidth = RopeWidth;
LineRenderer.endWidth = RopeWidth;
for (int n = 0; n < TotalNodes; n++)
{
LinePositions[n] = new Vector3(RopeNodes[n].transform.position.x, RopeNodes[n].transform.position.y, 0);
}
LineRenderer.positionCount = LinePositions.Length;
LineRenderer.SetPositions(LinePositions);
}
}
[ The layer 9 contains the circle collider objects and the layer 10 contains the box collider objects]
In the function Apply Constraints, I added this in order to manage collision with a box collider :
else if (ColliderHitBuffer[n].gameObject.layer == 10) {
Vector3 velocity = RopeNodes[i].transform.position - RopeNodes[i].PreviousPosition;
Vector3 newPos = RopeNodes[i].transform.position + velocity;
newPos += Gravity * Time.fixedDeltaTime;
Vector3 direction = RopeNodes[i].transform.position - newPos;
int result1 = -1;
result1 = Physics2D.CircleCast(RopeNodes[i].transform.position, RopeNodes[i].transform.localScale.x / 2f, -direction.normalized, ContactFilter, RaycastHitBuffer1, direction.magnitude);
if (result1 > 0) {
for (int m = 0; m < result1; m++) {
if (RaycastHitBuffer1[m].collider.gameObject.layer == 10) {
Vector3 collidercenter = ColliderHitBuffer[n].transform.position;
Vector3 collisionDirection = node.transform.position - collidercenter;
Vector3 raycastHit2DPoint = new Vector3(RaycastHitBuffer1[n].point.x, RaycastHitBuffer1[n].point.y, 0);
Vector3 hitPos1 = raycastHit2DPoint + collisionDirection.normalized * (node.transform.localScale.x / 2f);
node.transform.position = hitPos1;
break;
}
}
}
}
But, it is not working as intended! The main issues is that the rope gets through some circles, and it "cuts corners" of the box colliders. Here is what happens: Youtube Video
Can somebody help me please?