How to make a projectile turn with a arc

2019-05-11 09:20发布

问题:

I have a cannon that fires a bullet in a parabolic arc. Right now when I fire the bullet stays in the same rotation as it was when it fired out of the cannon.

How do I make it so the bullet's rotation follows the arc as it travels through the air?

I tried the following as a script running on the bullet

Exhibit 1

public class PointingBehaviour:MonoBehaviour
{
    private Rigidbody rb;

    private void Awake()
    {
        rb = GetComponent<Rigidbody>();
    }

    public void Update()
    {
        transform.up = rb.velocity;
    }
}

And that works fairly well. but I see a slight flicker on the first frame the object exists (I think this is because the velocity is still 0 at this point) and the object spins uncontrollably once it hits the ground.

I got it to stop flickering at the start and stop spinning when it lands by doing the following

public class BulletController : MonoBehaviour
{
    private Rigidbody _rb;
    private bool _followArc;
    private bool _firstFrame;
    private void Start()
    {
        _rb = GetComponent<Rigidbody>();
        _firstFrame = true;
        _followArc = true;
    }

    public void LateUpdate()
    {
        if (_followArc && !_firstFrame)
            transform.up = _rb.velocity;
        _firstFrame = false;
    }

    public void OnCollisionEnter(Collision collision)
    {
        _followArc = false;
    }
}

But if I happen to bump something in the air it stops following the arc and just does a free tumble till it lands. What is the "Correct" way to do what I want to do?


Because people wanted to see it, here is the code for spawning the bullet.

public class TankController : MonoBehaviour
{

    private Transform _barrelPivot;
    private Transform _bulletSpawn;
    public GameObject Bullet;
    public float FirePower;
    public float RotationSpeed;
    public float TiltSpeed;

    private void Start()
    {
        _barrelPivot = GetComponentsInChildren<Transform>().First(x => x.CompareTag("BarrelPivotPoint"));
        _bulletSpawn = GetComponentsInChildren<Transform>().First(x => x.CompareTag("BulletSpawn"));
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            FireCannon();
        }
        //(SNIP) Handle turning left and right and pivoting up and down.
    }

    private void FireCannon()
    {
        var newBullet = SpawnBullet();
        var rb = newBullet.GetComponent<Rigidbody>();
        rb.AddForce(_bulletSpawn.up * FirePower, ForceMode.Impulse);
    }

    private GameObject SpawnBullet()
    {
        var newBullet = (GameObject) Instantiate(Bullet, _bulletSpawn.position, _bulletSpawn.rotation);
        newBullet.transform.localScale = Bullet.transform.localScale;
        return newBullet;
    }
}

回答1:

I believe what you're saying is - your're script exhibit1 works great.

If you think about it, all you have to do is turn off that behavior, when you want to.

In this case, you're thinking "that behavior should stop when it hits the ground .. I assume that's what you mean physically.

It's very easy to turn off a MonoBehaviour, as you know just enabled = false;

So, you have some script somewhere like Bullet or perhaps BulletBoss

In there you'll have a variable private PointingBehaviour pb and you'll just pb.enabled = false

(I can't tell you "when" you want to do that, it depends on your game .. so it might be something like "when altitude is less than blah" or "when I hit something" ... probably OnCollisionEnter related.)

Note that - I'm sure you know this - for pointing behavior for a projectile, just setting it along the tangent is pretty basic.

A lovely very easy thing to do when you're writing a pointing behavior for a projectile like that, try lerping it gently to the tangent. The result is amazing real looking. Next perhaps add some random "wiggles" which is very bomb-like. It's amazing how the user can see such things, only only a few frames. (The next step up would be real air physics I guess!)

Note that, certainly, PointingBehaviour should just be its own script. You must keep behaviors totally isolated.


Regarding LateUpdate mentioned, there is never a need to use it.

Unity offer a "script order execution" system (see Preferences menu option), if one truly wants to order within the frame. But about the only reason to do that would be perhaps for some experimentation reason.

It's rather like "using a global" - there's just no reason for it. As with any code, if you have to do something in a given order, just call in order.