如何制作RTS游戏的寻路系统?

如何制作RTS游戏的寻路系统?

本期聚集话题:RTS游戏的寻路系统、AssetBundle打包后md5不同、UGUI中3D血条优化问题、如何快速改变场景里一部分物件的外观效果...


这是第107篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题,建议阅读时间15分钟,认真读完必有收获。文末,我们的互动话题是:能耗发热怎么办?期待你的灼见!

UWA 问答社区:answer.uwa4d.com
UWA QQ群:793972859(仅限技术交流)

本文封面图来源:RTS手游《战地指挥官》
http://zdzhg.53mayi.com


寻路

Q1:我们在做一个RTS游戏,开始用的是Unity自带的NavMesh的寻路,但发现这个并不适合RTS多人寻路,因为总会出现阻挡和闪跳的问题。看Asset Store上的A* path插件评论说在碰撞上有问题,不知道大家是否能提供比较成熟的解决方案?

我们上一个项目是RTS项目,刚开始寻路问题解决方案过程中遇到过和你相似的疑惑,修改探索和调教了很长时间,分享几点供题主参考下。

先上结论:我们采用了两层结构 A Start Pathfinding Project Pro + Unity NavMesh。

1)主体寻路方案使用 A Start Pathfinding Project Pro 这个插件中传统的网格形式,并进行了深度修改扩展,主要是满足寻路过程的通路性需要考虑到单位半径大小,这样寻路精度高;

2)使用Unity NavMesh系统作为寻路过程中的碰撞系统,因为你说的很对,这个寻路插件的碰撞有问题,稍后解释。

以上两部分组成“寻路算法+精确碰撞”。接下来说下以上方案的原因和细节。

1、NavMesh寻路速度快但不够精确。RTS还是适合使用传统的四方形NavGrid格子寻路,而且现有的任何寻路算法默认都是不支持考虑寻路单位半径大小对寻路过程的影响,但我们策划绝不允许1米宽的缝隙被一个两米宽的胖子挤过去,如果使用Navmesh并且动态生成阻挡会出现寻路路径是有了,可是由于碰撞问题大的单位会卡在缝隙这里来回“发抖”的情况;这个只能自己动手修改,所以我们买了该插件然后自己从里到外修改,具体修改算法原理和修改后的示意图,你可以参考这里:http://www.cnblogs.com/yaukey/p/rts_unit_traverse_size_based_path_finding.html
修改完后,每次寻路会将单位的直径大小当做寻路参数之一传入进行寻路。

使用这个插件开启两个线程左右来寻路是我后来调教出的、一个比较理想的结果。然后是地图中各种单位会出生和死亡,当单位站定不动,需要生成阻挡;移动了需要移除阻挡恢复此处的通路性,通过再修改下插件中的DynamicObscule这个组件(原生功能太简单)来实现;我们具体是每个单位都有一个对应的大小的CapsuleCollider空对象挂上这个脚本,单位站定将该物体放置到单位的位置并激活它,单位移动隐藏该空物体即可,这个脚本会引起地图通路性的更新,这个更新是多线程的,这一点是要注意的,通过回调事件来做响应的事情,切记不要在同一帧或者下一帧去做,因为即时性不能保证。这样就能让整个地图一直根据单位来更新通路性。

2、寻路的通路性问题解决了,现在来解决碰撞问题。这个插件自带了碰撞算法,使用的rvo算法,但是据我当初各种调教实验,它的碰撞都达不到我们的要求,单位多了拥挤的时候总是会有些单位站立后变得重叠了,最不能接受的是两个单位完全重叠。在这里我花了相当多的时间都不能修改出的满意的效果。后来想起来recastnavigation作者亲口说过虽然Unity的NavMesh基于此,但是Detour系统经过深度的改写,Detour包含了巡航和碰撞,而实际使用也发现NavMeshAgent之间的碰撞是很精确的,于是我想了个取巧的办法,给地表再铺一层跟AStar插件的寻路面积一样大的NavMesh,但是没有任何的阻挡(理解为一片纯蓝色),同时给寻路单位添加一个NavMeshAgent,然而这个东西只用它的碰撞,而不使用寻路(没有阻挡,任何寻路都是两点直线,可以考虑几乎无开销),这样最后实际的碰撞效果才算达到我们满意的效果。

以上就是我所说的两层含义,我再说下坑和问题。

1)当初开发Unity版本是Unity 5.3.7-5.3.8,插件版本5.x,寻路插件中底层会有Physics.CheckCapsule调用,但是多线程大量调用,会导致底层PhysX物理引擎崩溃,游戏闪退,不必现但是大量的话很容易一下就挂,插件官方论坛也有这个Bug讨论,后来我将此换成了两个 Physics.CheckSphere 调用,目前2017版本没有测试过;

2)我在1中使用的方法,在UWA性能评测中,每次都是Dynamic Collider超标,这个需要自己取舍了,但是总体我们游戏的瓶颈不在寻路一块,消耗在这些测试中看来比较理想;

3)插件中由于是多线程更新,有很多的完成事件回调,基本都是利用这些链接游戏系统中,不够了自己加,否则依然是容易在未更新完成时得到错误的结果;

4)有可能的话再优化下内部的一些堆内存分配,并且和游戏系统结合后注意释放和断开引用,否则容易内存泄漏,造成寻路插件被引用以至于后面引用很长一串对象都不得释放;

5)寻路精度和碰撞精度在游戏中的表现,需要花比较多的时间去调教参数,有时候表现不好未必是方案不好,然后如果你们对实时同步性要求较高,这个方案还必须再进一步完善和扩展下。

我们使用这个寻路方案的项目是:《战地指挥官》,你可以下载看看寻路是否符合你的要求,苹果安卓各平台都有。以上是我暂时想起来的一些心得,希望对题主有所帮助:)

感谢Yaukey@UWA问答社区提供了回答,欢迎大家转至社区进行进一步交流:

https://answer.uwa4d.com/question/5ad0553b910222635366dd34


渲染

Q2:我想要快速改变场景里一部分物件的外观效果,考虑到对DrawCall的影响、修改材质的耗时等等,怎样做是最高效的呢?

如果只是修改材质属性,那么可以用 MaterialPropertyBlock,如修改Color :

 MaterialPropertyBlock mpb = new MaterialPropertyBlock();
    mpb.SetColor("_FillColor", Color.red); 
    GetComponent<MeshRenderer>().SetPropertyBlock(mpb);

因为 MaterialPropertyBlock 的修改效率高,另外还可以配合 GPU Instacing合并 DrawCall。可以参考这篇文章:https://blog.uwa4d.com/archives/1983.html

感谢ice@UWA问答社区提供了回答,欢迎大家转至社区进行进一步交流:

https://answer.uwa4d.com/question/5acc75e8425802635474fc88


资源管理

Q3:我们在Unity 5.6.5版本下写了一个AssetBundle打包工具。用了最新的Pipeline那个API,就是带manifest的。举个例子,我们打包一个Perfab,这个Prefab有Texture、Shader和脚本。问题来了,我们在不同的电脑,都在Android平台下,打出的AssetBundle不一样,用UnityStudio分析了一下,是Shader的md5不一样导致的,请问大家有什么建议吗?

Unity一直有这个问题的,就算是同一台电脑,删掉Library再打也会不一样。我分享一下我们打补丁的工具的对比流程:

1)首先基于SVN找出两个版本差异的AssetBundle,缩小范围;
2)然后对比对应AB的manifest文件,看AssetHash是否真的变化了,过滤掉没有变化的。
同时检查了一下TypeTreeHash,如果发生了变化给出警告。

顺带吐槽一下这个API:
https://docs.unity3d.com/ScriptReference/BuildPipeline.GetHashForAssetBundle.html
它的实现就是去直接读同名的manifest里的AssetHash值,很容易让人误以为这API是去计算hash。

感谢littlesome@UWA问答社区提供了回答

UWA:MD5的问题一直存在,建议直接通过AssetBundle的Hashid来进行判断。
相关链接:
https://docs.unity3d.com/ScriptReference/BuildAssetBundleOptions.AppendHashToAssetBundleName.html

感谢欢迎大家转至社区进行进一步交流:
https://answer.uwa4d.com/question/5ace1d68425802635474fc9e


UI

Q4:我们使用UGUI WorldSpace Canvas 制作了小兵(小兵会经常移动、数量在几十个左右) 3D UI血条,血条UI会自动根据距离相机的远近形成合理的渲染顺序。但是这样的方式导致每个血条都需要一个Canvas, 一个Canvas 就是一个DrawCall。所以我们把所有的血条整合进同一个WorldSpace Canvas下面,这样所有的血条UI只有一个DrawCall,但是这样就无法形成合理的遮挡关系。

于是我们通过每隔几帧计算所有血条UI距离相机的距离进行排序,更改其transform.SetSiblingIndex 来模拟实现遮挡顺序问题,但是这样一来会增加CPU的计算。所以想问问大家对于数量较多的3D UI血条的优化是怎么处理的? 我们上述的优化方案是否可靠?感谢指点。

UWA:首先UGUI里要改变遮挡确实只能改SiblingIndex,如果需要排序和修改的血条量比较大的话确实容易带来明显开销。但“几十个”算不算大确实不好说,建议先测试下这种做法在低端机上的表现,主要关注的就是算距离、排序、改SiblingIndex的开销总和,如果也就1~3ms那还是可以接受的,毕竟是隔几帧做一次的。

但如果开销非常明显,甚至引起降帧了,那么UWA建议改用Unity2D的Sprite试试,这个是天然用相机距离排序的,不需要自己来做了。只是Unity2D在某些版本上还不支持Sliced等模式(2017.2是支持的),可能视觉效果上会有影响。

PS:接触下来,其实不少游戏是不对血条排序的,可能是因为不太容易被关注到,所以偷懒的办法就是低端机就不排了…

该回答由UWA提供,欢迎大家转至社区进行进一步交流:
https://answer.uwa4d.com/question/5ace15b7425802635474fc9c


今天的分享就到这里。当然,生有涯而知无涯。在漫漫的开发周期中,您看到的这些问答也许都只是冰山一角,像这样实用的知识点在UWA问答平台(answer.uwa4d.com)上还有一打!UWA欢迎热爱进步的你加入,也许你的方法恰能解别人的燃眉之急;而他山之“石”,也能攻你之“玉”。