首先放个视频来展示一下《星之海的阿斯特莉娅》最新的开发进度吧:加入了防空系统,可以拦截敌人的导弹
[[https://www.bilibili.com/video/BV1i541167BY/]](假装这里有视频,b站换成bv号了,这里好像还不支持)
然后来说说我在游戏中是如何实现子弹的碰撞检测吧。这部分其实是很早以前就写好的系统,只不过最近调试又发现了bug,而且这个bug的原因我实在是不吐不快。
首先要在Unity里实现子弹的碰撞检测,最直接的方法当然就是使用碰撞体了。但是这样也有一些可能缺点,比如弹速太快的话可能会穿墙啊,刚体数量太多消耗性能啊,处理不好Layer和是否Trigger的选取导致子弹发射时跟玩家的单位发生碰撞产生奇怪的物理效果等等。我希望子弹尽量简单一些,只检测碰撞,不需要物理效果,于是选择了Raycast的方法。然后在搜索Unity手册的过程中发现了这个:
RaycastCommand
一看描述:schedule一批射线检测,并行执行。这不是太好了吗,Unity性能瓶颈就在于单线程,而这个功能能把射线检测放到多线程去执行,那肯定有益于提升性能啊,那就用吧。
写一个BulletManager类,其中每帧更新时执行如下代码:
bullets.RemoveAll((x) => (x == null || x.destroyed));//先清理一下子弹数组,把已经不存在的子弹去掉 //把每个子弹的RaycastCommand放进一个数组 var results1 = new NativeArray<RaycastHit>(bullets.Count, Allocator.TempJob); var commands1 = new NativeArray<RaycastCommand>(bullets.Count, Allocator.TempJob); for(int i = 0; i < bullets.Count; i++) { commands1[i] = bullets[i].getRaycastCommand(); } //批量执行 JobHandle handle1 = RaycastCommand.ScheduleBatch(commands1, results1, 1, default(JobHandle)); //中途可以先干点别的 // Wait for the batch processing job to complete handle1.Complete(); //处理射线检测结果 for (int i = 0; i < bullets.Count; i++) { bullets[i].ReceiveRaycastResult(results1[i]); } // Dispose the buffers results1.Dispose(); commands1.Dispose();
然后子弹类里面有这两个函数:
//生成射线检测的指令:从当前位置到下一帧位置的射线 public virtual RaycastCommand getRaycastCommand() { float d = Speed * Time.fixedDeltaTime; return new RaycastCommand(t.position, t.forward, d,BulletManager.bulletLayerMask); } public virtual void ReceiveRaycastResult(RaycastHit result) { if (result.collider != null) { //处理碰撞的的结果,如是敌人则造成伤害等等 } }
这样就可以实现批量子弹的碰撞检测了,就是子弹非常多也不用担心碰撞检测部分消耗太多性能了。
后来,我又开发下一种武器:光束武器,于是又遇到了新的问题。光束武器与子弹不同,是瞬间击中敌人的,因此射线检测不是一次检测一小步,而是直接一条射线到头的。但是射线中途可能友军的单位挡住,而我又不希望友军能够遮挡光束,不然要让AI瞄准敌人时还要避开友军,运算太复杂了。因此需要使用多次碰撞的射线检测,检测到友军则继续,直到碰到敌人为止。我又看了一眼RaycastCommand的文档:
maxHits这个参数可以指定最多碰撞几次,那不是正好吗,只要设置成一个比较多的次数,比如10次,然后对返回结果依次判断一下,不就解决了!我一直以为这样是正确的……
直到最近调试时发现这样的现象:
怎么有的光束直接穿过敌人了,而没有检测到呢?经过一番分析,发现发生穿透的光束都是先穿过己方再穿过敌人。然后反复检查了一下自己写的碰撞逻辑,死活找不出问题。于是想到在遍历检测结果时加上个输出看看,输出结果个数一看:
怎么只有零和一呢?于是我上网搜索了一下,这个RaycastCommand用的人还挺少的,基本找不到什么官方文档以外的说明。终于,在Unity论坛里找到了一个帖子,提到了这个问题,帖子里有一个用户贴出了bug反馈,点开一看,官方这回复
批处理的射线检测只能检测一次碰撞,不能检测多次,是文档没有解释清楚。那你文档里煞有介事的写个其实没有任何作用maxHits有什么用啊。而且这个issue很早就关闭了,但文档至今也没有改,也没把多次碰撞加上。
坑爹呢这是!!!
没办法了,只能用一次碰撞将就了。后来我想出的解决办法是一次碰撞后如果检测的是友军,就将距离记录下来,下一帧再进行射线检测时跳过这个距离,直到检测到敌军为止。这样虽然会有几帧的延迟,不过最终效果差不太多。
顺便再分享一个小技巧:
制作防空炮的效果时,一开始是真的高射速发射大量子弹,后来觉得这样太浪费性能,画面效果也不美观,于是就想了另外一个方法。防空炮的效果,一方面子弹要慢一些,能够看清子弹飞行已及子弹之间断断续续的效果,而不是连成一片,另一方面又要子弹足够快,才能击中高速运动的导弹。这样既快又慢相互矛盾的要求如何实现呢?解决方法是这样的:防空炮的发射的子弹实际是一个光束,这样就能瞬间击中,而给光束的LineRender配合上一个贴图滚动的Shader,这样是不是就产生了发射子弹的错觉。
用普通Raycast就行了,只检测敌对势力Layer的对象,计算一下击中距离,换算成子弹飞行时间,子弹到时间销毁,就不会因为飞太快检测不到了
@LouisLiu:RaycastCommand是多线程并行运算,在大量子弹的情况下比普通Raycast效率更高。目前一个势力就需要船体、护盾、导弹等多个Layer了,而且两阵营使用的prefab资源是共通的,为两个势力分别指定一套Layer太麻烦了,而且考虑到以后可能要做中立和第三势力无法实现可扩展性,因此不同势力使用的是相同的Layer
@ZerothShell:prefab实例化的时候,在初始化相关逻辑里设置Layer就好了,prefab只保存模型和一些基本数据配置的信息就好了