本文乃Siliphen原創,轉載請注明出處
目錄
游戲介紹
概述
游戲整體流程
游戲框架設計
主要流程控制類
本文項目的代碼組織結構
構建游戲世界
數字方塊
地圖
?觸摸手勢識別
防觸摸抖動
判斷用戶輸入的方向
地圖
任意大小的地圖
初始化地圖大小
地圖繪制
合并和移動
合并和移動的邏輯
絲滑的合并和移動動畫
本文的完整實現源碼工程
游戲介紹
《2048》是一款曾經風靡全球的數字益智游戲。
目前(2023.08.14)在 App Store 的情況如下圖:
關于這個游戲的更多情況可看看百度百科:百度百科-驗證
概述
本文講解用 Cocos Creator 實現經典《2048》的核心流程和算法。
Cocos Creator 版本:Cocos Creator 3.8.0
本文實現的游戲效果如下:
?
可以隨意調整地圖大小。可隨意調整方塊移動速度。
上圖分別演示了 4 x 4 ,7 x 10 地圖大小的效果。
可在這個地址運行體驗下本文實現的版本:Cocos Creator | 2048
文本末尾給出完整實現的源碼工程。
游戲整體流程
游戲執行一輪玩家操作的流程:等待玩家輸入操作 -> 用戶滑動屏幕 -> 移動數字方塊 -> 合并方塊 -> 空白地方隨機出現一個數字方塊 -> 等待玩家輸入操作
以上流程是游戲玩家操作一次,游戲執行一輪的分解動作循環。
游戲通關條件:合成數字方塊2048。
游戲失敗條件:當整個棋盤都填滿數字方塊,且沒有可以合并的方塊。
游戲框架設計
主要流程控制類
從調用先后順序開始依次如下:
類名 | 作用 |
UiTouch | 處理用戶觸摸輸入 |
Merge | 處理移動、合并的邏輯和動畫。 |
FlowRound.fillEntity() | 在地圖空白處隨機生成一個數字方塊。 |
FlowRound.judge() | 判斷輸贏。 |
本文項目的代碼組織結構
構建游戲世界
《2048》的游戲世界只有2個實體:數字方塊、棋盤地圖。
棋盤是數字方塊的容器。后面的移動和合并算法,都是作用在棋盤上計算的。
數字方塊
// 實體
export class Entity {// 實體所代表的數值public val : number ;// 表現public presentation = new EntityPresentation() ;}// 實體表現
export class EntityPresentation {public root : Node ;}
地圖
地圖數據本質是個二維數組。定義如下:
export class Map {// 單件public static ins : Map = null ;// 地圖單元格public grid = new Array< Array< MapCell > >() ;// 格子寬高public size = new Size() ; // 表現public presentation = new MapPresentation() ;}// 地圖表現
export class MapPresentation {// 根節點public root : Node ;}// 地圖單元格
export class MapCell {// 單元格上的實體public entity : Entity = null ;// 單元格所在的局部空間的坐標public pos : Vec3 = null ;}
我們規定地圖單元格(0,0)的位置在地圖顯示的左下角。x , y 的增長分別向右邊和上邊延伸。如下圖:
?觸摸手勢識別
防觸摸抖動
在觸摸按下時記錄按下的坐標,在觸摸結束時用結束時的坐標減去按下時的坐標,得到一個向量。
判斷這個向量的長度,大于某個數值后,就認為是有效的輸入。
如果只是個很小的滑動,可能是抖動造成的,為了防止玩家誤操作,可以丟棄這種輸入。
判斷用戶輸入的方向
用上一步減法得到的向量就可以判斷用戶操作的方向。
主要是用到 Math.atan2 這個系統函數。atan2 判斷一個向量與 x 軸正方向的夾角,單位是弧度。
注意:atan2 的參數是 ( y , x ) , 不是( x , y ),y 是第一個參數。
不習慣使用弧度的話,可以轉換成角度。
判斷角度代碼如下:
// 手勢識別
export class GestureRecognition {// 返回:上左下右 1234 從上開始順時針。0 無效方向public static exe( v : Vec2 ) : number {let rad = Math.atan2( v.y , v.x ) ;let degree = rad * ( 180 / Math.PI ) ;if( 45 < degree && degree < 135 ){return 1 ;} else if( -45 < degree && degree < 45 ){return 2 ;} else if( -135 < degree && degree < -45 ){return 3 ;}else if( 135 < degree && degree < 180 || -180 < degree && degree < -135 ){return 4 ;}// console.log( "度數:" + degree ) ;return 0 ;}}
地圖
任意大小的地圖
本文的實現,可設置任意地圖大小。
如下圖:
上圖展示了這些尺寸的地圖大小效果:3 x 3 , 5 x 5 , 6 x 4 , 7 x 10
不同地圖尺寸對應不同的地圖根結點縮放值。
要實現可指定任意大小的地圖的前提是,動態繪制地圖。
先初始化地圖二維數組結構的大小,然后,地圖繪制類再處理地圖的繪制。
初始化地圖大小
// 地圖數據初始化
export class MapInit {public static exe( map : Map ) {map.presentation.root = find( "Canvas/Map" ) ;// 地圖寬高let mapWidth = 3 ; let mapHeight = 3 ; map.size = new Size( mapWidth , mapHeight ) ;for( let y = 0 ; y < mapHeight ; ++y ) {let row = new Array< MapCell >( ) ;map.grid.push(row) ;for( let x = 0 ; x < mapWidth ; ++x ){let cell = new MapCell() ; row.push( cell ) ;} // end for} // end for}}
地圖繪制
本文實現的中心對其的地圖布局,地圖的幾何中心點與其父節點的原點重疊。
算法是,先算出整個地圖的大小,然后寬高分別除以2,先算出 ( 0 , 0 ) 起始邏輯坐標單元格的位置。
先算出左下角的起始單元格的位置,后續可以統一處理其他單元格位置。僅僅是通過不斷累加間隔就行。
地圖繪制 具體實現查看源碼工程的類 MapDraw
設置地圖大小位置:MapInit.exe 函數
合并和移動
這個是2048的核心玩法實現,也是最難的部分。
合并和移動的邏輯
可以先算方塊邏輯上的合并,后算方塊邏輯上的移動。
也可以合并和移動合并在一起計算。
上下左右的合并和移動要分別處理。
這里列舉用戶向左( <- )滑動的處理算法,其他3個方向的以此類推。為了說明原理和簡單起見以下為描述性偽代碼。
// 一行行遍歷地圖。從左到右(->)
for( let y = 0 ; y < map.size.height ; ++y ) {for( let x = 0 ; x < map.size.width ; ++x ) {let cell = map.grid[ y ][ x ] ;// 如果單元格上沒有實體,略過。因為我們只處理實體。不處理空格。if( cell.entity == null ) continue ;let cell2 = 向右(->)查找一個最近的實體所在的單元格。if( cell2 != null && cell2 和 cell 的實體數字相同 ){ // 這表示找到一個可以合并的實體2個實體合并,合并和的實體放在 cell 單元格的位置上。}以 cell 單元格為起點向左(<-)查找一個連續的空位的最右邊的那個空位這個空位便是 cell 上的方塊實體要移動到的位置。} // end for
} // end for
算法圖示:
絲滑的合并和移動動畫
如果只是按照上一步說的先在邏輯上計算合并和移動的結果,然后直接更新畫面顯示,會顯得很生硬。
大部分的瞬間更新結果都會讓畫面顯得生硬。好的做法是,有個滑動和合并的移動緩動動畫。
加入動畫后,流程就變成了:
所有的方塊都會先移動到一邊,然后進行合并,如果合并后留出了空位,需要再移動。保證移動后,中間不留空位。
這個流程需要對以上的邏輯處理進行改造。
具體實現查看源碼工程的類 Merge
本文的完整實現源碼工程
源碼工程下載地址:Cocos Store
作者創作不易,您的支持讓我創造出更多更好的作品。?:)
【 Cocos Creator 項目實戰】系列文章鏈接:
【 Cocos Creator 項目實戰】益智游戲《2048》
??????【Cocos Creator 項目實戰 】消滅星星加強版