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



    前言:
      最近研究了box2dweb, 觉得自己编写Html5版台球游戏的时机已然成熟. 这也算是圆自己的一个愿望, 一个梦想.
      承接该序列的相关博文:
      • 台球游戏核心算法和AI(1) 
      同时结合html5的学习笔记:
      • box2dweb 学习笔记--sample讲解 
      这篇文章, 具体讲解台球游戏的box2d模型抽象, 并给出一个初步版本.

    演示:
      台球游戏的雏形如下所示:
      
      该台球游戏, 改编自box2dweb的demo程序, 可用鼠标拖动球来移动.
      代码的下载链接: http://pan.baidu.com/s/1sjzCwqD

    分析:
      让我们对台球游戏做个简单的物理抽象, 然后"庖丁解牛", 对每个组件结合box2d进行剖析.
      
      如图所示, 其抽象为6个球袋和6个边框构成, 球袋是球落入的目标, 边框则限定了台球的活动范围.
      • 边框抽象
      台球边框相对简单, 其可视为静态物体. 其物理形状就是一条边.

    // 设置为静态物体类型
    wallBodyDef.type = b2Body.b2_staticBody;
    
    // 采用多边形形状,然后SetAsEdge设置为边
    wallFixDef.shape = new b2PolygonShape;
    wallFixDef.shape.SetAsEdge(new b2Vec2(x1, y1), new b2Vec2(x2, y2));

      注: 边框转为box2d对象还是简单的.
      • 球袋抽象
      球袋本身也是静态物体, 但不同于边框, 其的box2d抽象, 多了点复杂和技巧.
      1). 感应设置
      球袋区域应为感应区, 球可以进入该区域, 但并不与之发生碰撞反应.
      可以通过设定定制器(Fixture)的isSensor属性为true来实现, 如下面代码所示:

    var holeFixtureDef = new b2FixtureDef;
    holeFixtureDef.shape = new b2CircleShape(0.5);
    holeFixtureDef.isSensor = true;

      注: 其特性为能感知碰撞不发生碰撞反应
      2). 落袋有效区域变换
      球袋和球的区域相交时, 并不代表球就进洞. 如下图所示:
      
      注: 红球刚好和球袋区域相交, 但红球重心并没有落入球袋的有效范围内.
      为了完美解决球进洞的逻辑判断, 我们有两种思路去解决.
      一种思路为: 从产生的碰撞接触对象b2Contact中, 计算两者的距离, 若两者圆心距离小于球袋半径, 则算进洞, 否则不算.
      另一种思路, 是做一个trick的技巧, 构造一个半径 = 球袋半径 - 球半径, 圆心依旧是球袋中心的圆, 并代替作为球袋的box2d物理模型. 该圆若与球相交, 则可以认为球重心落入球袋区域. 这可以免去前者的计算.
      
      注: 绿色的内部圆即是构造的球袋核心圆, 其外部的圆是物理表象的圆. 该场景为球和球袋相交, 但球重心和内部圆没有相交, 即重心没有落入球袋区域.
      环绕球袋本身的3/4圆, 则采用多边形来逼近模拟(样例采用16边形), 这也是防止球出有效区域(实际上这个可以忽略).
      • 球体放置
      我们都知道, 台球模拟, 最困难的往往是开球的时候. 一堆球挤在一起, 每个瞬间, 都有好多球彼此互相接触.
      球体的堆放其实是有技巧的, 摆放的球体不需要每个都紧挨着的, 可以适当的留些空隙. 如下所示:
      
      • 整体模拟
      由于采用垂直视角看台球桌面, 重力方向是指向内部. 创建世界对象时, 可简单设置gravity为零向量.

    var world = new b2World(new b2Vec2(0, 0), true)

      而台球桌面本身的摩擦阻力, 由于台球游戏在box2世界, 没有存在相关物理物体, 因此我们需要设置球的线速度减震来模拟台球桌摩擦阻力.

    ballBodyDef.linearDamping = 0.25;

      最终台球游戏整体的box2d物理模型, 对转换为如下图:
      
      • 进球处理
      球进球袋后, 需要消失, 可以理解为该球从box2d的物理世界中消除.
      对于碰撞反应, box2d提供了两种方式去处理.
      1). 注册ContactListener方式
      2). 遍历ContactList列表
      样例代码采用第二种方式, 原因如下:
      1). ContactListener的回调处于step的模拟过程中, box2d明确规定step模拟过程中, 不允许修改物理属性.
      2). 由于台球游戏的物体个数并不多, 因此遍历ContactList列表其性能是可接受的.

            /* 清除落入袋中球 */
            var contactList = world.GetContactList();
            for ( var contact = contactList; contact; contact = contact.GetNext() ) {
                if ( !contact.IsTouching() ) {   /* 接触只代表AABB重合 但不代表形体碰撞 */
                    continue;
                }
                var b1 = contact.GetFixtureA().GetBody();
                var b2 = contact.GetFixtureB().GetBody();
    
                if (b1.GetUserData() && b2.GetUserData()) {
                    if (b1.GetUserData() === BALL_TYPE.BG_HOLE_TYPE && b2.GetUserData() === BALL_TYPE.BG_BALL_TYPE ) {
                        world.DestroyBody(b2);
                    }
                    if ( b2.GetUserData() === BALL_TYPE.BG_HOLE_TYPE && b1.GetUserData() === BALL_TYPE.BG_BALL_TYPE ) {
                        world.DestroyBody(b1);
                    }
                }
            }

      注: 该处理代码在world.Step调用之后进行.

    总结:
      这边的demo图形是借助box2d的DrawDebug来渲染的. 下一步计划用漂亮的素材替换, 并完善台球的游戏规则. 虽然水平有限, 但感觉向前迈出了坚实的一步, 这种感觉挺好的.

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

       

  • 相关阅读:
    【Leetcode_easy】720. Longest Word in Dictionary
    【Leetcode_easy】717. 1-bit and 2-bit Characters
    【Leetcode_easy】709. To Lower Case
    【Leetcode_easy】707. Design Linked List
    【Leetcode_easy】706. Design HashMap
    第38课 栈和队列的相互转化
    第7章 网络层协议(4)_IGMP协议
    第7章 网络层协议(3)_ARP协议
    第33课 双向循环链表的实现
    第32课 Linux内核链表剖析
  • 原文地址:https://www.cnblogs.com/mumuxinfei/p/4502768.html
Copyright © 2020-2023  润新知