1

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

Programmer
  • 121,791
  • 22
  • 236
  • 328
SagiZiv
  • 932
  • 1
  • 16
  • 38
  • The bullet starts at (0, 0.5, 0) and lands at (0, 0.15, 27.91713), I calculate the time because the formula needs time and speed of the bullet. – SagiZiv Jul 14 '18 at 14:04
  • (0, 0.5, 0) - x, y, z? And bullet has velocity only in z-direction (0, 0, 100)? (I'm trying to understand the problem clearly) – MBo Jul 14 '18 at 14:22
  • time 0.26712 and z-shift 26.712 seems true – MBo Jul 14 '18 at 14:30
  • OP is calculating dt for a y-axis trip from a known dy, then uses that known dt for calculating a dz trip. – Rodrigo Rodrigues Jul 14 '18 at 14:32
  • How are you measuring the real distance? How did you get 27.91713? – Rodrigo Rodrigues Jul 14 '18 at 14:33
  • @RodrigoRodrigues I can see in the inspector where the bullet started and where it landed. this is how I know the real distance it has traveled – SagiZiv Jul 14 '18 at 14:40
  • @MBo I want to move the bullet forward, therefore I give it a velocity in the Z-axis (aka forward axis). – SagiZiv Jul 14 '18 at 14:40
  • @user9977758 I know you are looking on inspector, but how do you know it landed? Do you have a terrain with a mesh collider or any other type of collider? Do you have a trigger that prints the distance travelled when the collider of the bullet touches the land collider? If yes, are you considering the thickness of the bullet rigidbody? Are you considering a "land" when the bullet's center cross the 0.15 y line or when the bullet's surface touch it? – Rodrigo Rodrigues Jul 14 '18 at 14:46
  • @RodrigoRodrigues The bullet lands on a plane (which has a meshCollider) and When the bullet hits the plain I pause the debugger. private void OnCollisionEnter(Collision collision) { Debug.Break(); } – SagiZiv Jul 14 '18 at 15:03
  • 1
    How large is the bullet entity? Is it ~1 unit in dimension? – meowgoesthedog Jul 14 '18 at 15:10
  • @meowgoesthedog the bullet is a capsule and it's scale is (0.3, 0.3, 0.3) – SagiZiv Jul 14 '18 at 15:11
  • Ok, so you are calculation your time and distance from the bullet center, but measuring it when it's surface touches the plane? That could be a source of innacuracy. Instead, redo your math with a dy from the bullet bottom to the land and check the values. E.g., if your bullet is a sphere with 0.03 radius, and it's center is at (0,0,0) local position, use `(0.15f - transform.position.y + 0.03f)` – Rodrigo Rodrigues Jul 14 '18 at 15:14
  • @RodrigoRodrigues OP is already taking the bullet's size into account by subtracting 0.15 from the fall height. – meowgoesthedog Jul 14 '18 at 15:16
  • @RodrigoRodrigues the result from your calculation was 20.19275, which is still incorrect... My bullet is a capsule with scale of (0.3, 0.3, 0.3) rotated 90 degrees in the X-axis – SagiZiv Jul 14 '18 at 15:19
  • Could you please add a picture of your bullet in a Scene, showing it hierarchy and Inspector? – Rodrigo Rodrigues Jul 14 '18 at 15:21
  • @RodrigoRodrigues an image was added in the last line – SagiZiv Jul 14 '18 at 15:28
  • That doesn't look like (0.3, 0.3, 0.3). If the tip of the capsule touched the ground first, you will certainly get a larger distance reading. – meowgoesthedog Jul 14 '18 at 16:01
  • Add a [time manager](https://docs.unity3d.com/Manual/class-TimeManager.html) and decrease the timestep and maxtimestep to see if that changes anything. I'm thinking maybe the bullet moves an extra unit forward on the last step. The default timestep of 0.02 multiplied by 100 units per sec gives ~2 unit steps. – Dillon Davis Jul 15 '18 at 01:31
  • @user9977758 I have a solution but have you solved this issue? – Programmer Jul 16 '18 at 00:22
  • Hi @Programmer, I still didn't solve the issue yet, what is your solution please? – SagiZiv Jul 17 '18 at 15:01

2 Answers2

3

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.

Programmer
  • 121,791
  • 22
  • 236
  • 328
  • 1
    Omg, I m so glad to know they added `Physics.Simulate`. Good to know – Rodrigo Rodrigues Jul 18 '18 at 01:28
  • hi @Programmer, I tried your solution and it works, but it is not constant. I calculate the distance from my position to the landing position and getting about 7.012, but the other digits after the period are different, do you know why¿ Also I print the landing position in the OnCollisionEnter method and get a bit different values than the ones your function calculated. Thanks alot for your help – SagiZiv Jul 18 '18 at 05:05
  • @user9977758 I made a simple scene before posting this answer and it works 100% fine. What exactly is not constant? What is the landing point and what value do you get when you land? Are you sure you're using the-same velocity for both the simulation and the non simulation one? – Programmer Jul 18 '18 at 06:55
  • Also, I found something I used that I forgot to comment out. See the `landingYPoint` variable in the `PredictRigidBodyLandPos` function. The value is supposed to be `0.15f;` in your case not `-1.651335f;` which is what I used on my side so you may want to change that. – Programmer Jul 18 '18 at 07:09
  • Hi @Programmer, I have coppied all your code, and changed the velocity and landingYPosition to mine, but when I calculate the distance from my position to the predicted position it is higher or lower in ~0.001. This is OK, I think it is because they player moved/rotated. the mistake is much minor than mine. Thanks you – SagiZiv Jul 18 '18 at 14:51
  • lower than the one calculated before – SagiZiv Jul 18 '18 at 14:51
  • `0.001` off range is totally fine and shouldn't affect anything. Notice that I passed `Time.fixedDeltaTime` to `Physics.Simulate` and `Time.fixedDeltaTime` is usually ~`0.0167`, .If you really really need that much precision which I don't think you need, you have to re-write the whole code and pass a really small value to `Physics.Simulate` such as 0.0003 and see what happens. You'll have to re-write most of the code for that changes but again, you don't need that – Programmer Jul 18 '18 at 15:04
2

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

J. Bonnici
  • 21
  • 3