SagiZiv
SagiZiv

Reputation: 1040

Calculate projectile landing distance and time

I am pretty new in Unity and I am building small games to learn.

I am currently building a shooting-game and I have a small problem (miscalculation). Every time a player presses space key, I am creating new bullet (with RigidBody) and changes its velocity. I am trying to calculate where the bullet would land, but something is wrong in my calculation.

I am using the physics formula: dx = x0 + V0*t + 0.5*a*t^2 to calculate when the bullet would land and where.

This is what I have wrote so far:

float g = Physics.gravity.y;
print(transform.position.y); // it starts on 0.5
//Yt = Y0 + 0.5 * g * t^2
float time = ((0.15f - transform.position.y) * 2) / g; // The bullet would land on y equals to 0.15 because its height
print("TIME: " + Mathf.Sqrt(time));
print("dX = " + 100 * Mathf.Sqrt(time));

and to apply the velocity:

if (Input.GetKeyDown(KeyCode.Space))
{
    rb.velocity = new Vector3(0, 0, 100);
}

In that case the time is 2.67125 and dX is 26.7125, but in unity inspector I see a bullet traveled 27.91713.

Does anything seems wrong to you?

Below is the bullet in the scene

enter image description here

Upvotes: 1

Views: 2339

Answers (2)

J. Bonnici
J. Bonnici

Reputation: 21

Not a direct answer to the Op's question, but here it goes for anyone that comes across this thread but is working with Rigidbody2D.

  1. use Physics2D.simulationMode = SimulationMode2D.Script; instead of Physics.autoSimulation = false; when you want to switch off the Physics auto simulation
  2. use Physics2D.simulationMode = SimulationMode2D.FixedUpdate; instead of Physics.autoSimulation = true; when you want to turn auto simulation on again.
  3. When simulating use Physics2D.Simulate(Time.fixedDeltaTime);

Official documentation for reference: https://docs.unity3d.com/ScriptReference/Physics2D.Simulate.html

Upvotes: 2

Programmer
Programmer

Reputation: 125245

Don't do this manually. The only time you should manually do the calculation is when you have access to Unity's source code but an average Joe don't. Even if you get it working with your calculations the code can break anytime.

Unity 2017.1 introduced the Physics.Simulate function and Physics.autoSimulation property. Physics.autoSimulation is used to disable physics then Physics.Simulate is then called to manually simulate physics and return the position the Rigidbody object would be in the future.

Your landing point is at 0.15. First, disable physics with Physics.autoSimulation = false;, Add force to your Rigidbody with velocity or the AddForce function. Put Physics.Simulate(Time.fixedDeltaTime); in a loop and make it run continuously until you reach your landing spot or until pos.y < 0.15 becomes true. After the while loop exits, you should obtain the new position and store it in a temporary variable. You can now re-enable physics with Physics.autoSimulation = true; and reset the transform.

It would also be helpful to implement a timeout so that when the projectile do not reach the landing-spot within the time provided then break out of the loop. This prevents possible infinite loop in your game.

Here is a struct which holds the landing position, rotation and time result:

public struct PredictResult
{
    public Vector3 position;
    public Quaternion rotation;
    public float landingTime;
}

Here is the function that performs the landing check. It returns true when successful and false if it didn't reach the landing point within the time provided in timeOutTime then you probably have to increase the timeOutTime variable.

bool PredictRigidBodyLandPos(Rigidbody sourceRigidbody, Vector3 velocity, out PredictResult result)
{
    //const float landingYPoint = 0.15f;
    const float landingYPoint = -1.651335f;

    //Disable Physics AutoSimulation
    Physics.autoSimulation = false;

    //Shoot the Bullet 
    sourceRigidbody.velocity = velocity;

    //Get current Position and rotation
    Vector3 defaultPos = sourceRigidbody.position;
    Quaternion defaultRot = sourceRigidbody.rotation;

    Debug.Log("Predicting Future Pos from::: x " + defaultPos.x + " y:"
        + defaultPos.y + " z:" + defaultPos.z);

    //Exit after x seconds(In physics time) if Object does not land
    float timeOutTime = 15f;
    //The landing time that will be returned
    float landingTime = 0;

    //Determines if we landed successfully or not
    bool landedSuccess = false;

    //Simulate where it will be in x seconds
    while (timeOutTime >= Time.fixedDeltaTime)
    {
        timeOutTime -= Time.fixedDeltaTime;
        landingTime += Time.fixedDeltaTime;

        Physics.Simulate(Time.fixedDeltaTime);

        Vector3 pos = sourceRigidbody.position;
        Debug.Log("Pos: " + pos.x + " " + pos.y + " " + pos.z);

        //Check if we have landed then break out of the loop
        if (pos.y < landingYPoint || Mathf.Approximately(pos.y, landingYPoint))
        {
            landedSuccess = true;
            Debug.LogWarning("Landed");
            break;
        }
    }

    //Get future position and rotation and save them to output
    Vector3 futurePos = sourceRigidbody.position;
    Quaternion futureRot = sourceRigidbody.rotation;

    result = new PredictResult();
    result.position = futurePos;
    result.rotation = futureRot;
    result.landingTime = landingTime;

    //Re-enable Physics AutoSimulation and Reset position and rotation
    Physics.autoSimulation = true;
    sourceRigidbody.velocity = Vector3.zero;
    //sourceRigidbody.useGravity = false;

    sourceRigidbody.transform.position = defaultPos;
    sourceRigidbody.transform.rotation = defaultRot;

    return landedSuccess;
}

Usage:

Transform cameraTransform;
public float shootSpeed = 300;
public Rigidbody rbdy;

void Start()
{
    cameraTransform = Camera.main.transform;
}

void Update()
{
    if (Input.GetKeyDown(KeyCode.Space))
    {
        Vector3 velocity = cameraTransform.forward * shootSpeed;

        PredictResult result;
        if (PredictRigidBodyLandPos(rbdy, velocity, out result))
        {

            Debug.Log("DONE Predicting Landing Pos: x " + result.position.x + " y:"
            + result.position.y + " z:" + result.position.z);

            Debug.Log("Landing Time: " + result.landingTime);
        }
        else
        {
            Debug.Log("Failed to predict landing pos before timeout");
        }
    }
}

Press the Space key to shoot a Rigidbody then it returns the landing distance.

Note that you said it should land when pos.y <= 0.15. If you don't know where the y landing point is at, make a simple edit to the code and instead, use OnCollisionEnter to determine when the Object collides with the ground then toggle a boolean variable which is used to exit out of the while loop instead of pos.y < 0.15.

Upvotes: 3

Related Questions