台球游戲的核心算法和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來渲染的. 下一步計划用漂亮的素材替換, 並完善台球的游戲規則. 雖然水平有限, 但感覺向前邁出了堅實的一步, 這種感覺挺好的.

寫在最后:
  
如果你覺得這篇文章對你有幫助, 請小小打賞下. 其實我想試試, 看看寫博客能否給自己帶來一點小小的收益. 無論多少, 都是對樓主一種由衷的肯定.

   


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM