Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
159 views
in Technique[技术] by (71.8m points)

unity3d - Translated grapple physics from Processing to Unity to get different results

tl;dr Moving my game from Processing to Unity. Code responsible for grappling by manually changing the player's velocity doesn't work even though it's basically copy/pasted.


Hi, I've been working on a project of mine over the summer on Processing, and last week I decided to translate it over to Unity.

What I'm having a problem with is the grapple/rope physics. It's supposed to essentially keep the player inside a circle (made by the endpoint of the rope and the length of the rope). When the player falls outside of this circle, the player's position is moved back to the edge of the circle and the player's velocity is set to tangent of the circle.

Decreasing the length of the rope while swinging is supposed to speed you up. (See Floating Point)

On Processing, it works perfectly just as described above, but when I basically copy/pasted the code into unity it loses momentum too quickly (always ends up stopping at the same angle on the other side the player started on). Here is the code for both (run on each physics frame):

(I've also made some images to describe the motion that both versions produce)

Processing

Code

(warning: bad and redundant)

physics update:

exists = (endPoint != null);
if(lgth<=0) lgth = 1;
if(exists) {
  currentLength = phs.position.dist(endPoint);
  if(currentLength > lgth) {
    float angle = getAngle(endPoint, phs.position);
    phs.addPosition(abs(currentLength - lgth), angle);
    float angleBetween = getAngle(phs.position, endPoint);
    PVector relativeVelocity = new PVector(phs.velocity.x + phs.position.x, phs.velocity.y + phs.position.y);
    float displacement = angleBetween - 90;

    Line l1 = lineFromTwoPoints(relativeVelocity, endPoint);
    Line l2 = lineFromAngle(phs.position, displacement);
    PVector pointToLerpTo = intersection(l1, l2);
    if(pointToLerpTo!=null) {
      phs.velocity.x = pointToLerpTo.x-phs.position.x;
      phs.velocity.y = pointToLerpTo.y-phs.position.y;
    }
    else phs.velocity.mult(0);
  }
}

when the player shortens the rope, speed increases:

if(exists) {
  float newLgth = lgth-d;
  float distance = getDistance(phs.position, endPoint);
  if(distance > newLgth) {
    float ratio = (distance-newLgth)/lgth;
    phs.velocity.setMag(phs.velocity.mag()*(1+ratio));
  }
  lgth = newLgth;
}

Motion from Processing (good)

Player starts by moving downwards at left edge of rope circle. Doesn't lose speed and continues going around multiple times until gravity slows it down.

Unity

Code

both code blocks from above are handled in the same place here, under FixedUpdate() (problematic part seems to be the velocity section)

distance = Vector2.Distance(transform.position, endpoint);
if(connected && distance > length) {
    //lerp position -> endpoint// keep gameObject within length of the rope
    float posLerpAmount = (distance - length) / distance;
    transform.position = Vector2.Lerp(transform.position, endpoint, posLerpAmount);

    //'lerp' velocity -> endpoint// keep the velocity locked to the tangent of the circle around the endpoint
    Vector2 relativeVelocity = GetComponent<Rigidbody2D>().velocity + (Vector2)transform.position;
    Line l1 = Geometry.LineFromTwoPoints(relativeVelocity, endpoint);
    Line l2 = Geometry.LineFromAngle(transform.position, Geometry.GetAngle(endpoint, transform.position) - 90);
    if(!Geometry.AreParallel(l1, l2)) {
        Vector2 pointToLerpTo = Geometry.Intersection(l1, l2) - (Vector2)transform.position;
        GetComponent<Rigidbody2D>().velocity = pointToLerpTo;
    }
    else GetComponent<Rigidbody2D>().velocity = new Vector2(0, 0);

    //increases the magnitude of the velocity based on how far the rope moved the object's position
    float ratio = (distance - length) / length;
    GetComponent<Rigidbody2D>().velocity *= 1 + ratio;

    distance = length;
}

Motion from Unity (bad)

Player starts by moving downward at left edge of rope circle. Gains a little bit of speed from gravity, then will always stop 45 degrees on the other side where it started (regardless of starting speed), then slowly fall back down to the bottom of the circle.


If anyone needs me to explain the Geometry class (lines, intersections) then I can, but I think it's mostly self-explanatory. Otherwise, I think I explained this the best I could. Thanks in advance for any help.

(also, StackOverflow isn't letting me add the Unity2d tag so I guess I gotta settle for Unity3d)

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I found out that Rigidbody2D.velocity.magnitude is not how far the object moves every physics update. This is what was causing the issue, because the Processing code was based off the velocity being added directly to the position every update.

To fix this, what I did was do the same geometry, but scale the velocity to the % of how much of the velocity was actually 'used' (it usually travels 2% of the actual velocity vector).

Here is the final code in Unity: (this time I'm showing the fill FixedUpdate(), with the irrelevant parts removed)

float lastMagnitude;
Vector2 lastPosition;
void FixedUpdate() {
    float velocityMoved = Vector2.Distance(lastPosition, transform.position) / lastMagnitude;
    Debug.Log(velocityMoved * 100 + "%"); //this is usually 2%

    bool shortenedRope = false;
    if(Input.GetButton("Shorten Rope")) {
        shortenedRope = true;
        length -= ropeShortenLength;
    }

    distance = Vector2.Distance(transform.position, endpoint);
    if(connected && distance > length) {
        //lerp position -> endpoint// keep gameObject within length of the rope
        float posLerpAmount = (distance - length) / distance;
        transform.position = Vector2.Lerp(transform.position, endpoint, posLerpAmount);

        //'lerp' velocity -> endpoint// keep the velocity locked to the tangent of the circle around the endpoint
        Vector2 adjustedVelocity = rigidbody.velocity * velocityMoved;
        Vector2 relativeVelocity = adjustedVelocity + (Vector2)transform.position;
        Line l1 = Geometry.LineFromTwoPoints(relativeVelocity, endpoint);
        Line l2 = Geometry.LineFromAngle(transform.position, Geometry.GetAngle(endpoint, transform.position) - 90);
        if(!Geometry.AreParallel(l1, l2)) {
            Vector2 pointToLerpTo = Geometry.Intersection(l1, l2) - (Vector2)transform.position;
            rigidbody.velocity = pointToLerpTo;
            rigidbody.velocity /= velocityMoved;
        }
        else rigidbody.velocity = new Vector2(0, 0);

        //'give back' the energy it lost from moving it's position
        if(shortenedRope) {
            float ratio = (distance - length) / length;
            rigidbody.velocity *= 1 + ratio;
        }

        distance = length;
    }
    lastPosition = transform.position;
    lastMagnitude = rigidbody.velocity.magnitude;
}

EDIT: Recently learned that it is better to use Time.deltaFixedTime instead of the variable I made velocityMoved, since Time.deltaFixedTime is already calculated.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...