Physics Related
The force acting on the projectile at the moment it is launched (and, consequently, the speed at which it moves in the parabola) is a vector given by two components (in terms of a two-dimensional game, of course): a component on the x-axis (the force or velocity in the horizontal) and a component on the y-axis (the force or speed vertically):
Assuming that there is no wind resistance and that the projectile has no propulsion (i.e., it is not a missile that accelerates or decelerates on its own), the horizontal velocity (resulting from an initially applied force) is constant. I mean, it doesn’t change until the projectile stops because it’s hit something. Thus, the horizontal displacement is governed by the Uniform Movement (MU), and given by:
(equation 1: i.e., final space = initial space + speed x time)
Assuming the same for projectile in terms of vertical motion (no friction, propulsion, etc.), vertical velocity does not remain constant because on it acts at all times the acceleration of gravity (). Thus, vertical displacement is governed by the Uniformly Varied Movement (MUV), and given by:
(equation 2: i.e., final space = initial space + velocity x time + acceleration x time squared)
In fact, as the movement includes an acceleration, the speed changes constantly. And therefore, it is given by:
(equation 3: final speed = initial speed + acceleration x time)
Also, when a force is applied to a projectile, the speed at which it will move depends on the mass of the projectile. The heavier the projectile is, the greater the initial force applied to it in order for it to move at the same speed. This relation is given by the formula:
(equation 4: i.e., force = mass x acceleration)
Therefore, the physics simulation can be performed using these formulas to calculate, at each instant of time, the position (both on the X-axis and on the Y-axis) at which the projectile must be.
What do you want
What you want to do, then, is to calculate the force vector necessary for the first impulse in the projectile, given the initial positions (from where the projectile comes out, ie, ) and final (the target of the projectile, ie, ). That might be esteemed with the following pseudo-code:
velocidadeX = forca / massa
tempo = (alvo.x - x) / velocidadeX
velocidadeY = gravidade * (tempo / 2);
The first line uses equation 4 to calculate a speed on axis X from a force whichever (that you can kick or ask the player to report). As the horizontal movement is constant, no matter the chosen value! The balcony is precisely to calculate from it the speed on the Y axis correct for the target to be hit.
It should be easy to see that the lower the horizontal speed (that is, the slower the object moves "forward"), the longer it will take to reach the target. Thus, the projectile also needs to rise higher so that it does not fall in the middle of the way. Thus, the second line estimates the time it will take to traverse the horizontal axis, using equation 1.
Finally, the vertical velocity can be estimated with equation 3 in a simple way. In the parable movement, the projectile will first rise and then descend, all at the same time as the projectile takes to move horizontally. On the ascent, it decelerates, so that when it reaches the highest point its speed is 0. Therefore, in the second half of the movement, its initial speed is 0 (i.e., ) and it starts to accelerate in the fall. Therefore, in the formula, its initial speed is 0 and the time traveled is half of the previously calculated total time. The acceleration is that of gravity (the only one acting on the projectile), and it is without the negative sign because in the algebraic manipulation of equation 3 it "passed to the other side of equality".
Well, these are just the X and Y components of the initial velocity vector that the projectile needs to have. You can calculate the vector angle in relation to the X axis, if desired, using the code:
angulo = Arco-Tangente(velocidadeY, velocidadeX)
Note as I said earlier (and emphasized in bold) that this code esteem the initial velocity vector for the projectile. Why does it estimate? Well, consider a scenario where the speed X is low (close to 0). In this case, the projectile will take muuiiitoo long to travel across the horizontal space, and so it needs to be thrown almost upwards (almost totally vertically), so that it will fall on the target also almost totally vertically. In this scenario, the calculation is right.
The problem is in a scenario where the speed X is high. In this case, the projectile will travel the horizontal space very quickly, so it needs to be launched almost parallel to the ground to hit the target. This simple calculation then fails to disregard the angle of inclination at which the projectile reaches the target (this time it will no longer be almost entirely vertical).
One can think of a reasonably effective solution that "adjusts" time and speed Y by reconsidering the target from the difference in horizontal space (X) given by the previously calculated impact angle. For example, you can simply run the following lines again:
tempo = (alvo.x - Seno((90 - angulo)) - x) / velocidadeX
velocidadeY = gravidade * (tempo / 2);
In line 2, an excerpt is observed - Seno((90 - angulo))
. This part simply subtracts from the position of the target the displacement given by the angle (the opposite cathode, that is, the sine) of the input difference (90 would be if it was totally vertical, then subtracts the entrance angle to have the difference angle).
Observation 1: With this solution, this process would need to be
ideally repeated more iteratively, until the error was fully minimized (converged). But in the code I
produced I only did once because it was enough for illustration
(almost always hits the target when caught in it). But you still go
observe, by the dashed, that there is some small variation from where the
Real target is on X axis.
Observation 2: I’m not a physicist, so I admit I don’t know if there’s a
better way to do this calculation without having to adjust the angle
iteratively. If anyone has a better solution, you are welcome to
share (can even change in the same code smoothly!).
:)
Example in Unity3d
The source code I share below was built based on some sources (mentioned at the end of this post). The motion of the projectile is in charge of the physics of Unity3d. I only added one Rigidybody2D
as you would normally. Dashed calculus uses the same formulas mentioned, but in its variation with trigonometric functions (see references also for details). There are only colliders in the projectiles (cannonballs) and on the target. The explosion when "hitting" the ground uses a simple height check (for mere convenience, and to prevent more easily that bullets thrown too high can eventually "miss" a Colisor and produce leaks and memory).
The code can be executed online in Webgl (have patience; it takes a little to load) or downloaded in a zip archive from here.
Code
Class of cannon control:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.EventSystems;
// Classe para controle do canhao
public class CannonControl : MonoBehaviour
{
// Prefab do ponto de trajetoria (a ser definido via editor)
[SerializeField]
private GameObject m_dotPrefab;
// Prefab do projetil (a ser definido via editor)
[SerializeField]
private GameObject m_bulletPrefab;
// Objeto do alvo (a ser definido via editor)
[SerializeField]
private GameObject m_targetObject;
// Indicativo se a trajetoria deve ser sempre exibida (se true) ou somente
// quando o botao do mouse estiver pressionado (se false)
private bool m_alwaysShowTrajectory = false;
// Propriedade para permitir alteraçao via checkbox na interface grafica
public bool alwaysShowTrajectory
{
get
{
return m_alwaysShowTrajectory;
}
set
{
m_alwaysShowTrajectory = value;
if(!m_alwaysShowTrajectory)
hideTrajectoryPoints();
}
}
// Indicativo se a mira deve ser fixada no alvo
private bool m_fixedInTarget = false;
// Propriedade para permitir alteraçao via checkbox na interface grafica
public bool fixedInTarget
{
get
{
return m_fixedInTarget;
}
set
{
m_fixedInTarget = value;
}
}
// Indicaçao de que o mouse esta pressionado
private bool m_mousePressed = false;
// Transform com a posiçao da boca do canhao
private Transform m_cannonMouth;
// Lista com os pontos de trajetoria utilizados
private List<GameObject> m_trajectoryPoints;
// Angulo atual do canhao
private float m_angle;
// Transform com o limite vertical para os tracos da trajetoria
// (obtained by its name)
private Transform m_verticalLimit;
// Vetor de força com a qual o tiro sera executado.
private Vector2 m_force;
// Inicilizaçao do script
void Start()
{
GameObject obj = GameObject.Find("limit");
if(!obj)
throw new UnityException("A instancia do objeto empty chamado 'limit' nao foi encontrada!");
m_verticalLimit = obj.transform;
// Procura pelo objeto filho que marca a boca do canhao
m_cannonMouth = transform.Find("mouth");
if(!m_cannonMouth)
throw new UnityException("A instancia do objeto empty chamado 'mouth' nao foi encontrada!");
// Cria inicialmente 30 pontos de trajetoria (a lista e ajustada dinamicamente
// conforme a necessidade)
m_trajectoryPoints = new List<GameObject>();
for(int i = 0; i < 30; i++)
{
GameObject dot = (GameObject) Instantiate(m_dotPrefab);
dot.GetComponent<Renderer>().enabled = false;
m_trajectoryPoints.Add(dot);
}
}
// Atualizaçao quadro-a-quadro
void Update()
{
// A força de lançamento do projetil e igual ao vetor de distancia do
// canhao ao ponteiro do mouse.
Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector2 dirMouse = mousePos - transform.position;
m_force = dirMouse;
// Desenha uma linha amarela no scene viewer do editor para indicar
// visualmente o vetor de força
Debug.DrawLine(transform.position, mousePos, Color.yellow);
// Se a mira nao esta fixada no alvo, o jogador que controla o tiro
// (definindo a direçao e a força do tiro com o mouse)
if(!m_fixedInTarget)
{
// Posiciona o canhao apontando para o mouse
m_angle = (Mathf.Atan2(m_force.y, m_force.x) * Mathf.Rad2Deg) - 15;
}
// Caso contrario, o jogador so define a força no eixo X com base no mouse
// (o resto e calculado de forma a garantir que acerte o alvo)
else
{
// Equipara a "altura" do alvo a do canhao
Vector2 target = m_targetObject.transform.position;
//target.y = transform.position.y;
// Calcula os componentes de velocidade x e y
float mass = m_bulletPrefab.GetComponent<Rigidbody2D>().mass;
float gravity = Physics.gravity.magnitude;
float velX = m_force.magnitude / mass;
float time = (target.x - transform.position.x) / velX;
float velY = gravity * (time / 2);
// Calcula o angulo de lançamento
m_angle = (Mathf.Atan2(velY, velX) * Mathf.Rad2Deg) - 15;
// Ajusta conforme o angulo em que o tiro "deve" atingir o alvo
time = (target.x - Mathf.Sin((90 - m_angle) * Mathf.Deg2Rad) - transform.position.x) / velX;
velY = gravity * (time / 2);
// Calcula a velocidade e forma finais
Vector2 velocity = new Vector2(velX, velY);
m_force = velocity * mass;
}
// Rotaciona o canhao para o angulo calculado
transform.rotation = Quaternion.Euler(0f, 0f, m_angle);
if(m_alwaysShowTrajectory || m_mousePressed)
showTrajectoryPoints();
// Captura o pressionamento/liberaçao do botao do mouse
// (somente se nao estiver sobre o checkbox da UI)
if(!EventSystem.current.IsPointerOverGameObject())
{
if(Input.GetMouseButtonDown(0))
m_mousePressed = true;
if(Input.GetMouseButtonUp(0))
{
m_mousePressed = false;
hideTrajectoryPoints();
shootBullet();
}
}
}
// Simula o tiro do canhao
void shootBullet()
{
// Cria o projetil e o posiciona na boca do canhao
GameObject bullet = (GameObject) Instantiate(m_bulletPrefab);
bullet.transform.position = m_cannonMouth.position;
// Aplica a força a bala
Rigidbody2D body = bullet.GetComponent<Rigidbody2D>();
body.AddForce(m_force, ForceMode2D.Impulse);
// Toca o som do tiro
AudioSource sound = GetComponent<AudioSource>();
sound.Play();
}
// Exibe os pontos de trajetoria para a força atual.
// O angulo do tiro esta embutido no vetor de força.
void showTrajectoryPoints()
{
float mass = m_bulletPrefab.GetComponent<Rigidbody2D>().mass;
Vector2 velocity = m_force / mass;
float angle = Mathf.Rad2Deg * (Mathf.Atan2(velocity.y , velocity.x));
// Esse valor e uma estimativa de quanto de tempo passa a cada "quadro"
// Na pratica, ele serve pra ajustar a distancia entre os pontos no traçado
// Quanto mais proximo de zero (so nao pode ser zero mesmo!), mais parecido
// com uma linha o traçado se torna
float step = 0.03f;
float time = 0;
Vector2 currentPos = m_cannonMouth.position;
for(int i = 0; i < m_trajectoryPoints.Count; i++)
{
GameObject dot = m_trajectoryPoints[i];
dot.transform.position = currentPos;
if(currentPos.y >= m_verticalLimit.position.y)
dot.GetComponent<Renderer>().enabled = true;
else
dot.GetComponent<Renderer>().enabled = false;
float dx = velocity.magnitude * time * Mathf.Cos(angle * Mathf.Deg2Rad);
float dy = velocity.magnitude * time * Mathf.Sin(angle * Mathf.Deg2Rad) - (Physics2D.gravity.magnitude * time * time / 2.0f);
currentPos = new Vector3(m_cannonMouth.position.x + dx , m_cannonMouth.position.y + dy ,2);
time += step;
// Ajuste dinamico dos pontos necessarios para traçar a trajetoria.
// Basicamente: se faltam pontos para alcançar o limite vertical,
// adiciona mais 10 deles na lista.
if(i == m_trajectoryPoints.Count - 1 && currentPos.y > m_verticalLimit.position.y)
{
for(int j = 0; j < 10; j++)
{
dot = (GameObject) Instantiate(m_dotPrefab);
dot.GetComponent<Renderer>().enabled = true;
m_trajectoryPoints.Add(dot);
}
}
}
}
// Esconde os pontos de trajetoria
void hideTrajectoryPoints()
{
foreach(GameObject dot in m_trajectoryPoints)
dot.GetComponent<Renderer>().enabled = false;
}
}
Class of control of projectiles:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
// Classe para controle do projetil (bala de canhao)
// A fisica do projetil fica a cargo da Unity, pelo uso
// do componente Rigidbody 2D.
public class BulletControl : MonoBehaviour
{
// Transform com o limite vertical para os projeteis
// (obtained by its name)
private Transform m_verticalLimit;
// Inicializaçao do script
void Start()
{
GameObject obj = GameObject.Find("limit");
if(!obj)
throw new UnityException("A instancia do objeto empty chamado 'limit' nao foi encontrada!");
m_verticalLimit = obj.transform;
}
// Atualizaçao quadro-a-quadro
void Update()
{
// Checa se o projetil atingiu o "chao" (dado pelo posiçao vertical do
// objeto vazio chamado 'limit'). Se atingiu, destroi o projetil.
if(transform.position.y <= m_verticalLimit.position.y)
{
explode();
}
}
// Captura a colisao com o alvo (via um trigger)
void OnTriggerEnter2D(Collider2D other)
{
Text score = GameObject.Find ("/Canvas/score").GetComponent<Text>();
score.text = (int.Parse(score.text) + 1).ToString();
explode();
}
// Captura a colisao com outra bala (via um collider)
void OnCollisionEnter2D(Collision2D other)
{
explode();
}
// Anima a explosao
void explode()
{
// Toca a animaçao da exploçao (particulas de fogo)
ParticleSystem explosion = GetComponent<ParticleSystem>();
explosion.Play();
// Toca o som da explosao
AudioSource sound = GetComponent<AudioSource>();
sound.Play();
// Esconde a bala
GetComponent<Renderer>().enabled = false;
// Destroy o objeto apos o fim da animaçao
Destroy(gameObject, explosion.duration);
// Destroy (e para) esse script imediatamente
// (para que o som e as particulas nao encavalem)
Destroy(this);
}
}
Websites of Reference
Credits
Basically this is ballistics (physics of parables). Take a look here: http://fisicamoderna.blog.uol.com.br/arch2007-09-02_2007-09-08.html
– Luiz Vieira
If you know gravity, you can determine the speed necessary initial, yes, but to speak of "force" it is also necessary to consider the mass of the projectile and the time when the force will be applied to it. I think it’s likely there’s something impl
– mgibsonbr
Already implemented in Unity itself in the case of speed, but in the case of force it should be necessary to establish additional parameters. I’m not sure yet. P.S. It was bad, I posted the comment unintentionally, and I can’t find option to edit (I’m on Android)
– mgibsonbr
Unity already has the physics implemented. But the trajectory display, for example, would need to be created by you. This example of trajectory is interesting because in practice it uses the same calculation of physics that I mentioned earlier. You can use it to try to calculate the force: http://www.theappguruz.com/blog/display-projectile-trajectory-path-in-unity
– Luiz Vieira
good ballistics applied in example 1
– Roger Oliveira
@bigown This answer is worth about 1000 points, but my strangeness derives from the fact, that in simple questions, if the user does not put a code code he is targeted for not complying with certain policies, however, this is the kind of question "ask me"... In a flagged or something like that... Really the parameters for questions and answers, are not clear for a novice like me...
– MagicHat