前言:
  08年的时候, 写过一个台球游戏, 用的是java, 不过代码真的是用传说中的神器notepad写的(你信吗? 其实是用GVIM写的, ^_^), 很多类都在同一java文件中编写. 可见当时的JAVA水平真的不咋地, 时过进迁, 还是一样的不咋地.
  这边是当时的CSDN下载链接: java(台球游戏), 实现比较简单. 后来写过一个版本, 比这个要强大许多, 可惜源码丢失了.
  效果展示入下图所示:
  
  本文想讲述下台球游戏中核心算法的实现, 以及游戏AI的设计技巧. 当然自己也有个小愿望, 希望能实现一个html5版的台球游戏.

基础物理知识:
  • 摩擦阻力
  其满足牛顿第二定律:
  f = m * a
  速度与加速度关系公式:
  vt = v0 + a * t
  地面摩擦力与运动物体的方向相反, 阻碍物体的向前运动.
  • 动量守恒
  假设物体A质量为m1, 速度为v1, 物体B质量为m2, 速度为v2, 碰撞后速度分别为v1', v2'.
  则满足动量守恒定律:
  m1 * v1 + m2 * v2 = m1 * v1' + m2 * v2'
  

  • 碰撞类型和能量守恒定律
  1). 完全弹性碰撞
  动能没有损失, 则满足如下公式:
  1/2 * m1 * v1^2 + 1/2 * m2 * v2^2 = 1/2 * m1 * v1'^2 + 1/2 * m2 * v2'^2
  注: 前后物体的动能保持均衡, 没有其他能量的转化.
  结合之前的动量守恒定律, 我们可以进一步得到:
  v1' = [(m1-m2) * v1 + 2 * m2 * v2] / (m1 + m2)
  v2' = [(m2-m1) * v2 + 2 * m1 * v1] / (m1 + m2)
  2). 完全非弹性碰撞
  则存在其他能量的转化, 动能不守恒.
  且此时两物体粘连, 速度一致, 即v1'=v2', 此时动能损失最大.
  3). 弹性碰撞
  介于完全弹性碰撞和完全非弹性碰撞两者之间. 动能有损失的.

物理模型:
  台球游戏中, 最核心的就是其物理模型的抽象及其碰撞算法的执行过程了.
  鉴于是2D版的台球游戏, 因此我们对物理模型做下简化, 球运动的方向必然穿越球的中心.
  把每个台球抽象为圆(x, y, radius), 而台球桌边框抽象为线段((x1, y1), (x2, y2)).
  • 碰撞检测
  1). 检测球与球碰撞
  我们假定球A(x1, y1, r), 球B(x2, y2, r). 则满足条件:
  (x1 - x2) ^ 2 + (y1 - y2) ^ 2 <= (2*r) ^ 2
  则发生碰撞, 否则没有发生碰撞
  2). 检测球与球台边框碰撞
  相对比较简单. 求球心到边框的垂直距离即可, 若小于等于则发生碰撞, 若大于则没有.
  • 碰撞反应
  1). 球与球的碰撞反应
  
  动量是向量, 其在正交的两个方向上, 互相守恒. 我们选取两球圆心的直线为x轴, 垂直于圆心直线的为y轴. 如上图所述.
  x轴上满足动量守恒:
  m1 * Vx + m2 * Ux = m1 * Vx' + m2 * Ux';
  并假定两球碰撞是完全弹性碰撞, 两球质量相等m1=m2, 依据基础物理知识篇的结论.
  Vx' = [(m1-m2) * Vx + 2 * m2 * Ux] / (m1 + m2) = Ux;
  Ux' = [(m2-m1) * Ux + 2 * m1 * Vx] / (m1 + m2) = Vx;
  在X轴方向, 两球交换速度, 而在Y轴方向, 两球分速度不变.
  Vy' = Vy;
  Uy' = Uy;
  最终碰撞后的速度公式为:
  V' = Vx' + Vy' = Ux + Vy;
  U' = Ux' + Uy' = Vx + Uy;
  2). 球与边框的碰撞反应
  把台球边框视为质量无穷大, 则简单把运动的球, 其在垂直边框的分方向反向即可.
  
  假定碰撞碰撞平面为x轴
  Vx' = Vx;
  Vy' = -Vy;
  最终速度公式为:
  V' = Vx' + Vy' = Vx - Vy;

碰撞执行算法:
  游戏的主循环往往遵循如下代码结构:

    while ( true ) {
game.update(time_interval);
game.render();
}

  这个时间间隔(time_interval), 由游戏的FPS来确定. 以24帧为例, 每40毫秒刷新一次.
  对于台球本身而言, 若以该time_interval为更新周期, 使得运动的球体满足:
  Vt = V0 + a * t
  运行距离为:
  S = V0 * t + 1/2 * a * t^2.
  然后来检测球体是否发生了碰撞, 然后进行碰撞反应处理. 看似没有问题.
  但是当球体初速度很快时, 在time_interval中有可能, 发生穿越现象.
  如下图所展示的现象:
  
  紫色球在t2时刻, 和蓝球检测到碰撞, 但实际上, 在紫球在t1~t2之间的某时刻和蓝球发生了碰撞.
  为了解决该问题, 在具体的算法中, 需要引入更细的时间分片slice, 该过程在具体的update中进行模拟.
  整个台球场景的更新函数:

void update(time_interval) {

	while time_interval > 0:
// 碰撞检测
if detectionCollide(time_interval, least_time, ball_pairs):
// 游戏更新least_time
billiards.update(least_time)
// 对碰撞的两球进行碰撞反应
collideReaction(ball_pairs=>(ball, other))
// time_interval 减少 least_time
time_interval -= least_time
else:
// 游戏更新least_time
billiards.update(time_interval)
time_interval = 0 }

  注: 碰撞反应, 按物理模型篇讲述的来.
  而具体的碰撞检测算法为:

/*
@brief
在time_interval 时间内, 返回最先碰撞的球或台球边, 以及时间点
*/
bool detectionCollide(time_interval, least_time, ball_pairs) { res = false;
least_time = time_interval; foreach ball in billiards:
foreach otherBall in billiards:
// 求出两球的距离
S = distance(ball, otherBall)
// 以某一球作为参考坐标系, 则令一球速度向量变为 U’=U-V
// 在圆心的直线作为x轴
Ux(relative) = Ux(other ball) - Vx(ball)
// 若该方向使得两球远离, 则直接忽略
if Ux(relative) < 0:
continue
// 某该方向使得两球接近, 则可求其碰撞的预期时间点
A' = 2 * A; // 加速度为原来的两倍 // 取两者最小的时间点
delta_time = min(time_interval, Ux(relative) / Ax’)
// 预期距离 小于 两球距离,则在time_interval中不会发生碰撞
if 1/2 * Ax’ * delta_time ^ 2 + Ux(relative) * delta_time < S - 2*r:
continue // 解一元二次方程, 使用二分搜索逼近求解
res_time <= slove(1/2 * Ax’ * x ^ 2 + Ux(relative) * x = S - 2 * r) if res_time < least_time:
ball_pairs <= (ball, otherBall)
least_time = res_time
res = true foreach wall in billiards:
S = distance(ball, wall)
// 设垂直于平面的方向为x轴
if Vx < 0:
continue // 取两者最小的时间点
delta_time = min(time_interval, Vx / Ax)
// 预期距离 小于 两球距离,则在time_interval中不会发生碰撞
if 1/2 * Ax * delta_time ^ 2 + Vx * delta_time < S - r:
continue // 解一元二次方程, 使用二分搜索逼近求解
res_time <= slove(1/2 * A * x ^ 2 + Vx * x = S - r) if res_time < least_time:
ball_pairs <= (ball, walll)
least_time = res_time
res = true return res }

  注: 对于一元二次方程, 也可以借助分1000个细粒度时间片, 然后计算逼近求解.
  台球模拟碰撞算法过程, 大致就是如上所述.
  计算最复杂的时刻, 其实就是开球, 打散一堆球的时候.

总结:
  本文参考了"NEHE的OPENGL中文教程 第30课 碰撞检测与模型运动". 当然实现台球游戏, 未必真的需要该算法, 很多开发者直接使用box2d就能完美并轻松的实现. 参考"使用 cocos2d-x Box2d 的实现". 后续的文章, 想讲述下台球游戏的AI如何设计和实现. 望一同努力.

写在最后:
  
如果你觉得这篇文章对你有帮助, 请小小打赏下. 其实我想试试, 看看写博客能否给自己带来一点小小的收益. 无论多少, 都是对楼主一种由衷的肯定.

  

台球游戏的核心算法和AI(1)的更多相关文章

  1. 台球游戏的核心算法和AI(2)

    前言: 最近研究了box2dweb, 觉得自己编写Html5版台球游戏的时机已然成熟. 这也算是圆自己的一个愿望, 一个梦想. 承接该序列的相关博文: • 台球游戏核心算法和AI(1) 同时结合htm ...

  2. Prim算法和Kruskal算法

       Prim算法和Kruskal算法都能从连通图找出最小生成树.区别在于Prim算法是以某个顶点出发挨个找,而Kruskal是先排序边,每次选出最短距离的边再找. 一.Prim(普里姆算法)算法: ...

  3. 转:极小极大搜索方法、负值最大算法和Alpha-Beta搜索方法

    转自:极小极大搜索方法.负值最大算法和Alpha-Beta搜索方法 1. 极小极大搜索方法    一般应用在博弈搜索中,比如:围棋,五子棋,象棋等.结果有三种可能:胜利.失败和平局.暴力搜索,如果想通 ...

  4. 字符串匹配-BF算法和KMP算法

    声明:图片及内容基于https://www.bilibili.com/video/av95949609 BF算法 原理分析 Brute Force 暴力算法 用来在主串中查找模式串是否存以及出现位置 ...

  5. BF算法和KMP算法

    这两天复习数据结构(严蔚敏版),记录第四章串中的两个重要算法,BF算法和KMP算法,博主主要学习Java,所以分析采用Java语言,后面会补上C语言的实现过程. 1.Brute-Force算法(暴力法 ...

  6. 最小生成树---Prim算法和Kruskal算法

    Prim算法 1.概览 普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树.意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (gra ...

  7. js实现web网页版台球游戏

    js桌球小游戏在线试玩地址:http://keleyi.com/game/13/ 游戏截图: 完整代码,保存到html文件可以试玩: <!DOCTYPE html PUBLIC "-/ ...

  8. 经典算法和OJ网站(开发者必备-转)

    一. Online Judge简介: Online Judge系统(简称OJ)是一个在线的判题系统.用户可以在线提交程序多种程序(如C.C++.Pascal)源代码,系统对源代码进行编译和执行,并通过 ...

  9. BM算法和Sunday快速字符串匹配算法

    BM算法研究了很久了,说实话BM算法的资料还是比较少的,之前找了个资料看了,还是觉得有点生涩难懂,找了篇更好的和算法更好的,总算是把BM算法搞懂了. 1977年,Robert S.Boyer和J St ...

随机推荐

  1. WordPress搬家教程:换空间与换域名

    WordPress搬家教程:换空间与换域名 由于本人博客空间8月份已到期,便新购一个虚拟主机想进行WordPress搬家,于是特意在网上查了些WordPress搬家教程,进行了综合总结,并结合这次实操 ...

  2. 一个webpack,react,less,es6的DEMO

    1.package.json如下 { "name": "demo", "version": "1.0.0", " ...

  3. CentOS 7系统挂载NTFS分区的移动硬盘(转载及体验 CentOS6.5系统挂载NTFS分区的移动硬盘)

    作为IT的工作者,避免不了使用Linux系统,我比较喜欢CentOS,为了锻炼自己对CentOS的熟练操作,就把自己的笔记本装了CentOS,强制自己使用,使自己在平时的工作中逐渐掌握Linux的学习 ...

  4. JSPServlet精华笔记

    一.     JSP (Java Server Pages) JSP是指: ▶    在HTML中嵌入Java脚本代码 ▶    由应用服务器中的JSP引擎来编译和执行嵌入的Java脚本代码 ▶    ...

  5. ReentRantLock使用

    synchronized原语和ReentrantLock在一般情况下没有什么区别,但是在非常复杂的同步应用中,请考虑使用ReentrantLock,特别是遇到下面2种需求的时候. 1.某个线程在等待一 ...

  6. #pragma once与 #ifndef的区别

      为了避免同一个文件被include多次 1   #ifndef方式2   #pragma once方式 在能够支持这两种方式的编译器上,二者并没有太大的区别,但是两者仍然还是有一些细微的区别.   ...

  7. python内置函数 2

    import__( name[, globals[, locals[, fromlist[, level]]]])被 import 语句调用的函数. 它的存在主要是为了你可以用另外一个有兼容接口的函数 ...

  8. CENTOS修改主机名

    1.临时修改主机名 显示主机名: zhouhh@zzhh64:~$ hostname zhh64 修改主机名: zhouhh@zzhh64:~$ sudo hostname zzofs zhouhh@ ...

  9. jfinal 基本应用 --定时任务 QuartzPlugin

    jfinal 的定时器的使用: 项目中使用的maven管理器 1.导入要使用的包 2.添加Job类 配置参数 这个配置是jfinal-quartz 包中带的默认文档,即是默认加载的文档(其中还有一个q ...

  10. 跟着视频做的SSH项目总结

    一直没做过SSH(Struts2+Spring+Hibernate)的实际项目,只是三个框架学的还熟练,但整合起来使用就不知道了.所以前段时间在网上找了一套SSH实际项目的视频来学习(确切的说是买的. ...