How can I create a "round arc" physics based jump?

Hi,

I'm wanting to create a 2D platform game with physics based character control. I have tried the Character Controller component which does work really well for the most part but my character is spherical in shape and I would like to take advantage of physics when rolling down curved slopes etc. I have started coding and I'm running into a problem with jumping. I want my character to jump in a perfect arc unless the player changes direction in the air but the basic jump method from the reference doesn't do that. I'm sure others have seen it but basically the ball slows abruptly as it jumps up then speeds up as it falls back down.

Here is my code,

public class PhysicsControl : MonoBehaviour
{
    #region Public Variables

    public float speed = 10.0f;
    public float jumpSpeed = 10;

    #endregion

    #region Private Variables

    bool isGrounded = false;
    Rigidbody myRigidbody;

    #endregion

    // Use this for initialization
    void Start()
    {
        myRigidbody = rigidbody;
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        //calculate movement
        Vector3 moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, 0);
        myRigidbody.AddForce(moveDirection * speed);

        if (isGrounded)
        {

            Jump();
        }

        isGrounded = false;
    }

    void OnCollisionStay()
    {
        isGrounded = true;
    }

    void Jump()
    {
        if (Input.GetButton("Jump"))
        {
            myRigidbody.velocity = Vector3.up * jumpSpeed;
        }
    }
}

Any help would be great.

Thanks

Dan

p.s. I am also open to suggestions on how to use the character controller along with rigidbody if there is a way to seamlessly switch between the two as needed. I did try that option though with no success.

I'm not sure about switching between the character controller and rigidbody. It's really either one or the other.

Using a CharacterController

Character controllers are meant to handle collisions without involving the physics-y stuff. You could calculate all movement yourself if you wanted, foregoing the physics stuff, in which case, you could get something similar by actually calculating the speeds and movements. Take a look at the 3DPlatformTutorial's controller script which applies gravity and forward motion while jumping.

Using a rigidbody

You probably want something like:

public class PhysicsControl : MonoBehaviour
{
    #region Public Variables
    public float acceleration = 10.0f;
    public float jumpSpeed = 10.0f;
    #endregion

    #region Private Variables
    private bool isGrounded = false;
    //Unless you're changing your rigidBody at run-time, don't store it again
    #endregion

    // FixedUpdate is called at set intervals
    void FixedUpdate()
    {
        //This will add a forward force every call, accelerating the rigidBody
        rigidbody.AddForce(new Vector3(Input.GetAxis("Horizontal"), 0, 0) * acceleration);

        if (isGrounded && Input.GetButton("Jump")) Jump();
    }

    void OnCollisionStay() //You might want to check if you are colliding with the ground
    {
        isGrounded = true;
    }

    void Jump()
    {
        isGrounded = false;
        rigidbody.AddForce = Vector3.up * jumpSpeed;
    }
}

Your problem was that you were setting velocity, not adding to it. If you had even had `myRigidbody.velocity += Vector3.up * jumpSpeed;` in stead of what you had, it wouldn't have stopped before jumping.

Defining Perfection

I think maybe you just described it poorly or else the code is still wrong. A perfect arc would have the same radius from it's center point at either end and the same curve to reach either end. If you were following actual physics, without additional force being applied in the forward direction (do you have a fan or jetpack pushing you forward?), wind resistance (drag) would naturally slow you down, which is why moving jumps have a sharper fall than their initial rise and they do not follow a perfect arc.

Because your code accelerates based on horizontal input,

  • if you have drag on your rigidbody, the player will have to keep holding the horizontal input in order to move and will continue to accelerate until the acceleration and drag have evened out to a constant speed, meaning that if they release the horizotal input or the acceleration hasn't evened out, the arc will not be perfect as the horizontal speed will change over the arc.
  • If you don't have drag on your rigidbody, if the player touches the horizontal input, the horizontal speed will change over the arc.

In order to ensure a perfect arc, you would need to maintain a constant horizontal velocity, meaning that you would need to either adjust the speeds, or disable drag during the jump if it is on and prevent the user's input from affecting movement while jumping.

If a perfect arc in your definition is a perfect semi-circle, the your motion will have the same speed, but will move in a direction moving around in a circle. Essentially you'd have something like take Vector3.Up and rotate it by a number of degrees relative to the size of your arc in the direction that you are facing every update until it was equal to or past -Vector3.up and at each update change your velocity to be that Vector3 multiplied by some constant speed value.

I have a physics equation here that was made for firing a projectile in an arc towards a target. I'm not sure that the arc will be a perfect 180 degree arc, but you can give it a shot.

Here is the equation:

if(highArc)
    angle = Mathf.Atan( (Mathf.Pow(jumpVelocity, 2) + Mathf.Sqrt( Mathf.Pow(jumpVelocity, 4) - (gravity * ((gravity * Mathf.Pow(distance, 2)) + (2 * y * Mathf.Pow(jumpVelocity, 2)))) )) / (gravity * distance) ) * Mathf.Rad2Deg;
else
    angle = Mathf.Atan( (Mathf.Pow(jumpVelocity, 2) - Mathf.Sqrt( Mathf.Pow(jumpVelocity, 4) - (gravity * ((gravity * Mathf.Pow(distance, 2)) + (2 * y * Mathf.Pow(jumpVelocity, 2)))) )) / (gravity * distance) ) * Mathf.Rad2Deg;

You'll need the following information:

  1. What is the distance I will land?
  2. How much force should I use to get there?

1 will have to be calculated, and 2 can be a tweakable value inside of the editor.

So, let's say you want your jump to go 5 meters. You'd have this:

float gravity = Vector3.Magnitude(Physics.gravity);
float distance = Vector3.Distance( transform.position, transform.position + (Vector3.forward * 5) ); //Of course, depending on how complicated your game is, this may vary...

After you have those, you rotate your player like this:

transform.eulerAngles = new Vector3(angle * -1, transform.eulerAngles.y, transform.eulerAngles.z); //which axis you apply the angle to will vary depending on how your game works.

And then launch it in the air!

p.rigidbody.AddRelativeForce(Vector3.forward * jumpVelocity, ForceMode.VelocityChange); //ForceMode.VelocityChange makes the velocity instantaneous instead of accelerating to that velocity. Vector3.forward will vary depending on your game.