首页 > 编程 > JS > 正文

JS小游戏实例:2D桌面台球

2020-09-19 10:58:00
字体:
来源:转载
供稿:网友

demo: http://cnwander.com/demo/billiards/
原文地址:http://cnwander.com/blog/?p=11

先贴上代码:

运行代码框

[ctrl+a 全部选择 提示:你可先修改部分代码,再按运行]

虽厚颜冠名桌球,其实与真实桌球还相差甚远,还有太多需要改进的地方。
具体待解决的问题:

  1. 由于浏览器上刷新频率不可能太高,可能当检测两球距离时,两球已经重叠了大部分,甚至完全越过。
    完全越过的情况先不考虑,重叠部分如果还原到精确的相切状态,运行非常缓慢,所以我只采用了计算量较少的近似值,具体问题主要体现在开球时,多球碰撞时有些诡异。
    (如果哪位有好的优化计算方法,可以拿出来与wander分享,那wander真的感激不尽)
  2. 球自身滚动与桌面摩擦力问题。如正中击球的瞬间,母球滑行状态的摩擦力会大于向前滚动时的摩擦力,小于缩杆时摩擦力等等。这个问题好解决,只是刚开始没考虑进去,之后也没添加了,具体体现在两球正撞后,撞击球将完全静止,这是不正确的。
  3. 能量损耗问题,无论与边沿碰撞还是球与球相撞,都是直接减去一个固定值,这肯定是存在很大问题的。

其它肯定还会有很多问题,担心假期把心玩油了,回头没心思继续,干脆一气呵成,赶得有些匆忙,问题回头再慢慢解决吧,先发上来,对这块有兴趣的同学一块儿探讨探讨。

大学数学基本是过场,高中的物理数学与忘得所剩无几,真正做东西才发现自己这块太薄弱,希望在这方面经验比较丰富的同学不吝赐教。

|||

贴出一些关键代码稍作解释,有兴趣的同学看看。

// ball class
function ball(type,x,y) {
    ...
    this.type = type;
    this.x = x; //位置
    this.y = y;
    this.angle = 0; //角度
    this.v = 0; //速度(不包含方向)
    ...
    return this;
}
描述小球的四个信息,小球的类型(母球,目标球),坐标,角度,速度
在更新坐标时,读取小球的v,刷新小球的位置
在与边沿碰撞时,更改小球angle

var formpos = getballpos(cueball.elem),
      topos = getballpos(guideball),
      angle = math.atan2(topos[0] - formpos[0],topos[1] - formpos[1]);
计算母球,与参考球之间的角度,其它任意小球之间也是如此
值得注意的是我采用的是math.atan2,而非math.atan,这是因为math.atan2返回的是(0 - math.pi)和(-math.pi - 0),可以确定唯一的角度,而math.atan不唯一。

//边缘碰撞
if(ball.x < r || ball.x > w - r) {
ball.angle *= -1;
ball.v = ball.v * (1 - loss);
...
if(ball.type == "cue")    {
    if(ball.angle > 0) vy -= rollright;
    else vy += rollright;
    vx += rollup;
    rollup *= 0.2;
    rollright *= 0.2;
    ball.v = math.sqrt(vx*vx + vy*vy);
    ball.angle = math.atan2(vx,vy);
}
...
if(ball.y < r || ball.y > h - r) {
ball.angle = ball.angle > 0 ? math.pi - ball.angle : - math.pi - ball.angle ;
...
不考虑小球旋转时,边缘碰撞很简单,更改小球angle即可
当小球旋转时,如果碰到固定不动的物体时,那将会把速度作用给自身
并且自身旋转速度减小
我这里计算球与球之间碰撞,并没有将旋转传递,这是不准确的,有待改善

这一段是核心,即小球与小球碰撞后各自的速度,其实并不难
先判断两球距离
var dis = math.sqrt(math.pow(disx,2)+math.pow(disy,2));
if(dis <= gap) {
    //如果目标球是静止的,则添加到数组movingballs
    if(math.round(obj.v) == 0)   
    movingballs.push(obj);
   
    //还原两球相切状态,用其它方式做我不知道,但用js来做,这一步相当关键,否则误差将相当大
    ball.x -= (gap - dis)*sin;
    ball.y -= (gap - dis)*cos;
    disx = obj.x - ball.x;
    disy = obj.y - ball.y;
   
    // 下面则是先将整个坐标系旋转到相撞的水平方向
    // 计算角度和正余弦值
    var angle = math.atan2(disy, disx),
        hitsin = math.sin(angle),
        hitcos = math.cos(angle),
        objvx = obj.v * math.sin(obj.angle),
        objvy = obj.v * math.cos(obj.angle);
        //trace(angle*180/math.pi);
       
    // 旋转坐标
    var x1 = 0,
        y1 = 0,
        x2 = disx * hitcos + disy * hitsin,
        y2 = disy * hitcos - disx * hitsin,
        vx1 = vx * hitcos + vy * hitsin,
        vy1 = vy * hitcos - vx * hitsin,
        vx2 = objvx * hitcos + objvy * hitsin,
        vy2 = objvy * hitcos - objvx * hitsin;
   
    // 碰撞后的速度和位置
    var plusvx = vx1 - vx2;
    vx1 = vx2;
    vx2 = plusvx + vx1;
   
    //母球加塞
    if(ball.type == "cue")    {
        vx1 += rollup;
        rollup *= 0.2;
    }               
   
    x1 += vx1;
    x2 += vx2;
   
    // 将位置旋转回来
    var x1final = x1 * hitcos - y1 * hitsin,
        y1final = y1 * hitcos + x1 * hitsin,
        x2final = x2 * hitcos - y2 * hitsin,
        y2final = y2 * hitcos + x2 * hitsin;
    obj.x = ball.x + x2final;
    obj.y = ball.y + y2final;
    ball.x = ball.x + x1final;
    ball.y = ball.y + y1final;
   
    // 将速度旋转回来
    vx = vx1 * hitcos - vy1 * hitsin;
    vy = vy1 * hitcos + vx1 * hitsin;
    objvx = vx2 * hitcos - vy2 * hitsin;
    objvy = vy2 * hitcos + vx2 * hitsin;
   
    //最终速度
    ball.v = math.sqrt(vx*vx + vy*vy) * (1 - 0);
    obj.v = math.sqrt(objvx*objvx + objvy*objvy) * (1 - 0);
   
    // 计算角度
    ball.angle = math.atan2(vx , vy);
    obj.angle = math.atan2(objvx , objvy);
}

  • 坐标旋转的公式
    x1 = math.cos(angle) * x - math.sin(angle) * y;
    y1 = math.cos(angle) * y + math.sin(angle) * x;
  • 反坐标旋转
    x1 = math.cos(angle) * x + math.sin(angle) * y;
    y1 = math.cos(angle) * y - math.sin(angle) * x;

不一定用坐标旋转,我只是习惯了用这种方式来计算碰撞,只要将正向碰撞的速度相差即可

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表