1
\$\begingroup\$

I am trying to shoot an arrow at a target with a 100% success rate. Given the initial force applied to the arrow, the gravity, the location of the archer, and the location of the target, how can I do this in Unity?

I have read How can I find a projectile's launch angle?, and it contains the equation I am using:

Formula

However, this is only accurate when I am at the maximum distance from the object given its power. (Above the maximum distance means that the arrow cannot reach the target.) The closer I move to the target, the less accurate the arrow shot becomes, as it travels past the target. The angle is increased resulting in the arrow traveling a shorter distance as I'd expect, but it is not increased by enough.

The script spawns an arrow (a simple sphere) and launches it towards a target. It fires once per second, but the closer to the target the archer is, the more inaccurate the arrow is. The case of the archer being too far from the arrow is not handled, but that is not my concern right now.

using UnityEngine;
using System.Collections;

public class Archer : MonoBehaviour
{
    public GameObject arrow; //Simple 3D Sphere
    public GameObject target; //Simple 3D Cube
    public float power = 25.0f;
    public Vector3 aim;

    private void Start()
    {
        StartCoroutine(ShootArrows());
    }

    private IEnumerator ShootArrows()
    {
        while (true)
        {
            yield return new WaitForSeconds(1.0f);
            Vector3 spawnPosition = new Vector3(transform.position.x, transform.position.y, transform.position.z);
            GameObject arrowInstance = Instantiate(arrow, spawnPosition, Quaternion.identity) as GameObject;
            arrowInstance.GetComponent<Rigidbody>().AddForce(Aim() * power, ForceMode.VelocityChange);
        }
    }

    private Vector3 Aim()
    {
        float xAim = target.transform.position.x - transform.position.x;
        float yAim = Mathf.Rad2Deg * Mathf.Atan(((Mathf.Pow(power, 2) + Mathf.Sqrt((Mathf.Pow(power, 4) - (-Physics.gravity.y) * (-Physics.gravity.y * Mathf.Pow(HorizontalDistance(), 2) + 2 * VerticalDistance() * Mathf.Pow(power, 2))))) / -Physics.gravity.y * HorizontalDistance()));
        float zAim = target.transform.position.z - transform.position.z;
        Vector3 aim = new Vector3(xAim, yAim, zAim).normalized;
        return aim;
    }

    private float HorizontalDistance()
    {
        float xDistance = target.transform.position.x - transform.position.x;
        float zDistance = target.transform.position.z - transform.position.z;
        float distance = Mathf.Sqrt(Mathf.Pow(xDistance, 2) + Mathf.Pow(zDistance, 2));
        return distance;
    }

    private float VerticalDistance()
    {
        return Mathf.Abs(target.transform.position.y - transform.position.y);
    }
}

My best guess is that I am using the equation correctly, but I am plugging the angle into Unity wrong. Why is my script giving me inaccurate results?

\$\endgroup\$
  • \$\begingroup\$ I spent a lot of time trying to solve some physics problems analytically in unity, but with lots of fast moving objects. It turned out that the inaccuracies in the physics engine broke my logic - I ended up instead solving the physics problem analytically and interpolating samples along the analytical path generated in the process to do the physics myself - this removed the problems i had and made the process reliable. \$\endgroup\$ – jheriko Nov 5 '15 at 23:56
  • 1
    \$\begingroup\$ In this case I think it's just that yAim is being added directly to a vector as an angle (unless I'm misreading). It needs to be converted from an angle to an appropriate coordinate value. \$\endgroup\$ – Chris Mills-Price Nov 6 '15 at 0:15
  • \$\begingroup\$ I've been playing with this for a bit and I think it's peculiar that yAim is almost always the same value. If you look at a graph of the arctan function, it's aggressively asymptotic. Unless your objects are very close together, you're going to get essentially +- Pi/2, or +-90 degrees. Any "accuracy" from this implementation is an artifact of how the XYZ normalization weights each direction, not of the function actually producing appropriate results. I'll think on it some more. \$\endgroup\$ – Chris Mills-Price Nov 6 '15 at 9:36
  • \$\begingroup\$ I'm not familiar with unity (and the code looks a bit confusing), however, this should be related: stackoverflow.com/a/23204945/3182664 ... I'm not sure whether I understood the question correctly, but the computeRequiredAngles method may be what you are looking for... \$\endgroup\$ – Marco13 Nov 7 '15 at 0:10