Unity ECS实现RTS游戏中的游戏单位框选、集结和移动控制

今天想给大家分享的主题是如何实现RTS类型游戏中的游戏单位角色控制
本文中会介绍如何运用最新的ECS架构来实现游戏单位控制
版权声明
本文为“优梦创客”原创文章,您可以自由转载,但必须加入完整的版权声明
更多学习资源请加QQ:1517069595或WX:alice17173获取(企业级性能优化/热更新/Shader特效/服务器/商业项目实战/每周直播/一对一指导)
点赞、关注、分享可免费获得配套学习资源
详细内容可参考文末完整视频
效果演示

效果实现
选中多个游戏单位
上方代码实现的功能是获取被鼠标框选的游戏单位,如果需要源代码可以在文末添加爱丽丝老师的QQ或者微信号领取
代码讲解
获取鼠标框选方框的左下角和右上角
鼠标在按下和弹起的过程中画出的方框一般存在两种情况
鼠标的起始位置对应左下角,终止位置对应右上角
鼠标按下时的起始位置是右上角,终止位置则是左下角
在计算方框的起始位置为右上角,终止位置为左下角时不能直接用起始位置当方框的左下角,要把终止位置当做方框左下角的位置
想要统一的获得左下角和右上角的位置需要写一些算法,如上方代码所示,这个算法很简单,就是比较起始位置和终止位置,取较小值作为左下角点,然后用两者的较大值作为右上角点
查找被选中的游戏实体
ForEach方法的作用是遍历每一个游戏单位,后面的Lambda表达式的功能是判断游戏单位的位置坐标是否在鼠标框选范围内,并打印鼠标范围框选范围内的游戏单位
绘制选区

在场景中创建一个空节点,起名为SelectionArea(选择区域),再创建一个空子节点,取名为Sprite(精灵节点)

为精灵节点添加精灵渲染器,并选择一张绿色的图片(把白色图片设置成绿色也可以)
这里需要注意一下这张图片的大小

我们为这张图片设置了0.5的偏移值,这是什么意思呢?

也就是说SelectionArea(选择区域)节点和Sprite(精灵)节点的位置关系变成了上图的样子,上图中红色坐标轴的原点就是SelectionArea(选择区域)节点的位置,蓝色坐标轴的原点则代表Sprite(精灵)节点的位置,这样偏移以后,将来拖拽、缩放选框时就会以红颜色的中心点为起点,会比较方便
在代码中实现动态绘制选框
注意
SelectionArea节点被添加到总控脚本ECS_RTSControls里了,所以在上方代码中是通过访问总控脚本的单例来获取SelectionArea节点
此脚本是在之前的UnitControlSystem脚本上增加了一个类和一些代码
这些代码会在鼠标按下时激活SelectionArea节点,并将SelectionArea节点的位置设置为鼠标按下的位置
在鼠标拖拽过程中会不断获取鼠标当前位置,并用鼠标当前为减去鼠标初始位置,以得到当前鼠标框选的选区大小,然后赋值给selectionArea的localScale,这样就实现了selectionArea选区随着鼠标拖拽自动改变大小的效果
最后在鼠标抬起时会将selectionArea的SetActive设置为false,隐藏鼠标选区,并为被选中的游戏对象添加UnitSelected结构体
效果演示

绘制角色脚下的圆圈
注意
用于绘制角色脚下圆圈的材质被添加到总控脚本ECS_RTSControls里了,所以在上方代码中是通过访问总控脚本的单例来获取的圆圈的材质的
圆圈的网格模型则是在ECS_RTSControls调用Unity动画的创建网格方法动态创建的,所以也通过ECS_RTSControls的单例获取
unitSelectedCircleMaterial(角色选中材质)

如果需要项目源码或资源可以在文末通过添加爱丽丝老师的QQ获取
效果演示

问题
问题1:无法取消选中
上面的代码在选中了左边的角色后,点击空处或再次选中其他角色时并不会取消之前被选中角色的选中状态,为了要解决这个问题我们添加了几行代码,让UnitControlSystem脚本在鼠标弹起时遍历所有游戏对象,删除它们身上的UnitSelected组件
问题2:无法通过点击选中游戏对象
上面代码实现的选中效果必须要把游戏对象整体框入才能选中,但在RTS游戏里,游戏玩家对于单个游戏对象是可以通过点击选中的,而且框选也很麻烦,那怎样才能让它可以点中选中一个对象呢?方法很简单
就是扩大最小选区,选择区域在点击时自动扩大一圈,这样就能确保点击选中单个角色
注意:
这些代码会在鼠标抬起时执行,它会检测当前鼠标选区大小是否小于鼠标选区最小值,如果小于则会根据鼠标选区最小值减去当前鼠标选区大小的值放大鼠标选区
这样就保证了最小区域是足够大的,可以通过点击选中游戏单位
让游戏对象向指定的方向移动
注意:
这段代码会在鼠标右键抬起时执行,它会为所有被选中的游戏对象设置移动目标点,并使游戏对象向目标点移动
MoveTo是一个移动脚本,MoveTo的position代表游戏角色移动的目标点,move代表是否开始移动,如果需要完整的ECS源码资源,可以在添加爱丽丝老师领取
效果演示

实现游戏单位按阵列移动
上面实现的效果还有一个问题存在

可以看到上面的两个角色变成一个了,因为如果他们的移动目标点是相同的,所以这两个角色在移动时就会重叠起来
上面代码中的movePositionList是游戏单位的移动位置列表,当鼠标右键按下设置目标点时,这段代码会遍历所有被选中的游戏单位,并使用positionIndex(位置索引)取出移动位置列表里计算好的移动位置,让这些游戏单位的移动位置都不一样
效果演示

可以看到被选中的游戏单位朝着同一个目标点移动,并且位置都各不相同,这是因为目标点在movePositionList经过处理后,产生四个位置不同的坐标点,这样赋值给游戏单位的就是位置不同的坐标点了
这段代码的问题也很明显:当玩家选中四个以上的游戏单位进行移动时,仍然会产生重叠现象
这是因为movePositionList里的元素只有四个,当这段代码遍历完所有元素时,就会从movePositionList的起始位置重新遍历,所以多出来的游戏单位位置会与其他游戏单位重叠
解决这个问题最简单的方法就是增加movePositionList里的元素个数,让元素个数始终大于游戏单位个数,这个问题自然迎刃而解
不过在一些游戏单位数量动辄就是几十、几百上下的RTS游戏中,这种方法就不够看了,需要用另一种方法
上面的GetPositionListAround会返回一个位置列表,位置列表里的所有元素都会以该方法的position参数为圆心,distance为半径呈圆形排列(如下图),这些元素的数量就是positionCount

ApplyRotationToVector的作用则是通过传入的角度(angle)来构造旋转值,并使用这个旋转值旋转向量(vec),旋转到了angle所代表的角度,这个方法背后的数学原理,本文就不去细讲了,因为内容很多,如果想要学习这方面的知识,可以在文末添加爱丽丝老师了解
对之前的代码进行修改,使用GetPositionListAround生产位置列表
效果演示

由于在上面的代码中GetPositionListAround只指定了5个元素数量,所以重叠的现象依然存在,代码还需要继续改进
最上方的GetPositionListAround是对之前写的GetPositionListAround的改进,可以让小兵的位置分布成几个圆环(如下图所示)

新的GetPositionListAround方法的第一个参数startPosition仍然代表圆环中心点的位置,第二个参数ringDistance是半径数组,用于存放所有圆环的半径,第三个参数ringPositionCount代表每一个圆环上的位置点数量
效果演示

思考练习
实现不受位置点数量限制,为每一个游戏单位生成不与其他单位重叠的位置点(因为只要小兵数量超过上面代码中设置的位置点总数,还是会有游戏单位重叠现象)
兵种遇到障碍能否实现游戏单位躲避障碍行走到目标点(A*,ECS实现)
写在最后
更多学习资源请加QQ:1517069595或WX:alice17173获取(企业级性能优化/热更新/Shader特效/服务器/商业项目实战/每周直播/一对一指导)
点赞、关注、分享可免费获得配套学习资源
详细内容可观看下方完整视频
