用 canvas 做桌面弹球游戏(一):2d 弹球发射、碰撞和击打

1,102 阅读4分钟

(原版游戏是 windows 经典游戏三维弹球,规则:zhuanlan.zhihu.com/p/44596341 。但是因为最近比较忙,只能一点一点实现了)

先放一张原版游戏的图:

image.png

目前实现效果:

20220905013703-convert.gif

20220905014947.gif

  1. 空格可以蓄力弹射弹球;
  2. 弹球遇到障碍物时,发生弹性碰撞;
  3. Z 键和 / 键可以翻动击打板击打小球;
  4. 小球落到画布外后自动复活;

todo:

  1. 增加轨道、减速板等;
  2. 暂停、分数等辅助功能;
  3. 闪烁等动态效果;

弹球发射、小球运动

20220904162423.gif

  1. 方向:正值代表向右、向下,负值代表向左、向上;
  2. 半径为 R_BALL 的小球在弹簧压缩后,从高度为 H 的直线轨道发出,进入半径为 R 的半圆轨道,在半圆轨道中受重力影响抛出;
  3. 第一次 keydown 时弹簧组件开始计时并匀速压缩(缩小高度),keyup 时计时完毕,lauch.end() 返回起始速度 vy0
  4. 弹簧、小球均以 vy0 匀速恢复至原始高度;
  5. 弹簧回弹至初始高度后,小球以初始速度 vy0 发射,后续过程能量守恒;
  6. 如果计时等于或超过 2s,则弹簧压缩到最大值,小球起始速度达到最大值 MAX_VY0,MAX_VY0 恰好能使小球在相对于半圆轨道圆形 (2 / 3) R 的高度处抛出,其他情况下 vy0 = (lauchTime / 2000) * MAX_VY0;
  7. 计算小球在抛出时能达到的的最大高度 h,h = -(1 / 3) * ((vy0 * vy0 / g) + H),所以 MAX_VY0 * MAX_VY0 = 2 * g(H + R);
  8. 运动过程始终满足:x += vx * time, y += vy * time;
  9. 能量守恒过程中始终满足:v2 += 2 * g * vy * time;
  10. 小球不在半圆轨道上运动,为落体运动:vx = vx, vy += g * time;
  11. 小球在半圆轨道上运动,为圆周运动,(xr, yr) 为相对于圆心的位置:vx = (yr / R) * sqrt(v2), vy = (-xr / R) * sqrt(v2), 几何关系得, xr = x + R, yr = y + H;
  12. 小球不满足圆周运动条件时抛出,抛出条件:v2 / R + g * ((y + H) / R) <= 0;
  13. 更新小球位置:ctx.arc(x, y, R_BALL, 0, 2 * Math.PI);

碰撞检测

20220905000603.gif

形状检测

  1. 记录所有障碍物形状,每次更新时遍历障碍物,检测是否发生碰撞,如发生碰撞则反弹;
  2. 圆形障碍物:
    1. 依据圆心坐标和半径绘制;
    2. 如果障碍物圆心和小球圆心距离小于两者半径之和 + 小球速度方向朝向障碍物圆心(根据速度和(障碍物圆心 -> 小球圆心)向量之间夹角是否为钝角判断),则发生碰撞;
    3. 碰撞法线方向为(障碍物圆心 -> 小球圆心);
  3. 线段障碍物
    1. 依据两端点距离绘制;
    2. 如果线段所在直线和小球圆心距离小于小球半径 + 小球圆心的投影点在线段内(根据端点 + 圆心不构成钝角三角形判断)+ 小球速度方向朝向线段(线段按照障碍物轮廓顺时针绘制,根据速度和线段向量叉乘正负性判断),则发生碰撞;
    3. 碰撞法线方向为线段法向;
  4. 半圆弧轨道障碍物(属于特化,不太好,但是任意圆弧判断是否碰撞比较难处理)
    1. 小球与半圆弧轨道圆心距离大于两者半径之差 + 小球位置大于圆弧圆心位置 + 小球的速度方向背向障碍物圆心,则发生碰撞;
  5. 碰撞后速度变化:
    1. 计算速度向量在法线方向的投影向量;
    2. 2 * 投影向量 - 速度向量,即为所求值;

image.png

像素检测(一个想法,没有实现)

  1. 根据小球位置,检验小球轮廓的像素是否有其他图形像素存在,圆形障碍物 rgb 为 (0, 圆心坐标 xc, 圆心坐标 yc),可以根据圆心坐标 + 碰撞点坐标计算碰撞的法线,线段障碍物 rgb 为 (1, 法线 x 分量,法线 y 分量);
  2. 这样直接可以从像素中获得足够的信息,计算小球碰撞后如何改变运动状态了,所有障碍物只需要作为背景绘制一次、不用维护信息了;
  3. 但是这种方案圆心坐标只能在 0-256 之间,圆的像素是否也能直接存储法线信息?画一个五彩斑斓的圆?

击打

20220905013307-convert.gif

  1. 击打实质也是小球与线段障碍物碰撞;
  2. keydown 后 1s 内,击打板向上旋转,y 方向上匀速,到水平位置后又落下;
  3. 击打板障碍物:
    1. 依据两端点距离、和中止角度绘制;
    2. 根据两端点位置计算初始角度;