
本文将介绍一种适用于2D、2.5D视角游戏的子弹弹射算法。游戏场景基于Tile的结构,每个障碍物都可以看做是Unit Tile,也可以称作地块。

扫描阶段
根据输入线段作为对角线,构建一个矩形区域,该矩形区域内所有可反射的地块元素即为潜在的遮挡障碍。
下面介绍一下客户端的瞄准线关于扫描阶段的思路。
由于场景地图是由Tiled编辑器进行编辑的,所以可以将场景想象成一个大的棋盘。每个地块元素的位置只可能是位于棋盘单元格的中心位置。
首先,利用哈希结构存放地图中所有类型的地块数据。其中,Key值表示地块的格子坐标,Value值对应地块的详细数据集合。
采取类似“栅格化”的策略,将线段端点转换成「格子坐标」,在根据方向、矩形区域的长宽(格子长度)来进行栅格遍历,再通过格子坐标来去查找得到对应的地块元素类型,如果是符合反射需求的地块元素,则加入扫描结果集合中。
检测交点
编码裁剪算法
为地块障碍物以及周边区域进行「二进制编码」 黄色:地块障碍物 蓝色:上、下、左、右四个区域 绿色:左上、右上、左下、右下(相邻蓝色编码「按位与」计算可得)

分别计算线段两个端点相对于黄色地块的编码分布,可以快速判断不相交的情况:「编码按位与计算不等于0」
如果存在相交可能,根据线段起始点的编码,来获取黄色地块最邻近的两条边(线段),例如:
❝ 起始点位置的编码是0001,则只可能和左边相交 起始点位置的编码是0101,则可能和左边和下边相交
❞
判断两线段相交
当确定输入线段与可能相交的边后,剩下要做的就是判断两条线段是否相交。
已知两向量 「a(x1, y1)」 和 「b(x2, y2)」,它们的叉乘结果就是之间夹角的正弦乘以向量的模,即
因此,根据两向量叉乘结果的正负关系,可以推断出线段端点是在另一条线段端点的同侧还是异侧。

如上图,我们只需要确定c点和d点在线段ab的异侧并且a点和b点在线段cd的异侧,就可得出线段ab与线段cd相交的结论,判断依据就是「同时满足」:
其中,叉乘的计算公式为:
计算交点坐标
本质上就是二元二次方程的求解,这里直接给出公式:
确定法线方向
由于地块的摆放是统一规整的,所以反射平面的法线向量也是固定的。
top(0,1)bottom(0,-1)left(-1,0)right(1,0)
理论上来说,入射向量与法线向量的夹角应该是小于90°的,即它们的夹角余弦应该大于0。利用向量的内积运算即可快速的计算出两「单位向量」的夹角余弦:
因此,在之前确定潜在相交边的时候,需要做一个检验判断,即入射方向与边的法线向量的内积,应该是大于0的,否则该边不属于合法边。

如上图,线段与地块的交点恰好是地块矩形的某个顶点的话,该点实际上是同属于相邻的两边的,如果我们认为是与右边相交,而使用法线向量(0,1),计算出的反射结果就会是错误的。
「需要特别注意」,如果交点在矩形顶点上,并且入射方向与相邻两边的法线向量夹角都小于90°,取夹角余弦值(内积结果)更大的作为反射法线向量。如下图:

交点在矩形的左下顶点,并且入射向量与左边与底边的法线向量夹角都小于90°,由于入射向量与底边的法线向量(0,-1)夹角余弦值(内积结果)更大,所以选择底边的法线作为反射法线。
计算反射方向
当已知了入射方向和法线方向,计算反射方向就尤为容易了。

如上图,已知入射向量 「I」(由瞄准起始点和反射点求出),法线方向 「N」(单位向量),求反射向量 「R」。
根据向量加法特性可知:
由于入射角等于反射角,根据等腰三角形特性可知:
所以,
其中,「S」 就是 「-I」 在向量 「N」 上的投影,根据投影公式可知:
最终的反射向量计算公式:
这里对向量「R」进行归一化,即可求出反射方向(单位向量)。接着,再根据反射点和反射方向作为新的起始点和入射方向,继续计算下一轮反射。新的线段长度,需要剔除之前的入射向量的长度,如果小于等于0,则不再进行反射计算。
参考引用
计算交点坐标
求反射向量
向量运算介绍