计算弹丸着陆距离和时间

时间:2018-07-14 13:06:08

标签: c# unity3d math physics

我在Unity方面还很陌生,我正在开发一些小游戏来学习。

我目前正在制作射击游戏,但有一个小问题(计算错误)。 每当玩家按下空格键时,我都会创建新的项目符号(使用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,但是在统一检查员中,我看到一颗子弹经过了27.91713。

您觉得有什么不对吗?

下面是场景中的子弹

enter image description here

2 个答案:

答案 0 :(得分:2)

请勿手动执行此操作。唯一应该手动进行计算的时间是可以访问Unity的源代码,而普通的Joe则没有。即使您将其用于计算,代码也可能随时中断。

Unity 2017.1 引入了Physics.Simulate函数和Physics.autoSimulation属性。 Physics.autoSimulation用于禁用物理,然后调用Physics.Simulate来手动模拟物理并返回Rigidbody对象将来的位置。

您的着陆点位于 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;
}

这是执行着陆检查的功能。成功时它将返回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");
        }
    }
}

按【{1】】键拍摄一个刚体,然后它返回着陆距离。

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

答案 1 :(得分:1)

不是对 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