Unity3D中的弹道和移动目标提前量计算

弹道计算是游戏里常见的问题,其中关于击中移动目标的自动计算提前量的话题,看似简单,其实还是挺复杂的数学。网上这方面的资料还真不多,而且都是写的含含糊糊。抽空总结一下自己的方法。


讨论的前提是,假设目标是在3D空间里以匀速直线方式运动。


1.直线弹道
在不考虑重力和空气阻力影响的情况下,子弹的弹道呈直线运动。这种情况下,其实是个纯平面几何空间的问题,不需要微积分和线代知识。
分析的情况如下图:

图片:tj-1.jpg



        虽然在3D空间飞行,但火炮命中时,命中点和火炮位置、飞机初始位置处于一个三角形上,只需要平面几何知识就能解决问题。在这个三角形中,飞机起始位置P和火炮T的位置是确定的,飞机的飞行方向也是确定的,所以θ角是已知的,D的长度也是已知的,F和G的长度虽然不知道,但在命中点H相遇的时候经过的时间t都是一样的,所以F/G的比例实际等于两者速度的比例,而两者的速度都是已知的。这样就可以用高中的余弦公式来解决这个求边长的问题:

图片:tj02.jpg

                   
其中V_p和V_g分别代表飞机的速度和炮弹飞行的速度,这是一个标准的1元2次方程,化简、消元对某这么一个非数学专业的来说太麻烦了,直接用求根公式求解吧,有好的化简方法请指教。

图片:tj03.jpg


 
在Unity中实现的方法:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Vector3 hitPoint = Vector3.zero;//存放命中点坐标
//假设飞机物体是aircraft,炮塔物体是gun 两者间的方向向量就是两种世界坐标相减
Vector3 D = gun.transform.position - aircraft.transform.position;
//用飞机transform的TransformDirection方法把前进方向变换到世界坐标,就是飞机飞行的世界方向向量了
Vector3 aircraftDirection = aircraft.transform.TransformDirection(Vector3.foward);
//再用Vector3.Angle方法求出与飞机前进方向之间的夹角
float THETA = Vector3.Angle(D,aircraftDirection);
float DD = D.magnitude;//D是飞机炮塔间方向向量,D的magnitued就是两种间距离
float A =1-Mathf.Pow((gunVelocity/aircraftVelocity),2);//假设炮弹的速度是gunVeloctiy飞机的飞行线速度是aircraftVeloctiy
float B = -(2*DD*Mathf.Cos(THETA**Mathf.Deg2Rad));//要变换成弧度
float C = DD*DD;
float DELTA = B*B-4*A*C;
if (DELTA>=0){//如果DELTA小于0,无解
    float F1 = (-B+Mathf.Sqrt(B*B-4*A*C))/(2*A);
    float F2 = (-B-Mathf.Sqrt(B*B-4*A*C))/(2*A);
    if(F1<F2)//取较小的一个
    F1 = F2;
    //命中点位置等于 飞机初始位置加上计算出F边长度乘以飞机前进的方向向量,这个乘法等于把前进的距离变换成世界坐标的位移
    hitPoint = aircraft.transform.position + aircraftDirection * F1;       
}
假设你的炮弹是个Prefab叫projectilePrefab,带有一个刚体,那么可以这样生成炮弹实例:

复制代码
1
2
3
4
5
6
if(hitPoint != Vector3.zero){//如果有解
     //生成一个炮弹实例,位置在炮塔的位置,方向是从炮塔指向命中点
     GameObject obj =  (GameObject)Instantiate(projectilePrefab,gun.transform.position,Quaternion.LookRotation(hitPoint));
     //假设muzzleVelocity是设定的炮弹速度(0,0,muzzleVelocity)表示往正z方向运动,用TransformDirection把这个速度变换成世界坐标的速度向量
    obj.rigidbody.velocity = obj.transform.TransformDirection(new Vector3(0,0,muzzleVelocity));
}

经过以上计算,炮弹可以准确的命中飞行中的目标,只要目标是按照固定速度和方位角飞行的,可以百发百中。当然也会有无解的情况,所以计算的时候判断了Delta, 一共也就是几条语句。


2.抛物线弹道
考虑进重力影响,炮弹的弹道就是一个抛物线方程,而目标还是在3D空间的匀速直线运动,一个空间直线方程。

图片:tj04.jpg





一个曲线方程和一个直线方程,以隐含参数t(飞行时间)求共同解(相交)问题,列方程组:

图片:tj05.jpg


 
其中Vp和Vg分别代表飞机和炮弹飞行速度,角度Theta是炮弹射出时的仰角,t是飞行时间。这是个非齐次非线性隐含微分方程组,以某人的数学基础,看不出有什么特殊解的方法,用常规的迭代逼近求解吧,求达人提供更好方法。
迭代的过程大致是:
        1.用一组预测的xy落点坐标带入抛物线方程
       2.求出发射的角度和飞行时间t
       3.将时间带t入直线方程,求出相应的xy坐标
       4.将这个坐标与之前猜测的xy坐标进行比较,如果差值小于允许误差,迭代结束返回结果
       5.如果差值大于误差,将这个新的xy作为下一次计算的预测xy,返回步骤1
   这个过程的物理含义可以这样理解:瞄准飞机现在的位置发射,等炮弹飞到的时候飞机已经往前飞行了一段距离,把炮弹飞行时间乘以飞机速度,得到飞机在该时刻的实际位置,下次瞄准这个位置,再计算,因为目标变了,炮弹的飞行时间也变了,所以该时刻飞机位置也不同了,就这样不停循环,炮弹落点追赶飞机位置,直到两者差距无穷小。


针对抛物线方程把t带入,得到:

图片:tj06.jpg


 
然后用基础代数方法进行推导化简,再用通用求根公式得到:

图片:tj07.jpg



这样θ角就可以通过预测的落点坐标、炮弹初速度、重力加速度g来求出。
迭代是有很多技巧的,这些内容需要复习大学微积分课程。好的迭代方法能够快速收敛,最大化的解释运算开销。望高数达人提供更佳的迭代方法。
在Unity中实现,有几个核心思想:
   1.迭代体用函数递归来实现
   2.抛物线本身是2D曲线,所以其实不需要3重坐标就能计算,每次运算时把z指向预测的落点,第3个坐标可以无视
   3.各种变换可以快速的通过向量、矩阵运算得到,很方便,不需要总是借助transform

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//抛物线方程 X Y代表预测落点,V代表炮弹初速,G是重力加速度 返回值是Vector2,其中x是发射角,y是飞行时间
Vector2 formulaProjectile(float X,float Y,float V,float G){
if(G ==0){//如果无重力 问题就成了简单的三角函数 THETA等于atan(y/x) 飞行时间就等于(Y/sin(THETA))(斜边长)再除以速度
     float THETA = Mathf.Atan(Y/X);
     float T = (Y/Mathf.Sin(THETA))/V;
     return(new Vector2(THETA,T));
}else{//用上面的公式进行计算
     float DELTA = Mathf.Pow(V,4)-G*(G*X*X-2*Y*V*V);
     if(DELTA < 0){//DELTA小于0无解
         return Vector2.zero;
     }
     float THETA1 = Mathf.Atan((-(V*V)+Mathf.Sqrt(DELTA))/(G*X));
     float THETA2 = Mathf.Atan((-(V*V)-Mathf.Sqrt(DELTA))/(G*X));
     if(THETA1>THETA2)//取较小值
         THETA1 = THETA2;
     float T = X/(V*Mathf.Cos(THETA1));//用抛物线水平运动方程计算飞行时间 比较简单
     return new Vector2(THETA1,T);
     }
}
//目标运动的直线方程 VT是目标运动速度 PT是目标当前位置 DT是目标运动方向 TT是运动时间 返回值是目标经过时间TT以后的实际位置
Vector3 formulaTarget(float VT,Vector3 PT,Vector3 DT,float TT){
//简单的一句话搞定直线方程计算 目标实际位置=目标当前位置+目标运动方向向量*(目标飞行速度*目标飞行时间)
     return PT + DT * (VT * TT);
}
//主迭代函数 参数灰常多 用于算法演示 实际使用是可以简化的
//gunVelocity:炮弹初速度 gunPosition:炮塔世界坐标 aircraftVelocity:飞机线速度 aircraftPosition:飞机当前位置世界坐标
//aircraftDirection:飞机飞行方向向量 hitPoint:预测的命中点 G:重力加速度 accuracy:计算精度 小于这个值认为计算完成 diff:上次迭代的差值
//返回值是炮塔发射时瞄准点的坐标(注意不是实际命中点)
Vector3 calculateNoneLinearTrajectory(float gunVelocity,Vector3 gunPosition,float aircraftVelocity,
Vector3 aircraftPosition,Vector3 aircraftDirection,Vector3 hitPoint,float G,float accuracy,float diff){
//如果预测命中点是0 无解 返回0
if(hitPoint == Vector3.zero){
     return Vector3.zero;
}
//把炮塔正z指向预测命中点在炮塔高度的一个水平面上的投影点
//这样就构造了一个以炮塔为原点,以重力方向为-y轴 以炮塔正前方为x轴的标准抛物线2D坐标系,这个要自己体会下
Vector3 gunDirection = new Vector3(hitPoint.x,gunPosition.y,hitPoint.z) - gunPosition;
//构造一个从世界坐标到炮塔坐标的旋转矩阵
Quaternion gunRotation = Quaternion.FromToRatation(gunDirection,Vector3.forward);
//把预测命中点变换到炮塔坐标(减法是计算相对坐标差,再旋转到炮塔当前坐标来)
Vector3 localHitPoint = gunRotation * (hitPoint - gunPosition);
float V = gunVelocity;
float X = localHitPoint.z;//注视方向 前方是z,也就是抛物线坐标里的X
float Y = localHitPoint.y;
Vector2 TT = formulaProjectile(X,Y,V,G);//用抛物线方程计算射击仰角和飞行时间
if(TT == Vector2.zero){//如果无解 返回
     return Vector3.zero;
}
float VT = aircraftVelocity;
Vector3 PT = aircraftPosition;
Vector3 DT = aircraftDirection;
float T = TT.y;//TT的y是用抛物线方程计算出的弹丸飞行时间
Vector3 newHitPoint = formulaTarget(VT,PT,DT,T);//带入直线方程计算目标实际位置 注意目标的计算是在3D世界坐标进行的
float diff1 = (newHitPoint - hitPoint).magnitude;//判断预测点和实际目标位置的距离
if (diff1 > diff){//如果距离大于上一次计算的距离 那么要么迭代算法有问题 是发散的 要么就无解 返回0
     return Vector3.zero;
}
if(diff1<accuracy){//如果距离小于希望的精度 找到结果 返回瞄准点 炮弹是抛物线 发射时不能瞄准命中点 要计算瞄准点
     gunRotation = Quaternion.Inverse(gunRotation);//把刚才构造的旋转矩阵进行逆变换,从炮塔坐标变回世界坐标
     Y = Mathf.Tan(TT.x)*X;//TT的x是炮弹射出的仰角tan(仰角)*水平距离=垂直高度了(三角函数),这才是瞄准点的高度
    return gunRotation * new Vector3(0,Y,X) + gunPosition;//把瞄准点变换回世界坐标 注意X其实是Z
}
//即不是无解 也未达到精度要求 递归调用继续迭代 其中预测命中点用目标轨迹方程计算出的新位置取代 参考差值用本次计算的差值取代
return calculateNoneLinearTrajectory(gunVelocity,gunPosition,aircraftVelocity,aircraftPosition,aircraftDirection,newHitPoint,G,accuracy,diff1);
}

一个炮弹运动轨迹方程 一个目标运动轨迹方程,加一个迭代函数,就能完成计算抛物线弹道命中直线匀速移动目标的问题。实际使用的时候,可以先用方法1直线弹道算出一个命中点,作为初始预测点带入进行迭代,可以减少迭代次数。过程里使用了大量简化的向量和矩阵运算,对这部分不熟的读起来可能费劲。
在几千米范围以内的飞机,飞行速度在300-700km/h,炮弹出膛速度在500m/s(2战水平,其实高射炮出膛速度不止这么点),命中精度10m以内的前提下,基本上4次迭代以内可以完成。


3.更多复杂因素的计算
   在实际情况中,还可能有更多的影响。比如目标不是匀速直线而是加速运动或者曲线运动,比如空气阻力对弹道的影响,弹丸质心不在几何中心时与重力、空间阻力夹角产生的偏转力矩,炮弹在移动的平台上射击移动的目标。另外炮弹出射点是从炮口算起,在旋转炮塔和炮管的情况下,这个出射点其实是个球面轨迹而不是个固定点。火炮发现目标到炮口转动到合适位置的时间里,目标又发生了位移,所以还要计算这个炮塔旋转的提前量。这一系列的复杂问题其实都可以通过联立方程组,然后迭代求解的方法实现,原理完全一样,只是计算复杂度大大增加。


比如在考虑空气阻力等情况下,炮弹的轨迹方程会是这种形式:        

图片:tj08.jpg


 
这些影响因素可能是线性的、2次乃至高次的。 根据上面的算法,只要把抛物线方程组变成这个新的高次方程组求解,也可以适用。
再比如目标飞行的不是直线而是圆形,那么把目标的方程组变换成圆方程,也可以适用。当然在目标轨迹是非线性轨迹的情况下,迭代就不能用这种线性的迭代了,否则迭代结果会一会收敛一会发散,常规的方法是用目标轨迹函数的导函数计算迭代, 这部分实在很难做到通用,需要根据具体情况调整。


给出一个概念方程组:

图片:tj09.jpg



把这些方程组中按照影响关系进行多次分步迭代,最终能得到合适的解或者判断无解。这组方程可以应对各种复杂情况的组合,实际上军事上火控系统正是这样计算的。不过在游戏中,通常不需要这么复杂的计算,只要简单的模拟就足够了,所以只是从概念层面讨论一下,如果这些复杂因素都考虑进去,完全可以作成一个可视的军事仿真的程序来了。


惯例...只写原理不付DEMO大致是没多少人看的,附上自己写的demo

图片:tj10.jpg



Reset Target:设置目标飞机以随机方位角和速度飞行
Gravity ON/OFF:设置是否开启重力
Aiming Mode: Manual/Auto 设置瞄准模式人工/自动
人工模式下:wsad键上下左右旋转炮管方位角 空格键击发
自动模式下:炮管自动瞄准提前量瞄准点,空格击发 百发百中
AutoCam ON/OFF:设置飞机小镜头的显示模式,关闭自动镜头可以用按钮调整镜头的角度 + -按钮缩放镜头

图片:tj12.jpg


图片:tj17.jpg


Change Focus:改变主镜头焦点,在高射炮和目标飞机之间切换,以飞机为焦点时的镜头:

图片:tj11.jpg


 
如果无法命中,会发出提示音效
 
不同视角下的效果:百发百中的弹道 只给了炮弹一个初速度和方向 然后靠碰撞检测显示爆炸效果,过程完全靠物理引擎控制。

图片:tj13.jpg


图片:tj14.jpg


图片:tj16.jpg


图片:tj19.jpg


人工发射模式:

图片:tj15.jpg


 

图片:tj20.jpg


设置参数:
速度的单位都是m/s,长度单位都是m
Range是飞机初始位置的范围
Gravity Modifier:重力缩放因子,因为场景和模型是1:10比例,所以重力设置为1/10,否则就像玩具,模拟的效果不会真实
其他参数都会用这个因子缩放,所以按照实际情况设置就行。
TrajectorySim.unitypackage

 

  • 0
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
故事简介丑小鸭生来就很丑,谁都不喜欢它,从小被其他鸭子欺负。它无奈离开了妈妈,拿上一把猎枪,独自流浪,风餐露宿。每当遇到各种怪物而子弹不够用时,丑小鸭只能通过自己的血肉之躯踩死怪物。路上只能靠水果和蔬菜维持体力,无聊时也能抬头数星星。翻山越岭,逢水架桥,勇闯空栈道和独木桥,踩过蹦床,躲过电锯,钻过加农炮,坐过火箭,穿过枪林弹雨,在极度艰苦的条件下大战终极BOSS。最终,丑小鸭占领了一座豪华城堡,里面住着它心仪的白富美(其实也是个丑小鸭),它在夜色降临之前,轻轻关上门,打开灯,结束了流浪生涯,此时天空绽放绚烂的烟花,拉开幸福生活的序幕......这个故事告诉我们:只要你肯奋斗,我命由我不由天 时长课程分为上下两部,共64节课(21.1小时)其,上部29节课(8.5小时),下部35节课(12.6小时)课程特色对初学者友好,初次遇到新技术会详细讲解全程直播,坚决不在直播外偷偷修改展示所有细节,手把手教学游戏元素完整丰富,共3张地图18个关卡代码和文档开源,github托管地址 https://github.com/sailings/DuckAdventure完善的售后支持涵盖实战常用的知识点Physics,刚体,碰撞,弹簧体Mecanim,动画状态机,动画融合,动画层Animation,动画编辑与录制单例模式协程Dotween粒子特效射线检测键盘和移动端输入Cinemachine相机跟随,Confiner扩展UGUI常见控件,HUD屏幕自适应地图与关卡解锁关卡滑动背景滚动子弹轨迹计算音效管理场景编辑数据及上下文存储大纲丑小鸭历险记——趣味玩转unity2d游戏开发(上)  1.游戏简介及演示2.怎样画一匹骏马3.千里之行始于足下4.修复连续跳跃以及Jump动画融合和播放5.匍匐前进6.星星碰撞以及游戏管理增加积分7.吃水果蔬菜、游戏结束、制作水果蔬菜预设8.相机跟随、口水怪动画9.踩死怪物10.喷火怪11.钢管怪12.飞翔的小鸟怪13.食人鱼14.从天而降怪15.落水逻辑16.空栈道和独木桥17.蹦床和电锯18.加农炮19.强力磁铁20.坐着火箭旅行21.漫天飞舞的电锯22.枪林弹雨23.丑小鸭的反击24.定点保存25.Boss动画、移动、释放怪物、生命值管理26.Boss无敌以及特效27.Boss血条以及坠机冒烟28.Boss射击以及子弹轨道计算29.梦幻城堡
        针对有大量Unity初学者,只想在短期内(例如:2-4周时间)快速掌握Untiy的基本使用,了解基本开发技能。为了满足想要快速入门学员的学习要求,“刘国柱讲Unity”系列课程,因此推出了本套“Unity快速入门系列课程”,目前内容包含如下:    1: 项目:  “我的世界”: 讲解Unity软件的重要组成窗口与基本使用。    2: 项目:  台球游戏:   讲解Untiy脚本的基本使用,Unity碰撞体与触发器的使用。    3:  项目: “Flappy Bird” 讲解纯2D(手游)游戏的开发过程,了解Unity2D 开发技能。    4:  项目: 太空射击 讲解使用3D空间,开发2D手游的过程,其讲解“单例模式”做数据传值技术、基本粒子系统的使用、音频处理方法、碰撞与触发检测脚本算法......     5:   模块“移动端发布技术”,讲解快速发布Android 发布包(*.APK文件)技术。讲解JDK的安装与配置,以及Android SDK 的配置方式方法。   一、热更新系列(技术含量:高级):A:《lua热更新技术级篇》https://edu.csdn.net/course/detail/27087B:《热更新框架设计之Xlua基础视频课程》https://edu.csdn.net/course/detail/27110C:《热更新框架设计之热更流程与热补丁技术》https://edu.csdn.net/course/detail/27118D:《热更新框架设计之客户端热更框架(上)》https://edu.csdn.net/course/detail/27132E:《热更新框架设计之客户端热更框架()》https://edu.csdn.net/course/detail/27135F:《热更新框架设计之客户端热更框架(下)》https://edu.csdn.net/course/detail/27136:框架设计系列(技术含量:级): A:《游戏UI界面框架设计系列视频课程》https://edu.csdn.net/course/detail/27142B:《Unity客户端框架设计PureMVC篇视频课程(上)》https://edu.csdn.net/course/detail/27172C:《Unity客户端框架设计PureMVC篇视频课程(下)》https://edu.csdn.net/course/detail/27173D:《AssetBundle框架设计_框架篇视频课程》https://edu.csdn.net/course/detail/27169三、Unity脚本从入门到精通(技术含量:初级)A:《C# For Unity系列之入门篇》https://edu.csdn.net/course/detail/4560B:《C# For Unity系列之基础篇》https://edu.csdn.net/course/detail/4595C: 《C# For Unity系列之级篇》https://edu.csdn.net/course/detail/24422D:《C# For Unity系列之进阶篇》https://edu.csdn.net/course/detail/24465四、虚拟现实(VR)与增强现实(AR):(技术含量:初级)A:《虚拟现实之汽车仿真模拟系统 》https://edu.csdn.net/course/detail/26618五、Unity基础课程系列(技术含量:初级) A:《台球游戏与FlappyBirds—Unity快速入门系列视频课程(第1部)》 https://edu.csdn.net/course/detail/24643B:《太空射击与移动端发布技术-Unity快速入门系列视频课程(第2部)》https://edu.csdn.net/course/detail/24645 C:《Unity ECS(二) 小试牛刀》https://edu.csdn.net/course/detail/27096六、Unity ARPG课程(技术含量:初级):A:《MMOARPG地下守护神_单机版实战视频课程(上部)》https://edu.csdn.net/course/detail/24965B:《MMOARPG地下守护神_单机版实战视频课程(部)》https://edu.csdn.net/course/detail/24968C:《MMOARPG地下守护神_单机版实战视频课程(下部)》https://edu.csdn.net/course/detail/24979  
编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值