Calculate mortar parabolic trajectory knowing the shot angle but not velocity
The problem is that when I set the y position of the mortar to a different one to 0 the tajectories dont work. But it does work if the bullet I shoot has to impact on different altitudes.
I have two scripts Mortar Script:
public class Projectile1: MonoBehaviour
{
// launch variables
[Range(20.0f, 75.0f)] public float LaunchAngle;
public float waitTime;
public GameObject Bullet;
public GameObject Canon;
public GameObject Mark;
// state
private bool bTouchingGround;
private bool Reloading = false;
// cache
public RaycastHit hit;
//-----------------------------------------------------------------------------------------------
// Use this for initialization
void Start()
{
}
// resets the projectile to its initial position
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonUp(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit, Mathf.Infinity))
{
Debug.DrawLine(Camera.main.transform.position, hit.point, Color.red);
//Debug.Log(hit.transform.position.x);
if (hit.transform != null & Reloading == false)
{
Instantiate(Mark, new Vector3(hit.point.x, hit.point.y+0.01f, hit.point.z), Quaternion.Euler(new Vector3(90,0,0)));
Instantiate(Bullet, Canon.transform.position, Quaternion.Euler(new Vector3(90, 0, 90)));
StartCoroutine(Reload(waitTime));
}
}
}
}
IEnumerator Reload(float waitTime)
{
Reloading = true;
yield return new WaitForSeconds(waitTime);
Reloading = false;
}
}
Bullet Script:
public class Bullet: MonoBehaviour
{
Rigidbody rigid;
public GameObject Mortar;
private Quaternion initialRotation;
// Start is called before the first frame update
void Start()
{
Mortar = GameObject.FindGameObjectWithTag("Arma");
//Debug.Log(Mortar.GetComponent<Projectile1>().hit.transform.position.x);
rigid = GetComponent<Rigidbody>();
initialRotation = transform.rotation;
Launch(Mortar.GetComponent<Projectile1>().hit.point, Mortar.GetComponent<Projectile1>().LaunchAngle);
}
private void Update()
{
transform.rotation = Quaternion.LookRotation(rigid.velocity) * initialRotation;
}
void Launch(Vector3 hit, float LaunchAngle)
{
//GameObject Disparo = Instantiate(Bullet, Canon.transform.position, Quaternion.identity);
// think of it as top-down view of vectors:
// we don't care about the y-component(height) of the initial and target position.
Vector3 projectileXZPos = new Vector3(transform.position.x, 0.0f, transform.position.z);
Vector3 targetXZPos = new Vector3(hit.x, 0.0f, hit.z);
// rotate the object to face the target
transform.LookAt(targetXZPos);
// shorthands for the formula
float R = Vector3.Distance(projectileXZPos, targetXZPos);
float G = Physics.gravity.y;
float tanAlpha = Mathf.Tan(LaunchAngle * Mathf.Deg2Rad);
float H = hit.y - transform.position.y;
// calculate the local space components of the velocity
// required to land the projectile on the target object
float Vz = Mathf.Sqrt(G * R * R / (2.0f * (H - R * tanAlpha)));
float Vy = tanAlpha * Vz;
// create the velocity vector in local space and get it in global space
Vector3 localVelocity = new Vector3(0f, Vy, Vz);
Vector3 globalVelocity = transform.TransformDirection(localVelocity);
// launch the object by setting its initial velocity and flipping its state
rigid.velocity = globalVelocity;
}
private void OnTriggerEnter(Collider other)
{
if(other.tag == "Mark")
{
Destroy(other.gameObject);
Destroy(this.gameObject);
}
}
}
Answer by lgarczyn · Jan 23, 2020 at 09:04 PM
A lot of code can be made a bit better
Instantiate(Mark, new Vector3(hit.point.x, hit.point.y+0.01f, hit.point.z), Quaternion.Euler(new Vector3(90,0,0)));
can be
Instantiate(Mark, hit.point + Vector3.up * 0.01f, Quaternion.Euler(-90, 0, 0));
You should also avoid using a rigidbody's transform, so
transform.rotation = Quaternion.LookRotation(rigid.velocity) * initialRotation;
should be
rigid.rotation = Quaternion.LookRotation(rigid.velocity) * initialRotation;
To avoid using FindObject, you can call Launch from the Mortar script:
GameObject bulletGO = Instantiate(Bullet, Canon.transform.position, Quaternion.Euler(new Vector3(90, 0, 90)));
bulletGO.GetComponent<Bullet>().Launch(hit.point + Vector3.up * 0.01f, LaunchAngle);
It's good to keep data transfer in one direction, usually from weapon to projectile
Now your math can be made a bit simpler. We know the shot direction, which can be calculated like so
Vector3 xzdir = (hit - rigid.position);
xzdir.y = 0f;
Vector3 shotDir = Quaternion.LookRotation(xzDir) * Quaternion.AngleAxis(LaunchAngle, Vector3.right) * Vector3.forward;
But the velocity is a bit more complicated, I decided to figure out my own equation for fun. We know
rigidbody.positon + shotDir * vel * time + Physics.gravity * time * time / 2 == hit;
or
shotDir * vel * time - Physics.gravity * time * time / 2 == deltaPos
We can extract two equations:
shotDir.y * vel * time + Physics.gravity.y * time * time / 2 == deltaPos.y
shotDir.x * vel * time == deltaPos.x
Using the second, we can find the confusing
vel * time == deltaPos.x / shotDir.x
And if we use it the first equation, we get
shotDir.y * deltaPos.x / shotDir.x + Physics.gravity.y * time * time / 2 == deltaPos.y
After further refining, we get a negative a positive solution for time (we discard the negative)
float time = Mathf.Sqrt((shotDir.y * deltaPos.x / shotDir.x - deltaPos.y) / -Physics.gravity.y * 2);
And by reversing the second equation we get:
float vel = deltaPos.x / shotDir.x / time;
And thus the code
void Launch(Vector3 hit, float LaunchAngle)
{
Vector3 deltaPos = hit - rigid.position;
Vector3 xzDelta = deltaPos;
xzDelta.y = 0f;
Vector3 shotDir = Quaternion.LookRotation(xzDelta) * Quaternion.AngleAxis(-LaunchAngle, Vector3.right) * Vector3.forward;
float time = Mathf.Sqrt((shotDir.y * deltaPos.x / shotDir.x - deltaPos.y) / -Physics.gravity.y * 2);
float vel = deltaPos.x / shotDir.x / time;
if (float.IsNaN(vel))
{
Debug.Log("Impossible Trajectory")
}
rigid.velocity = vel * shotDir;
}
First of all thanks for the help. Im gonna try to make the changes you recommend and lets see if it gets solved. Thanks again
Dude Thanks so much it works!!!
Now Ive gotta understand why yours works and $$anonymous$$e doesnt. But thanks so much I was breaking my head trying to figure it out.
Honestly, I'm not sure exactly how $$anonymous$$e works. The steps to get the formula make perfect sense, but the result is a bit absurd.
Not sure why this should work
Your time calculation ignores the z axis The shot direction looks confusing ... wouldn't it be Auaternion.LookRotaiton(deltaPos) * Vecto3.Forward; ?
If this works at all it would only work when the target is along the X axis such as in 2D ... unless I have missed something here