射击要具备四个基本条件:射击方向direction,射击速度speed,射击角度angle,目标位置dstPosition。
【A】我们从最简单的情况开始:射击方向、角度、速度固定,目标位置随意。取angle=45°,speed=40,direction=Vector3.right
根据斜抛运动的物理知识可知,炮弹的运动可分为水平方向的运动和垂直方向的运动。实现运动实际上是要计算出每个时刻炮弹的位置。
【实现效果】
【实现代码】
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shoot : MonoBehaviour
{
[SerializeField]
private float speed;
[SerializeField]
private float angle;
private Vector3 dirction;
private float Vspeed;
private float gravity;
private float time;
private bool isStartShoot = false;
// Use this for initialization
void Start () {
dirction=Vector3.right;
gravity = Physics.gravity.magnitude;
}
// Update is called once per frame
void Update ()
{
if (isStartShoot)
{
time += Time.deltaTime;
Vspeed = speed * Mathf.Sin(angle * Mathf.Deg2Rad) - gravity * time;//计算垂直方向的速度
transform.Translate(dirction*speed*Mathf.Cos(angle*Mathf.Deg2Rad)*Time.deltaTime //水平方向位置
+ Vector3.up*Vspeed*Time.deltaTime);//垂直方向位置
}
if (Input.GetMouseButton(0))//鼠标左键点击开始射击
isStartShoot = true;
}
}
【B】接着改变射击方向,用鼠标选择方向,鼠标点击位置减去炮弹发射时的位置即为射击方向。
【实现效果】
【实现代码】
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shoot : MonoBehaviour
{
[SerializeField]
private float speed;
[SerializeField]
private float angle;
private Vector3 dirction;
private Vector3 dstPosition;
private float Vspeed;
private float gravity;
private float time;
private bool isStartShoot = false;
public Transform cursor;
// Use this for initialization
void Start () {
dirction=Vector3.right;
gravity = Physics.gravity.magnitude;
}
// Update is called once per frame
void Update ()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
cursor.position = hit.point;
}
if (isStartShoot)
{
time += Time.deltaTime;
Vspeed = speed * Mathf.Sin(angle * Mathf.Deg2Rad) - gravity * time;//计算垂直方向的速度
transform.Translate(dirction*speed*Mathf.Cos(angle*Mathf.Deg2Rad)*Time.deltaTime //沿射击方向的位置
+ Vector3.up*Vspeed*Time.deltaTime);//垂直方向位置
}
if (Input.GetMouseButton(0)) //鼠标左键点击开始射击
{
isStartShoot = true;
dstPosition = cursor.position;//鼠标选中的目标位置
dirction = (dstPosition - transform.position).normalized;//计算射击方向,方向要归一化
}
}
}
【C】可以看到炮弹沿着选择的方向发射,但是炮弹并没有落在选择的目标位置处。现在想炮弹射中鼠标选中的位置,该怎么办呢?
【分析】鼠标点击时,射击的四个基本条件中,目标位置dstPosition和射击方向direction就确定了,射击角度angle和射击速度speed不确定。在当前的angle和speed值下,炮弹无法射中目标位置,所以要改变其值。angle和speed取值为什么时,可以刚好射中目标位置呢?这正是需要我们求解的。
再看这个运动方程,距离x为炮弹发射点和目标位置之间的距离,可求,为已知值。y为目标位置和炮弹发射点的高度之差,可求,为已知值。所以,这个方程只有三个未知数,速度speed,角度angle,从炮弹发射到击中目标位置的时间t。(方程中所用的符号和代码中用的不一致,请自行转换)
两个方程只能解得两个未知数,所以我们要给其中一个未知数一个定值才行。
【C1】若射击角度不变=45°,改变速度,可求得
【实现效果】
【C2】若射击速度不变=40,改变角度,可求得
此处要先判断根号里边的式子是否大于零,否则该等式无实数解。在物理上对应于,炮弹在给定速度下所能达到的最远距离是否大于炮弹发出点与目标点的水平距离。
【实现效果】(存在误差,但调试下运行比正常运行更准确?没找出来原因)
【C3】若飞行时间已知t=4s,可求得
【实现效果】
【实现代码】
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
public class Shoot : MonoBehaviour
{
[SerializeField]
private float speed;
[SerializeField]
private float angle;
private Vector3 dirction;
private Vector3 dstPosition;
private float Vspeed;
private float gravity;
private float time;
private bool isStartShoot = false;
private float yOffset;
private float distance;
public Text tofText;
public float tofTime;
public Transform cursor;
// Use this for initialization
void Start () {
dirction=Vector3.right;
gravity = Physics.gravity.magnitude;
}
// Update is called once per frame
void Update ()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
cursor.position = hit.point;
}
if (isStartShoot)
{
time += Time.deltaTime;
tofText.text = "飞行时间:" + time + "s";
Vspeed = speed * Mathf.Sin(angle * Mathf.Deg2Rad) - gravity * time;//计算垂直方向的速度
transform.Translate(dirction.normalized*speed*Mathf.Cos(angle*Mathf.Deg2Rad)*Time.deltaTime //沿射击方向的位置
+ Vector3.up*Vspeed*Time.deltaTime);//垂直方向位置
}
if (Input.GetMouseButton(0)) //鼠标左键点击开始射击
{
isStartShoot = true;
dstPosition = cursor.position;//鼠标选中的目标位置
dirction = dstPosition - transform.position;//计算射击方向,方向要归一化
yOffset = dirction.y;//炮弹发射点和目标位置的高度差
dirction = dirction - (Vector3.Dot(Vector3.up, dirction) * Vector3.up);//水平射击方向
distance = dirction.magnitude;//炮弹发射点和目标位置的水平距离
speed = ShootWithAngle(yOffset, distance, angle, gravity);//计算得到射击速度
//angle = ShootWithSpeed(yOffset, distance, speed, gravity);//计算得到射击角度
//if (angle < 0)
// isStartShoot = false;
//ShootWithTime(yOffset,distance,tofTime,gravity,out angle,out speed);
}
}
public float ShootWithAngle(float yOffset,float distance,float angle,float gravity)
{
return
distance*Mathf.Sqrt(gravity/(2*Mathf.Cos(angle)*(distance*Mathf.Sin(angle) - yOffset*Mathf.Cos(angle))));
}
public float ShootWithSpeed(float yOffset, float distance, float speed, float gravity)
{
float root = Mathf.Pow(speed, 4) - gravity*(gravity*distance*distance + 2*speed*speed*yOffset);
if(root < 0)
{ Debug.Log("目标超出射程"); return -1;}
else
{
float angle0, angle1;
angle0 = Mathf.Atan((speed * speed + Mathf.Sqrt(root)) / (gravity * distance)) * Mathf.Rad2Deg;
angle1 = Mathf.Atan((speed * speed - Mathf.Sqrt(root)) / (gravity * distance)) * Mathf.Rad2Deg;
return angle0;
}
}
public void ShootWithTime(float yOffset, float distance, float tofTime, float gravity,out float angle,out float speed)
{
yOffset = yOffset + transform.localScale.y;//碰撞检测的影响:小球的Position表示质心的坐标不是小球下边缘的坐标
speed = Mathf.Sqrt(distance*distance + Mathf.Pow((yOffset + gravity*tofTime*tofTime/2), 2))/tofTime;
angle = Mathf.Atan((yOffset + gravity*tofTime*tofTime/2)/distance)*Mathf.Rad2Deg;
}
void OnCollisionEnter(Collision collision)
{
Destroy(gameObject);//碰撞销毁
}
}
【D】给小球添加刚体