JavaScript 子弹跟踪算法实现

1,874 阅读5分钟

前言

最近突发奇想的想实现一个使用由 Canvas 技术实现的塔防游戏,其中游戏玩法主要为怪物从起点出生,在其抵达终点之前,玩家可以通过消耗金币摆放/升级 道具来阻止或击败怪物。
而当怪物进入道具的攻击范围时,道具的枪口将对着怪物的方向,并且朝其方向发射子弹。


一、实例截图

image.png


二、功能分析

在实现这个 子弹跟踪算法 前,我们首先需要明白几件事。

  1. 子弹的速度是否会因怪物离道具的距离而发生变化?
  2. 子弹需要如何移动?
  3. 如何取得子弹的移动方向?

好了,在提出了这三个问题后,我们就可以来仔细思考一下这三个问题的答案了。

那么,首先是第一个问题。

子弹的速度是否会因怪物离道具的距离而发生变化?

这个问题其实很简单。

我们仔细想想,在我们周围生活中可以看到过的塔防游戏里,是否有子弹无论是从近到远都是一秒钟到的游戏?

很显然,几乎没有,又或者说很少见。

其次是第二个问题。

子弹需要如何移动?

这里我们可以看看上面那个实例图(下面也画了个草图)。

image.png

从图里我们可以得知,怪物在道具的左上角。

也就是说,怪物的 x 值与 y 值均小于 道具的 x值与y值。

又因子弹的初始位置为道具的中心点,所以当我们的子弹想要朝着左上角移动时,子弹的 x 与 y 均 加上负值即可朝着左上角移动。

同理可得
x = 0, y = -1 则朝着正上方移动
x = 0, y = 1 则朝着正下方移动
x = -1, y = 0 则朝着正左方移动
x = 1, y = 0 则朝着正右方移动
x = 1, y = 1 则朝着右下方移动
x = 1, y = -1 则朝着右上方移动
x = -1, y = 1 则朝着左下方移动

最后我们思考一下第三个问题,也是最重要,最困难的问题。

如何取得子弹的移动方向?

上面我们知道了 子弹 的 x 与 y 轴的速度值会影响 子弹发射的方向。

也许聪明的朋友会想到我们去对比 怪物 与 道具的 x与y 的坐标,然后得出 x 或 y 的值是应该为 1,还是 -1 还是0。

这么想其实没错,但也不对。

因为如果我们只是单纯的取 1,-1, 0的话会造成什么影响?

那样的话,子弹就只会朝着 0°,45°,90°,135°等角度移动了。

但很显然,怪物与道具之间的角度绝不会只有这几个固定值。

所以我们这个时候可以用到某个数学定理了。


三、勾股定理

我们来看看这张图。

image.png

这个图里,怪物与道具的位置显然可以构成一个直角三角形。

而在数学里,是可以通过勾股定理来求出直角三角形的斜角边的。

image.png

不过说到这可能有的朋友会困惑,我们为什么要算斜边?算了斜边对子弹发射角度有什么帮助吗? 这里咱暂时不用急,讲到后面咱就会明白为什么了。

在上面那个图中,我们可以得知 a值 为怪物与道具的 y 轴的 绝对差值, b值为 为怪物与道具的 x 轴的绝对差值。

那么我们就假设怪物的 x 为 10,y 为 10。

道具的 x 为 30, y 为 20。

那么我们就可以得到斜角的边

let x1 = 10,
    x2 = 30, 
    y1 = 10,
    y2 = 20,
    c = Math.sqrt(Math.pow(Math.abs(x1 - x2), 2) + Math.pow(Math.abs(y1 - y2),2));// 22.360679774997898

此时我们得到了斜角边后,我们就可以声明一个值,即子弹的移动速度,这样是为了保证子弹无论离怪物的距离是否远近,速度都是不会变化的。

const speed = 5;

这里我们声明子弹的速度的变量,并为其赋值5。其意义为,子弹在每帧移动5像素。

随后我们用我们得到的斜角边除以子弹的速度,就可以得到子弹在多少帧后抵达该斜角边的最末端的位置。

let hy = c / speed;// 4.47213595499958

这里我们可以看到,在4.47213595499958 帧以后,我们的子弹将抵达该斜边的最末端的位置。

也就是说,我们只需要用 怪物与道具的 x与y 的绝对差值去除以这个帧数,就可以得到子弹每一帧的 x与y 的移动距离。

(不过在这里我们需要取反,不然的话子弹只会朝着相反的方向移动)

let x = -((x1 - x2) / hy),
    y = -((y1 - y2) / hy);

最后,整个算法如下。

const speed = 5;

function get_move_speed(m, t) {
    let m_x = m.x + m.speed.x,
        m_y = m.y + m.speed.y,
        abs_x = Math.abs(t.x - m_x),
        abs_y = Math.abs(t.y - m_y),
        hy = Math.sqrt(Math.pow(abs_x, 2) + Math.pow(abs_y, 2)) / speed,
        x = -((t.x - m_x) / hy),
        y = -((t.y - m_y) / hy);
        
        // 注意,这里 m 对象的坐标和 t对象的坐标 均是怪物与道具的中心点
        
   return {x,y}
}

PS: 最后这里是做了点预判,因为怪物是会移动的,如果不做预判,很容易造成在子弹移动到怪物原来的位置后,怪物已经离开了。
也为了避免被当成标题党,这里也说明一下,如果想要子弹跟着怪物移动的话,其实每过几帧再调用这个方法就行了。

最后的最后,如果大家觉得这篇文章对你有所帮助的话,还请点个赞支持支持一下拉!

(下一期或者下下期更新这个塔防游戏的如何实现~)。