Hello,
I recently upgraded to Obi Rope 7 and encountered a few issues issues with the ropes not behaving as they did in Obi Rope 6. I've kept the same settings from Obi 6 and am using the default settings for the new solver options (except for substeps, which I set to match the value I used in the Obi Fixed Updater).
In my game, the player can shoot ropes. I originally based this mechanic on the Extendable Grappling Hook script provided with Obi Rope, although I've modified it over time. After upgrading to Obi 7, I found that I could no longer shoot ropes at all. I noticed the Extendable Grappling Hook script had changed since Obi 6, so I tried to apply similar changes to my custom rope generation script. This allowed rope generation to work again, but I'm not sure if I implemented it correctly, and I suspect this might be related to some of the issues I'm experiencing (particularly issues 2 & 3 below).
I've attached a video showing the differences between Obi 6 and Obi 7.
There are the issues I'm having:
Any advice or suggestions on how to resolve these issues would be greatly appreciated!
Thanks in advance!
I recently upgraded to Obi Rope 7 and encountered a few issues issues with the ropes not behaving as they did in Obi Rope 6. I've kept the same settings from Obi 6 and am using the default settings for the new solver options (except for substeps, which I set to match the value I used in the Obi Fixed Updater).
In my game, the player can shoot ropes. I originally based this mechanic on the Extendable Grappling Hook script provided with Obi Rope, although I've modified it over time. After upgrading to Obi 7, I found that I could no longer shoot ropes at all. I noticed the Extendable Grappling Hook script had changed since Obi 6, so I tried to apply similar changes to my custom rope generation script. This allowed rope generation to work again, but I'm not sure if I implemented it correctly, and I suspect this might be related to some of the issues I'm experiencing (particularly issues 2 & 3 below).
I've attached a video showing the differences between Obi 6 and Obi 7.
There are the issues I'm having:
- Increased Rope Bounciness:
- When lifting certain rigidbodies, the ropes are much more bouncy than they were in Obi 6.
- When lifting certain rigidbodies, the ropes are much more bouncy than they were in Obi 6.
- Rope Flashing in the Wrong Location:
- Occasionally, when generating a rope, it momentarily appears to connect from the player’s hand to the world origin for one frame.
- Occasionally, when generating a rope, it momentarily appears to connect from the player’s hand to the world origin for one frame.
- Unpredictable Behavior When Shooting Ropes at Rigidbodies:
- When shooting ropes at rigidbodies (e.g., a box), the behavior is inconsistent. Sometimes the box is lifted into the air or pushed around unexpectedly. I think this might be related to the issue mentioned above.
- When shooting ropes at rigidbodies (e.g., a box), the behavior is inconsistent. Sometimes the box is lifted into the air or pushed around unexpectedly. I think this might be related to the issue mentioned above.
- Increased Rope Jitter:
- When ropes are left to settle, there seems to be more residual movement in Obi 7 compared to Obi 6. I'm using the same sleep threshold in both versions.
- When ropes are left to settle, there seems to be more residual movement in Obi 7 compared to Obi 6. I'm using the same sleep threshold in both versions.
Code:
public class RopeGenerator : MonoBehaviour{
public float breakThreshhold = 0.1f;
public float extraRopeLengthOnSpawn = 0;
[SerializeField] private bool _useTearing;
[SerializeField] private float _tearResistance = 800;
[Range(0, 2)]
[SerializeField] private float _stretchingScale = 1;
public ObiSolver solver;
public Material material;
public ObiRopeSection section;
[Range(0, 1)]
public float hookResolution = 0.5f;
//public float hookExtendRetractSpeed = 2;
[FormerlySerializedAs("hookShootSpeed")]
public float ropeShootSpeed = 1;
public int particlePoolSize = 100;
public float ropeParticleInverseMass = 100.0f;
//public ObiRope rope;
[SerializeField] private float ropeThickness = 0.5f;
[SerializeField] private float cursorParticle = 4f;
[SerializeField] private float sourceParticle = 2f;
[SerializeField] private bool particleRendererOn = false;
[SerializeField] private bool _useSurfaceCollision;
public void InitialiseRope(RopeData ropeData, Action ropeInitialised = null) {
if (ropeData.IsInitialised) {
return;
}
// Setup a blueprint for the rope:
ropeData.Blueprint = ScriptableObject.CreateInstance<ObiRopeBlueprint>();
ropeData.Blueprint.resolution = hookResolution;
ropeData.Blueprint.pooledParticles = particlePoolSize;
// Create both the rope and the solver:
ropeData.ObiRope = ropeData.RopeObject.AddComponent<ObiRope>();
ropeData.RopeObject.AddComponent<ObiParticleRenderer>().enabled = particleRendererOn;
ObiRopeExtrudedRenderer ropeRenderer = ropeData.RopeObject.AddComponent<ObiRopeExtrudedRenderer>();
ropeRenderer.section = section;
ropeRenderer.uvScale = new Vector2(1, 4);
ropeRenderer.normalizeV = false;
ropeRenderer.uvAnchor = 1;
ropeRenderer.material = material;
//ropeData.RopeObject.GetComponent<MeshRenderer>().material = material;
// Tweak rope parameters:
ropeData.ObiRope.maxBending = 0.02f;
// Add a cursor to be able to change rope length:
ropeData.Cursor = ropeData.RopeObject.AddComponent<ObiRopeCursor>();
ropeData.Cursor.cursorMu = 0;
ropeData.Cursor.direction = false;
ropeData.ObiRope.tearingEnabled = _useTearing;
if (_useTearing) {
ropeData.ObiRope.tearResistanceMultiplier = _tearResistance;
}
ropeData.ObiRope.surfaceCollisions = _useSurfaceCollision;
ropeData.ObiRope.stretchingScale = _stretchingScale;
StartCoroutine(InitialiseRopeRoutine(ropeData, ropeInitialised));
}
IEnumerator InitialiseRopeRoutine(RopeData ropeData, Action ropeInitialised = null) {
var localHit = Vector3.forward;
// Procedurally generate the rope path (just a short segment, as we will extend it over time):
int filterCollision = ObiUtils.MakeFilter(ObiUtils.CollideWithEverything, 0);
int filterNoCollision = ObiUtils.MakeFilter(ObiUtils.CollideWithNothing, 0);
ropeData.Blueprint.path.Clear();
ropeData.Blueprint.path.AddControlPoint(Vector3.zero, Vector3.zero, Vector3.zero, Vector3.up, 1 / ropeParticleInverseMass, 0.1f, ropeThickness, filterCollision, Color.white,
"Rope start");
ropeData.Blueprint.path.AddControlPoint(localHit.normalized * 0.5f, Vector3.zero, Vector3.zero, Vector3.up, 1 / ropeParticleInverseMass, 0.1f, ropeThickness, filterCollision,
Color.white, "Rope end");
ropeData.Blueprint.path.FlushEvents();
// Generate the particle representation of the rope (wait until it has finished):
yield return ropeData.Blueprint.Generate();
ropeData.IsInitialised = true;
yield return null;
ropeInitialised?.Invoke();
}
private void LayParticlesInStraightLine(Vector3 origin, Vector3 direction, ObiRope rope)
{
// placing all particles in a straight line, respecting rope length
float length = 0;
for (int i = 0; i < rope.elements.Count; ++i)
{
int p1 = rope.elements[i].particle1;
int p2 = rope.elements[i].particle2;
solver.prevPositions[p1] = solver.positions[p1] = origin + direction * length;
length += rope.elements[i].restLength;
solver.prevPositions[p2] = solver.positions[p2] = origin + direction * length;
}
}
public void GenerateRope(RopeData ropeData, RopeControlPoint sourceRcp, RopeControlPoint endRcp, Action<RopeData> onRopeGenerated, bool generateImmediately) {
StartCoroutine(GenerateRopeRoutine());
IEnumerator GenerateRopeRoutine() {
var endRcpPosition = endRcp.transform.position;
yield return null;
// Clear pin constraints:
var pinConstraints = ropeData.ObiRope.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiConstraints<ObiPinConstraintsBatch>;
pinConstraints.Clear();
if (generateImmediately) {
InitialiseRope(ropeData);
}
// Set the blueprint (this adds particles/constraints to the solver and starts simulating them).
ropeData.ObiRope.ropeBlueprint = ropeData.Blueprint;
ropeData.ObiRope.GetComponent<ObiRopeExtrudedRenderer>().enabled = true;
// wait for the solver to load the rope, after the next physics step:
yield return new WaitForFixedUpdate();
yield return null;
// set masses to zero, as we're going to override positions while we extend the rope:
if (!generateImmediately) {
for (int i = 0; i < ropeData.ObiRope.activeParticleCount; ++i)
solver.invMasses[ropeData.ObiRope.solverIndices[i]] = 0;
}
float currentLength = 0;
const float maxPercentOfDistance = 0.75f; // the max percent of the distance between the source and the hit that the rope can extend to in one frame
//move definitions out of loop
Vector3 origin;
Vector3 direction;
// while the last particle hasn't reached the hit, extend the rope:
do {
//--Set Cursor and source to a set number of particles away from the end of the rope
SetCursorAndSourceMu(ropeData, cursorParticle, sourceParticle);
// calculate rope origin in solver space:
origin = solver.transform.InverseTransformPoint(sourceRcp.transform.position); //spawn from rope origin
// update direction and distance to hook point:
direction = endRcpPosition - origin;
float distance = direction.magnitude;
direction.Normalize();
LayParticlesInStraightLine(origin, direction, ropeData.ObiRope);
if (generateImmediately) {
ropeData.Cursor.ChangeLength(distance + extraRopeLengthOnSpawn);
}
else {
// calculate the max increment for this frame to stop the increment from being larger than the distance.
float maxIncrement = distance * maxPercentOfDistance;
var increment = ropeShootSpeed;
increment = Mathf.Min(maxIncrement, increment);
// increase length:
currentLength += increment;
// if we have reached the desired length, break the loop:
if (currentLength >= distance) {
ropeData.Cursor.ChangeLength(distance + extraRopeLengthOnSpawn);
break;
}
// change rope length (clamp to distance between rope origin and hook to avoid overshoot)
ropeData.Cursor.ChangeLength(Mathf.Min(distance, currentLength));
}
yield return null;
} while (!generateImmediately);
// wait for the last length change to take effect, and ensure the rope is straight:
yield return new WaitForFixedUpdate();
yield return null;
LayParticlesInStraightLine(origin, direction, ropeData.ObiRope);
// restore masses so that the simulation takes over now that the rope is in place:
for (int i = 0; i < ropeData.ObiRope.activeParticleCount; ++i)
solver.invMasses[ropeData.ObiRope.solverIndices[i]] = ropeParticleInverseMass; // 1/0.1 = 10
// Pin both ends of the rope (this enables two-way interaction between character and rope):
var batch = new ObiPinConstraintsBatch();
batch.AddConstraint(ropeData.ObiRope.elements[0].particle1, sourceRcp.ObiCollider, Vector3.zero, Quaternion.identity, 0, 1000, breakThreshhold);
batch.AddConstraint(ropeData.ObiRope.elements[^1].particle2, endRcp.ObiCollider, Vector3.zero, Quaternion.identity, 0, 1000, breakThreshhold);
batch.activeConstraintCount = 2;
pinConstraints.AddBatch(batch);
ropeData.ObiRope.SetConstraintsDirty(Oni.ConstraintType.Pin);
//--Set Cursor and source to a set number of particles away from the end of the rope
SetCursorAndSourceMu(ropeData, cursorParticle, sourceParticle);
//Add Rope Data to both Control points
endRcp.RopeActivated(ropeData); // must happen before onRopeGenerated Electrics needs to know the RCPs Rope Data
endRcp.InitialisePointOnRopeGenerated(ropeData); // must happen before onRopeGenerated Electrics needs to know the RCPs Rope Data
onRopeGenerated?.Invoke(ropeData); // calls hand wait finished, source.RopeActivated, RopeManager.RopeGenerated and RcpAttachPoint.AttachRcp
}
}
public void SetCursorAndSourceMu(RopeData ropeData, float cursorParticlesFromEnd, float sourceParticlesFromEnd) {
cursorParticlesFromEnd = Mathf.Clamp(cursorParticlesFromEnd, 2.0f, (float)ropeData.ObiRope.activeParticleCount - 2);
sourceParticlesFromEnd = Mathf.Clamp(sourceParticlesFromEnd, 2.0f, (float)ropeData.ObiRope.activeParticleCount - 2);
ropeData.Cursor.cursorMu = 1 - cursorParticlesFromEnd / ropeData.ObiRope.activeParticleCount;
ropeData.Cursor.sourceMu = 1 - sourceParticlesFromEnd / ropeData.ObiRope.activeParticleCount;
}
}
Any advice or suggestions on how to resolve these issues would be greatly appreciated!
Thanks in advance!