计算弹丸着陆距离和时间

Calculate projectile landing distance and time

我是 Unity 的新手,我正在制作小游戏来学习。

我目前正在制作一款射击游戏,但遇到了一个小问题(计算错误)。 每次玩家按下 space 键时,我都会创建新子弹(使用 RigidBody)并改变其速度。 我正在计算子弹会落在哪里,但我的计算出了点问题。

我正在使用物理公式:dx = x0 + V0*t + 0.5*a*t^2 来计算子弹何时落在何处。

这是我到目前为止写的:

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));

并应用速度:

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

在这种情况下,时间是 2.67125,dX 是 26.7125,但在 unity inspector 中我看到子弹经过了 27.91713。

你觉得有什么地方不对吗?

下面是场景中的子弹

不要手动执行此操作。您唯一应该手动进行计算的情况是当您可以访问 Unity 的源代码但普通人不能访问时。即使你让它与你的计算一起工作,代码也可能随时崩溃。

Unity 2017.1引入了Physics.Simulate函数和Physics.autoSimulation属性。 Physics.autoSimulation 用于禁用物理然后调用 Physics.Simulate 手动模拟物理和 return 刚体对象在未来的位置。

您的着陆点在 0.15。首先,使用 Physics.autoSimulation = false; 禁用物理,使用 velocityAddForce 函数为刚体添加力。将 Physics.Simulate(Time.fixedDeltaTime); 置于一个循环中,并使其连续 运行 直到到达您的着陆点或直到 pos.y < 0.15 变为 true。在 while 循环退出后,您应该获取新位置并将其存储在一个临时变量中。您现在可以使用 Physics.autoSimulation = true; 重新启用物理并重置变换。

实现超时也很有帮助,这样当弹丸没有在规定的时间内到达着陆点时就会跳出循环。这可以防止您的游戏中出现无限循环。

这里有一个struct,其中保存着陆位置、旋转和时间结果:

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

这是执行着陆检查的函数。它 returns true 成功时 false 如果它没有在 timeOutTime 提供的时间内到达着陆点那么你可能必须增加 timeOutTime变量。

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;
}

用法:

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");
        }
    }
}

Space键射击一个刚体然后它return是着陆距离。

请注意,您说它应该在 pos.y <= 0.15 时着陆。如果您不知道 y 着陆点在哪里,请对代码进行简单的编辑,然后使用 OnCollisionEnter 来确定对象何时与地面发生碰撞,然后切换一个布尔变量,即用于退出 while 循环而不是 pos.y < 0.15.

不是对 Op 问题的直接回答,但这里适用于遇到此线程但正在使用 Rigidbody2D 的任何人。

  1. 当您想关闭物理自动模拟时,请使用 Physics2D.simulationMode = SimulationMode2D.Script; 而不是 Physics.autoSimulation = false;
  2. 当您想再次打开自动模拟时,请使用 Physics2D.simulationMode = SimulationMode2D.FixedUpdate; 而不是 Physics.autoSimulation = true;
  3. 模拟使用时Physics2D.Simulate(Time.fixedDeltaTime);

参考官方文档: https://docs.unity3d.com/ScriptReference/Physics2D.Simulate.html