4

I am working on an endless runner game for Android by Unity. I dont want to use a kinematic rigidbody. So physics is involved but the rigidbody is supposed to run along a predefined path by default. (and jumps or changes lanes by user actions). Moving straight is easy. I have done that but I want to have the next stage in the game where there are turns. It seems to work but it sometimes gets jittery and the turning isnt as smooth as I want it to be. And if I increase the speed, the player gets wonky. Could you please help me to optimize the code to get a smoother turns no matter what the speed is.

As far as I searched I couldnt find an answer on internet probably people are using kinematic rigidbodies more often in order not to deal with physics. So I use .AddForce and .AddTorque. I now use prefabs with predefined turns (road pieces). So it is spawned as the player moves along. Each road prefab has a spline (a free asset based on Unity 2015 procedural spline generation video I suppose) for the moving path. So the player is picking up a node along the spline and sets it as target and uses its rotation to turn towards using the AddTorque.

Maybe it is easier if I switch to kinematic rigidbody. Maybe that is ideal but I insist on doing this for the sake of learning physics and some people might find it useful for another project as there isnt enough resources on this.

void FixedUpdate()
  {


    if (!jump)
    {
        //maxangle = Mathf.Clamp(r.velocity.magnitude * 2f,3,15f);
        maxangle = r.velocity.magnitude;

        r.constraints = RigidbodyConstraints.None;
        r.constraints = RigidbodyConstraints.FreezeRotationZ | RigidbodyConstraints.FreezeRotationX;
        TurnToTarget(transform, sample.Rotation,target, maxangle);
        r.constraints = RigidbodyConstraints.None;
        r.constraints = RigidbodyConstraints.FreezeRotationZ | RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationY;
    }
    //Debug.Log(currentroad.transform.name + maxangle);

    if (!GameManager.gameManager.dead  && running)
    {
        r.isKinematic = false;
        //Debug.Log(transform.position.y);
        var speed = r.velocity.magnitude;
        Vector3 directionOfTarget = (target - transform.position).normalized;

        if (speed < runspeed)
        {
            //r.velocity += Vector3.forward * 1f;
            Debug.Log(r.velocity.z+ " " + r.velocity.magnitude);
            Debug.Log(directionOfTarget);
            r.AddForce(directionOfTarget* (runspeed-speed), ForceMode.VelocityChange);
        }
        if (transform.position.y > 2.7f)
        {
            r.mass = 50000f;
            Physics.gravity = new Vector3(0, -100f, 0);
        }
        if (grounded)
        {
            r.mass = 10f;
            Physics.gravity = new Vector3(0, -10f, 0);
        }

private void TurnToTarget(Transform transform, Quaternion targetrot, Vector3 movePoint, float maxTurnAccel)
 {
      Vector3 directionOfTarget = (movePoint -transform.position).normalized;
      Vector3 directionInEulers = targetrot.eulerAngles;

      Vector3 offsetInEulers = ClampHeading(directionInEulers) - ClampHeading(transform.eulerAngles);
    offsetInEulers = ClampHeading(offsetInEulers);
    //optional

    Vector3 angularVelocity = r.angularVelocity / Time.fixedDeltaTime;
    if (offsetInEulers.sqrMagnitude < Mathf.Pow(maxTurnAccel, 2))
    {
        if (offsetInEulers.y < 0)
        {
            if (angularVelocity.y < offsetInEulers.y)
            {
                offsetInEulers.y = -offsetInEulers.y;
            }
        }
        else
        {
            if (angularVelocity.y > offsetInEulers.y)
            {
                offsetInEulers.y = -offsetInEulers.y;
            }
        }
        if (offsetInEulers.x > 0)
        {
            if (angularVelocity.x < -offsetInEulers.x)
            {
                offsetInEulers.x = -offsetInEulers.x * 2;
            }
        }
        else
        {
            if (angularVelocity.x > -offsetInEulers.x)
            {
                offsetInEulers.x = -offsetInEulers.x * 2;
            }
        }
        if (offsetInEulers.z > 0)
        {
            if (angularVelocity.z < -offsetInEulers.z)
                offsetInEulers.z = -offsetInEulers.z * 2;
        }
        else
        {
            if (angularVelocity.z > -offsetInEulers.z)
                offsetInEulers.z = -offsetInEulers.z * 2;
        }
    }
    offsetInEulers = ClampVector(offsetInEulers, -maxTurnAccel, maxTurnAccel);
    //Debug.Log(currentroad + " " + offsetInEulers + " " + r.angularVelocity + " " + directionOfTarget + " " + ClampHeading(directionInEulers)+" " +transform.eulerAngles);

    r.AddRelativeTorque(transform.up * offsetInEulers.y);
    //r.AddTorque(offsetInEulers*r.velocity.magnitude);

}
Aykut Karaca
  • 350
  • 3
  • 15
  • Any ideas on this please? İs this not a common problem? – Aykut Karaca Aug 19 '19 at 15:31
  • just asking, why u dont use any tweening plugin for turning like DoTween?, this will fix all of that, beside it support regular transforms and rigidbodies , where also u can simple DoTween.().Stop ..etc If that sound interest for you, let me know so I write down an example for you. – VectorX Aug 23 '19 at 06:57
  • As I said I want to use physics for this project. – Aykut Karaca Aug 27 '19 at 09:06

2 Answers2

3

You could look into splines. You can reduce the amount of computation to move a character along a path by calculating how many points along that path are needed for movement to appear smooth as the character is moved from point to point.

Sometimes blurring effects are used to reduce the number of polygons that need to be drawn, when a character is moving fast.

  • Splines here have limited number of nodes already but you can get and point along its bezier curve at any rate you want. I don't think that is the issue. Adding right amount of torque and adjusting the angular velocity depending on the rigidbody velocity is the key here. I don't have enough math mind to solve this. – Aykut Karaca Aug 27 '19 at 09:03
2

First

First thing to note is in this code:

    if (transform.position.y > 2.7f)
    {
        r.mass = 50000f;
        Physics.gravity = new Vector3(0, -100f, 0);
    }
    if (grounded)
    {
        r.mass = 10f;
        Physics.gravity = new Vector3(0, -10f, 0);
    }

If looks like you're manipulating mass to achieve the affect of "stopping". If the player is in the air, you slam them into the ground with a high gravity value to slow them down rapidly. Manipulating mass of an object in motion can cause a great deal of issues, especially if you're applying a force in the same physics frame. I see you use ForceMode.VelocityChange to counter this problem, so kudos to you on that. However, when you AddRelativeTorque to an object, its impact depends heavily on the mass.

Changing the mass of an object directly does not automatically scale the current linear and angular momentum that an object has. Instead, when you increase the mass to 50,000f you are increasing the momentum by:

50,000 / (prior mass).

Changing mass on-the-fly is usually bound to cause problems. I would recommend finding a different solution to force your player to the ground that does not involve manipulating mass (and to a lesser concern, gravity). Perhaps lerping the rigidbody.position downard, or applying a downward impulse force may achieve the same effect without the risk for bugs.

Second

All of this logic is occurring in the FixedUpdate() cycle. This is separate from the Update() cycle that runs every frame. I see that you are accessing some frame-specific data, notably transform.position and transform.eulerAngles. It is possible that 2 physics cycles occur before the next frame cycle occurs, resulting in something like the following:

Update() - transform is updated
FixedUpdate() - reads most recent transform data
Update() - transform is updated
FixedUpdate() - reads most recent transform data
FixedUpdate() - reads most recent transform data
Update() - transform is updated

Since some of your game logic is based on the transform data, the FixedUpdate() can sometimes double-act before it can be updated, resulting in jittery movements.

I would recommend avoiding any transform reads in FixedUpdate(), and instead using RigidBody.position. Changing the rotation is a bit more nuanced, but RigidBody.MoveRotation may serve useful here as well.

Erik Overflow
  • 2,220
  • 1
  • 5
  • 16
  • 1
    Hi Erik, thank you for your answer. For your first point, yes I change the mass for fast landing after jumping so this is done only during jumping. I get jittery effect during running without jump so mass change should not be a reason for that but a very good point you made. Probably to prevent other issues such as side shifting during jumping, I should probably change it to a `Forcemode.Impulse` from the objects top downwards. Your second point might be actually the reason for the problems. I should try `Rigidbody.position` instead and see. – Aykut Karaca Aug 20 '19 at 20:26
  • However I want to avoid using `.MovePosition` and `.MoveRotation` on nonkinematic rigidbody and only use forces as I believe this is recommended by Unity. – Aykut Karaca Aug 20 '19 at 20:26
  • I tried `Rigidbody.position` but didnt make a difference. Something else is still wrong. My logic has to be changed completely with one depending on velocity etc. I probably get vectors wrong while applying forces too. – Aykut Karaca Aug 20 '19 at 21:01
  • 2
    You are also reading transform.eulerAngles (a frame specific value) in your TurnToTarget. Angles make my brain hurt, but that one might be a cause for jitters, too. Can you provide a video or more detail on exactly what the jitters look/act like? (Are they fast super fast jitters? Do they only occur when you start turning, or happen throughout? Do they happen when not turning at all?, etc) – Erik Overflow Aug 20 '19 at 22:04
  • This is a common function used to apply torque for turning a specific angle to the target. I found it online. I will post the link if I can find. If you can come up with a better solution I would appreciate because that is the key where jitter happens. It applies torque a bit more than enough and as it goes beyond the target direction, it applies opposite torque at the next fixedupdate frame. And so on. Even though my problem isn't solved, I awarded bounty to you because you had more insight then other replies. – Aykut Karaca Aug 28 '19 at 08:00