简体   繁体   中英

Unity rigid object receives twice the force it is supposed to receive

I am making a game where bullets can start orbiting a player. The orbits are circular, and not elliptical. Bullets have a drag factor of 0.

At first, I simply calculated the next position in the orbit, and divided the delta position by fixedDeltaTime to obtain its new velocity.

Unfortunately, it meant that fast bullets instead follow polygonal paths, and often miss a target.

I wanted to improve the precision by giving them an inward force, and a velocity nearly tangential to the orbit, so that they follow parabola segments around the player instead of straight lines.

The parabola segments are defined by their start and end point, and their vertices, which must lay on the orbit arc, with a velocity equal to the velocity I was previous using ((endpos - startpos) / fixedDeltaTime).

To calculate the parabola, I calculate the midway points on the arc and segment, and their difference is proportional to the force applied.

so we use the following names

fdt = fixedDeltaTime
t = fdt / 2
f = the force applied during the incoming physics frame
v0 = the velocity at the start of the frame
v1 = the velocity at the middle of the frame (at the vertex of the parabola)

dp1 = the relative position at the middle of the frame (midpos - startpos) and vertex of the parabola
dp2 = the relative position at the end of the frame (endpos - startpos)

The force is defined by these two equations:

// at vertex, only the "lateral" velocity remains
v1 = dp2 / fdt

// the difference between dp2 / 2 and dp1 is what the force can apply over half a frame

dp2 / 2 - dp1  = f * 0.5tt
therefore
(dp2 / 2 - dp1) / (0.5 * t * t) = f
(dp2 / 2 - dp1) / (0.5 * fdt/2 * fdt/2) = f
(dp2 - dp1 * 2) * 4 / (fdt * fdt) = f

//v0 is then easily calculated
v0 = v1 - t * force
v0 = (dp2 / fdt) - force * (fdt / 2)

We then get this working code:

Vector3 startPos = _rigidbody.position;

if (firstFrame == false && Vector3.Distance(predictedNextFramePos, startPos) > 0.01f)
    Debug.Log("WTF");

Vector3 nextPosition;
GetLocalOrbitPos(nextTime, out nextPosition);
nextPosition += _parent.GetPosition();

float fdt = Time.fixedDeltaTime;

float halfTime = (time + nextTime) / 2f;
Vector3 halfPosition;
GetLocalOrbitPos(halfTime, out halfPosition);
halfPosition += _parent.GetPosition();

Vector3 dp2 = nextPosition - startPos;
Vector3 dp1 = halfPosition - startPos;

Vector3 force = (dp2 - 2 * dp1) * 4 / (fdt * fdt);
Vector3 v0 = (dp2 / fdt) - (force * fdt / 2f);

Vector3 deltaPosPredicted = PhysicsTools.GetMovement(v0, force, fdt);
if (Vector3.Distance(deltaPosPredicted, dp2) > 0.001f)
    Debug.Log("position prediction error: " + Vector3.Distance(deltaPosPredicted, dp2));

predictedNextFramePos = deltaPosPredicted + startPos;

Vector3 deltaHPosPredicted = PhysicsTools.GetMovement(v0, force, fdt / 2f);
if (Vector3.Distance(deltaHPosPredicted, dp1) > 0.001f)
    Debug.Log("position prediction error: " + Vector3.Distance(deltaHPosPredicted, dp1));

//drawing the startpos, midpos, endpos triangle
Debug.DrawLine(startPos, startPos + dp2, Color.magenta, Time.fixedDeltaTime * 2);
Debug.DrawLine(startPos, startPos + dp1, Color.red, Time.fixedDeltaTime * 2);
Debug.DrawLine(startPos + dp2, startPos + dp1, Color.red, Time.fixedDeltaTime * 2);
//drawing v0 and force
Debug.DrawLine(startPos, startPos + force, Color.gray, Time.fixedDeltaTime * 2);
Debug.DrawLine(startPos, startPos + v0, Color.blue, Time.fixedDeltaTime * 2);
//drawing the parabola arc
{
    Vector3 pos = startPos;
    Vector3 vel = v0;
    for (int i = 0; i < 10; i++)
    {
        Vector3 offset = PhysicsTools.GetMovementUpdateVelocity(ref vel, force, Time.fixedDeltaTime / 10f);
        Debug.DrawLine(pos, pos + offset, Color.green, Time.fixedDeltaTime * 2);
        pos += offset;
    }
}

// Old version
// Vector3 deltaPosition = nextPosition - _rigidbody.position;
// Vector3 velocity = deltaPosition / t;
// SetPhysicsState(_rigidbody.position, velocity, time);

//Applying results
SetPhysicsState(startPos, v0, time);
_rigidbody.AddForce(force / 2f, ForceMode.Acceleration);

I am using my physics helper class

public static class PhysicsTools
{

    public static Vector3 GetMovement(Vector3 velocity, Vector3 force, float time)
    {
        return (velocity * time) + 0.5f * force * (time * time);
    }

    public static Vector3 GetMovementUpdateVelocity(ref Vector3 velocity, Vector3 force, float time)
    {
        Vector3 ret = (velocity * time) + 0.5f * force * (time * time);
        velocity += force * time;

        return ret;
    }
}

Everything works fine, but if, and only if, I divide the force by two when applying it. My own simulation using PhysicsTools does not require such tampering.

Here's a picture of one of my tests, with the force factor applied to both the physics engine and the PhysicsTools simulation. You can see that the simulated lines go off into the distance, but not the actual projectile, which stays in its weird pentagram, as it should.

速度偏移

Here we can see it working as intended (still with the applied force reduced)

工作正常

My question, why would I need to divide that damned force by two?

Well here folks is what happen when you make assumptions.

I assumed that ForceMode.Continuous meant that the force would be applied continuously through the frame. That is not the case.

The unity physics engine is incapable of any kind of continuous acceleration or parabola casting. Any object moves in straight lines, and AddForce simply modifies the velocity right then and there.

It turns that simply dividing the force by two was enough to reset the starting velocity to my previous linear solution to the problem, and that the only reason that objects seemed to react outside of the polygon was that my bullet collider was much wider than I thought it was.

Please read this post for more information: https://answers.unity.com/questions/696068/difference-between-forcemodeforceaccelerationimpul.html

The only solution to the problem is to increase the physics framerate, or to use your own raycasting solution, which comes with a slew of other problems.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM