在我们正常的开发过程中,除了一些必要的射线检测,为了性能着想,我们都会尽可能的不使用unity的物理系统,因为unity为了让物体具备真实的运动效果,通常底层会有大量的算法,就比如collider的触发器,看似一句api就搞定的触发,实际上底层经过大量的运算,这种运算是十分消耗性能的。当然,如今我们处在性能过盛的时代,大多数人不必要考虑这方面的消耗,但是就我而言,性能这东西,能抠一点是一点,等到你的游戏成型之后,你就会有大惊喜,这也就是为什么Unity开发为什么非常注重优化的原因。项目往往到了中期,老大打手一呼,全部人手里活先停一停,搞一波优化再开工,这样的事情屡见不鲜。
我们在开发一些比较大型的3D游戏过程中,逻辑代码非常多,很多技能,位置的运算其实是很复杂的,如果每个小兵、防御塔之类的都用触发器,那么1000个小兵就是1000个触发器,这是很恐怖的,为了不增加CPU的压力,使得游戏更加流畅,我们就会自己使用公式去模拟一些物理行为。如果你只是开发一个小型的,休闲益智的小游戏,就没有必要纠结这些了,物理系统该上就上,这些游戏大多时间短任务急,内容也不多,用物理系统能给自己省不少事情。这里会写3种算法,个人改良的,感觉比较好用比较实用,所以记录下来。
一、圆形攻击判断
圆形适用于360度无死角的检测,就像英雄联盟,一旦你进入防御塔的攻击范围,无论你是在哪个位置,打你没商量,这种算法是最简单的,也不需要很多的位置运算,逻辑就是以防御塔自身为圆心,触发攻击的范围是半径(这个自己设定,看各自的需求),计算圆心与英雄根节点的距离再跟半径对比,通常会有三个结果,圆上,圆内,圆外加上自己的逻辑即可,下图的逻辑就跟农药一样,当你到一定范围,先是警示,再进去就发动攻击。
1 /// <summary> 2 /// 圆形攻击判断 3 /// </summary> 4 /// <param name="hero">英雄方位</param> 5 /// <param name="attackRadius">攻击距离</param> 6 /// <param name="warnningRadius">警告距离</param> 7 void RoundAttackLogic(Transform hero,float attackRadius,float warnningRadius) 8 { 9 float tmpDis = Vector3.Distance(hero.position, transform.position); 10 if (tmpDis > attackRadius) 11 { 12 if (tmpDis <= warnningRadius) 13 { 14 //警告 15 } 16 } 17 else 18 { 19 //攻击 20 } 21 }
二、扇形攻击判断
扇形攻击是从圆形攻击演变而来,复杂了那么一丢丢。圆形攻击判断是360无死角的,那么策划同学过来了,说我现在不做防御塔了,我要做一个灯塔。好样的,那么这样再继续使用圆形攻击判断显然是不行的。大家都知道,灯塔是从一点出发,越往远处照射范围就越大,还有一点,就是灯塔照射范围存在角度,并不是周围全部扫描,就类似于一个扇形。算法逻辑还是先算距离,但是这里不同的是算扇形原点和英雄之间的向量,因为现在存在角度需求,我们必须用向量,因为向量有大小有方向,我们能算出角度,这样才符合需求。让灯塔的Z轴永远处于照射角度的中分线上,做灯塔圆心与英雄之间的向量AB,之后AB的单位向量与Z的单位向量做向量的点乘(不明白的去看一下向量的知识,由AB 点乘 Z = |AB| |Z| COSX 变形而来),这样可以得到两个向量之间的角度余弦值,通过反三角函数Acos得到弧度,之后弧度转角度得到向量之间的夹角,AB的模就是圆心距离英雄的距离,那么现在夹角有了,距离有了,就完事具备了。
1 /// <summary> 2 /// 扇形攻击逻辑判断 3 /// </summary> 4 /// <param name="hero">英雄坐标</param> 5 /// <param name="attackAngle">攻击角度</param> 6 /// <param name="attackRadius">攻击半径</param> 7 void SectorAttack(Transform hero,float attackAngle,float attackRadius) 8 { 9 Vector3 tmpVec = hero.position - transform.position; 10 float cosValue = Vector3.Dot(tmpVec.normalized, transform.forward); 11 float realAngle = Mathf.Acos(cosValue) * Mathf.Rad2Deg; 12 if (realAngle <= attackAngle / 2 && tmpVec.magnitude <= attackRadius) 13 { 14 //攻击 15 } 16 }
三、矩形攻击判断
做完了上面那些,那么恭喜你,策划同学又该来找你了(哈哈哈哈哈哈),这次策划同学说我们不做防御塔,也不做灯塔了,我们改防御强吧。需求是这样的,墙面向外延伸发电,进到触犯范围就直接放电攻击。这下好了,扇形,圆形都不能用了,给你来方形了,半圆是不能代替矩形,这样就会导致我还没进入范围,你就开始攻击,这不合逻辑。于是就有了矩形攻击判定,思想是一样的,做向量,然后向量点乘,这里对比扇形的向量计算又是不一样的,注意了!还是前面一样,做墙根节点与英雄的向量AB,然后与墙前方的单位向量做点乘(这里为什么是前方呢,你见过哪个防御强是防着墙后自己人的...),注意啦!!!这里我没有说AB的单位向量!!!而是直接用AB向量来点乘!!!还是上面的公式变形,当两个向量点乘,其中一个向量为单位向量时,得到的是另外一条向量在这条单位向量方向上投影长,而矩形攻击正式用到这个概念。现在没有半径,那我们就算英雄是否墙前方规定的范围内,AB点乘墙的forward,得到AB在不在墙的防御范围内,也得到了在墙的前方还是后方,没错,这个点乘出来的值是有正有负的,前面说到,两个向量点乘,也能得到其夹角的余弦值,这也很好的反应了身前身后的问题,算出来的值,以墙的forward的0度,0-90 大于0, 90-270小于0, 270-360大于0,这既是投影长也是余弦值。同样的方法再用于左右两侧,就能知道到底在不在范围了。
1 /// <summary> 2 /// 矩形攻击判定 3 /// </summary> 4 /// <param name="hero">英雄</param> 5 /// <param name="forwardAttack">前方防御范围</param> 6 /// <param name="sizeAttack">左右防御范围</param> 7 void RectAttack(Transform hero, float forwardAttack, float sizeAttack) 8 { 9 Vector3 delteA = hero.position - transform.position; 10 float forwardDis = Vector3.Dot(delteA, transform.forward); 11 if (forwardDis <= forwardAttack && forwardDis > 0) 12 { 13 float bothSize = Mathf.Abs(Vector3.Dot(delteA, transform.right));//能进到这里说明前方满足条件,而左右两边正负都行,所以只判断距离 14 if (bothSize < sizeAttack) 15 { 16 //攻击 17 } 18 } 19 }
最后,这里面都是用的向量的知识,我只能这么说,如果你做Unity开发,向量,矩阵是必须会的东西,不用深刻理解,但至少得明白什么意思,这些知识不止运用于方位坐标的运算,包括shader的顶点计算,像素计算都会使用到,不明白的建议补一下该方面的知识