近期出現一款魔性的消除類HTML5游戲《神奇的六邊形》,今天我們一起來看看如何通過開源免費的青瓷引擎(www.zuoyouxi.com)來實現這款游戲。
(點擊圖片可進入游戲體驗)
因內容太多,為方便大家閱讀,所以分成四部分來講解。
本文為第二部分,主要包括:
6. 歷史最高分顯示
7. 當前分數顯示
8. 繪制棋盤
9. 形狀池設計與實現
10. 形狀預制的實現
若要一次性查看所有文檔,也可點擊這里。
六. 歷史最高分顯示
對於DOM節點,其實就是個div,可以指定其樣式,指定其樣式表類名,也可以內嵌html元素。
-
在Scripts/ui下新建文件:BestScore.js,處理最高分數的顯示邏輯,並將此邏輯腳本掛載到UIRoot/best節點上。
1 /** 2 * 繪制最高分數 3 */ 4 var BestScore = qc.defineBehaviour('qc.tetris.BestScore', qc.Behaviour, function() { 5 var self = this; 6 self.runInEditor = true; 7 }, { 8 }); 9 10 /** 11 * 初始化處理 12 */ 13 BestScore.prototype.awake = function() { 14 var self = this, div = self.gameObject.div; 15 div.className = 'score_best'; 16 self.setScore(qc.Tetris.score.current); 17 }; 18 19 /** 20 * 更新最新的高分 21 */ 22 BestScore.prototype.setScore = function(best) { 23 this.gameObject.div.innerHTML = 'Best: ' + best; 24 };
本腳本可以在編輯器下運行(方便在編輯狀態下查看效果),首先設置DOM的樣式表類名為score_best,然后獲取最高分數並顯示之。
2. 增加score_best樣式表。打開Assets/css/style.css,添加樣式表:
1 .score_best { 2 font-weight: 60; 3 font-size:30px; 4 color: #ffffff; text-align: right; 5 }
3.刷新下頁面,查看效果:
4. 注意:更改樣式表目前需要刷新頁面才能看到效果。
七. 當前分數顯示
1. 在Scripts/ui下新建文件:CurrentScore.js,處理當前分的顯示邏輯,並將此邏輯腳本掛載到UIRoot/score節點上。
1 /** 2 * 繪制當前分數 3 */ 4 var CurrentScore = qc.defineBehaviour('qc.tetris.CurrentScore', qc.Behaviour, function() { 5 var self = this; 6 self.runInEditor = true; 7 }, { 8 }); 9 10 /** 11 * 初始化處理 12 */ 13 CurrentScore.prototype.awake = function() { 14 var self = this, div = self.gameObject.div; 15 16 div.className = 'score_current'; 17 self.setScore(qc.Tetris.score.current); 18 }; 19 20 /** 21 * 更新最新的分數 22 */ 23 CurrentScore.prototype.setScore = function(best) { 24 this.gameObject.div.innerHTML = '' + qc.Tetris.score.current; 25 };
本腳本可以在編輯器下運行(方便在編輯狀態下查看效果),首先設置DOM的樣式表類名為score_current,然后獲取當前分數並顯示之。
2.增加score_current樣式表。打開Assets/css/style.css,添加樣式表:
1 .score_current{ 2 color: #ffffff; 3 font-weight: 100; 4 font-size:50px; 5 text-align: center; 6 }
3.刷新下頁面,查看效果
八. 繪制棋盤
DOM的層次
DOM節點可以繪制在最底層(當工程背景設置為非透明時,將不可見),也可以繪制在最上層(在所有精靈、UIImage等節點之上)。
本棋盤放在最底層,因為棋盤上面還需要拖拽形狀、顯示可放入指示等。方法是:選中board節點,在Inspector中設置Pos=BACK:
開始繪制棋盤
-
棋盤中的每個格子都作為一個div元素,采用絕對定位。
格子可能表現為7種圖片形式(空時為灰色,其他為形狀的對應顏色),這7種圖片通過不同的樣式類來區分。 -
打開Assets/css/style.css,加入如下幾個樣式表:
1 .board { width: 61px; height: 67px; position: absolute; background-repeat: no-repeat; } 2 .board_gray { 3 background-image: url("../raw/gray.png"); 4 } 5 .board_blue { 6 background-image: url("../raw/blue.png"); 7 } 8 .board_cyan { 9 background-image: url("../raw/cyan.png"); 10 } 11 .board_green { 12 background-image: url("../raw/green.png"); 13 } 14 .board_lightyellow { 15 background-image: url("../raw/lightyellow.png"); 16 } 17 .board_red { 18 background-image: url("../raw/red.png"); 19 } 20 .board_yellow { 21 background-image: url("../raw/yellow.png"); 22 }
這些樣式表指明了棋盤格子中的圖片
3. 編輯Tetris.js,加入IMAGES常量,指明在不同的值下面格子所使用的className
1 window.Tetris = qc.Tetris = { 2 // 棋盤的大小(半徑) 3 SIZE: 4, 4 5 // 棋盤中,每個格子的寬度和高度 6 BLOCK_W: 61, 7 BLOCK_H: 67, 8 9 // 所有的格子圖片 10 IMAGES: [ 11 'gray', // 0 12 'blue', // 1 13 'cyan', // 2 14 'green', // 3 15 'lightyellow', // 4 16 'red', // 5 17 'yellow' // 6 18 ], 19 20 // 所有的操作指令集合 21 operation: {} 22 };
4. 在Scripts/ui下創建腳本BoardUI.js,負責棋盤的繪制邏輯:
1 var s = qc.Serializer; 2 3 /** 4 * 管理棋盤的數據並繪制棋盤 5 */ 6 var BoardUI = qc.defineBehaviour('qc.tetris.BoardUI', qc.Behaviour, function() { 7 var self = this; 8 9 /** 10 * 棋盤的棋子元素 11 */ 12 self.pieces = {}; 13 14 // 本腳本在編輯模式下可以運行 15 self.runInEditor = true; 16 }, { 17 linePrefab: s.PREFAB 18 }); 19 20 /** 21 * 初始化處理 22 */ 23 BoardUI.prototype.awake = function() { 24 var self = this; 25 self.reset(); 26 27 // 立刻重繪制下 28 self.redraw(); 29 30 // 設置游戲畫布的背景色 31 self.gameObject.div.parentNode.style.background = '#1F1E1E'; 32 33 // 緩存圖片,防止在圖片切換過程中出現卡頓 34 if (self.game.device.editor) return; 35 qc.Tetris.IMAGES.forEach(function(c) { 36 var div = document.createElement('div'); 37 div.className = 'board board_' + c; 38 div.style.left = '-2000px'; 39 div.style.left = '-2000px'; 40 self.gameObject.div.appendChild(div); 41 }); 42 }; 43 44 /** 45 * 重繪棋盤 46 * @private 47 */ 48 BoardUI.prototype.redraw = function() { 49 var self = this; 50 51 // 繪制背景 52 self.drawBackground(); 53 }; 54 55 /** 56 * 繪制棋盤背景 57 */ 58 BoardUI.prototype.drawBackground = function() { 59 var self = this; 60 61 for (var pos in self.pieces) { 62 var div = self.pieces[pos]; 63 div.className = 'board board_' + qc.Tetris.IMAGES[qc.Tetris.board.data[pos].value]; 64 } 65 }; 66 67 /** 68 * 初始化棋盤 69 */ 70 BoardUI.prototype.reset = function() { 71 var self = this, o = self.gameObject; 72 73 // 構建棋盤數據 74 if (o.children.length === 0) { 75 for (var pos in qc.Tetris.board.data) { 76 var info = qc.Tetris.board.data[pos]; 77 var div = self.pieces[pos] = document.createElement('div'); 78 div.className = 'board board_' + qc.Tetris.IMAGES[info.value]; 79 div.style.left = Math.round(info.x + (o.width - qc.Tetris.BLOCK_W) / 2) + 'px'; 80 div.style.top = Math.round(info.y + (o.height - qc.Tetris.BLOCK_H) / 2) + 'px'; 81 o.div.appendChild(div); 82 } 83 } 84 else { 85 o.children.forEach(function(child) { 86 self.pieces[child.name] = child; 87 }); 88 } 89 };
本腳本運行在編輯模式下,這樣就可以實時看到棋盤。對本腳本做一些解釋:
- 屬性pieces存放了下面所有的div元素(顯示格子),key為其邏輯坐標
- 初始化時,會將底下的所有格子創建出來,並設置上合適的位置(由於在Board.js中計算坐標時,是以棋盤中心為原點;但格子div是以棋盤的左上角為原點,因此代碼中做了換算)
- redraw方法中,以格子的值獲取對應的className,對應的設置到格子div就可以了
- 工程設置的背景是為透明的,因此在初始化時,順便將游戲背景的顏色設置為:1F1E1E(最底層div的background-color屬性)
- 另外,初始時我們把所有格子的圖片都加載進來(方法是創建個屏幕內不可見的div),這樣在className切換過程中,就可以直接從本地緩存讀取圖片,避免加載圖片影響體驗
5. 將此腳本掛載到board節點上,保存場景並刷新頁面,就可以看到效果了:
九. 形狀池設計與實現
1. 本游戲中,總共有23種形狀,每種類型的形狀,其顏色是不同的。
在Scripts/logic下創建腳本Shapes.js,負責各種形狀的配置、抽取等。
1 var Shapes = qc.Tetris.Shapes = { 2 // 所有可能的形狀 3 tiles: [ 4 // 1個點的 5 { 6 value: 1, 7 list: [[[0, 0]]] 8 }, 9 10 { 11 value: 2, 12 list: [ 13 [[1, -1], [0, 0], [1, 0], [0, 1]], 14 [[0, 0], [1, 0], [-1, 1], [0, 1]], 15 [[0, 0], [1, 0], [0, 1], [1, 1]] 16 ] 17 }, { 18 value: 3, 19 list: [ 20 [[0, -1], [0, 0], [0, 1], [0, 2]], 21 [[0, 0], [1, -1], [-1, 1], [-2, 2]], 22 [[-1, 0], [0, 0], [1, 0], [2, 0]] 23 ] 24 }, { 25 value: 4, 26 list: [ 27 [[0, 0], [0, 1], [0, -1], [-1, 0]], 28 [[0, 0], [0, -1], [1, -1], [-1, 1]], 29 [[0, 0], [0, 1], [0, -1], [1, 0]], 30 [[0, 0], [1, 0], [-1, 0], [1, -1]], 31 [[0, 0], [1, 0], [-1, 0], [-1, 1]] 32 ] 33 }, { 34 value: 5, 35 list: [ 36 [[0, 0], [0, 1], [0, -1], [1, -1]], 37 [[0, 0], [1, -1], [-1, 1], [-1, 0]], 38 [[0, 0], [1, -1], [-1, 1], [1, 0]], 39 [[0, 0], [1, 0], [-1, 0], [0, -1]], 40 [[0, 0], [1, 0], [-1, 0], [0, 1]] 41 ] 42 }, { 43 value: 6, 44 list: [ 45 [[0, -1], [-1, 0], [-1, 1], [0, 1]], 46 [[-1, 0], [0, -1], [1, -1], [1, 0]], 47 [[0, -1], [1, -1], [1, 0], [0, 1]], 48 [[-1, 1], [0, 1], [1, 0], [1, -1]], 49 [[-1, 0], [-1, 1], [0, -1], [1, -1]], 50 [[-1, 0], [-1, 1], [0, 1], [1, 0]] 51 ] 52 } 53 ], 54 55 /** 56 * 重新開始的邏輯 57 */ 58 restart: function() { 59 qc.Tetris.Shapes.pool = []; 60 for (var i = 0; i < 3; i++) { 61 qc.Tetris.Shapes.pool.push(qc.Tetris.Shapes.random()); 62 } 63 }, 64 65 /** 66 * 隨機抽取一個形狀 67 */ 68 random: function() { 69 // 先抽取分類 70 var math = qc.Tetris.game.math; 71 var shapes = Shapes.tiles; 72 var shape = shapes[math.random(0, shapes.length - 1)]; 73 74 // 再抽子類 75 var list = shape.list[math.random(0, shape.list.length - 1)]; 76 return { 77 color: shape.color, 78 value: shape.value, 79 list: list 80 }; 81 }, 82 83 /** 84 * 當前的pool數據 85 */ 86 pool: [] 87 };
代碼說明如下:
-
- value指明了格子應該使用哪個圖片
- list包含了多個形狀,形狀由數組組成,每個元素指明了邏輯坐標
- pool屬性存儲了當前屏幕上3個形狀的數據信息
-
修改Tetris.js的qc.initGame方法,最后面添加3個形狀的初始化邏輯:
1 qc.initGame = function(game) { 2 // 將游戲實例記錄下來,便於訪問 3 Tetris.game = game; 4 5 // 幀率顯示為60幀(滿幀) 6 game.time.frameRate = 60; 7 8 // 初始化分數信息 9 Tetris.score = new qc.Tetris.Score(); 10 11 // 構建棋盤對象 12 Tetris.board = new qc.Tetris.Board(); 13 14 // 3個形狀 15 qc.Tetris.Shapes.restart(); 16 };
十. 形狀預制的實現
本章節我們進行形狀的繪制實現,如下圖:
1. 在UIRoot/pool節點下,創建一空的Node節點,設置如下圖
- 定位在父親的中心點
- 大小為200*100
- pivot設置為:(0.5, 0.5)
- 本對象需要可以交互(需要被拖放),勾選Interactive。並設置碰撞盒類型為Rectangle(正方形),按圖示設置大小(碰撞盒大小會比節點實際大小更大)
2.在Scripts/ui創建文件BlocksUI.js
1 /** 2 * 繪制一個形狀 3 */ 4 var BlocksUI = qc.defineBehaviour('qc.tetris.BlocksUI', qc.Behaviour, function() { 5 var self = this; 6 7 // 格子的預置,一個形狀下有多個格子 8 self.blockPrefab = null; 9 10 // 下屬所有的格子 11 self._blocks = {}; 12 }, { 13 blockPrefab: qc.Serializer.PREFAB 14 }); 15 16 Object.defineProperties(BlocksUI.prototype, { 17 /** 18 * 關聯的數據 19 */ 20 data: { 21 get: function() { return this._data; }, 22 set: function(v) { 23 this._data = v; 24 this.redraw(); 25 } 26 }, 27 28 /** 29 * 第幾個? 30 */ 31 index: { 32 get: function() { 33 return this.gameObject.parent.getChildIndex(this.gameObject); 34 } 35 } 36 }); 37 38 /** 39 * 初始化 40 */ 41 BlocksUI.prototype.awake = function() { 42 // 點擊時的偏移量 43 var self = this; 44 self.offsetY = self.game.device.desktop ? 0 : 50; 45 }; 46 47 /** 48 * 重新繪制區塊 49 */ 50 BlocksUI.prototype.redraw = function() { 51 var self = this; 52 var frame = qc.Tetris.IMAGES[self.data.value]; 53 self.data.list.forEach(function(pos) { 54 var x = pos[0], y = pos[1]; 55 var block = self.game.add.clone(self.blockPrefab, self.gameObject); 56 block.find('block').frame = frame + '.png'; 57 block.name = x + '_' + y; 58 self._blocks[qc.Tetris.makePos(x, y)] = block; 59 }); 60 self.reset(); 61 }; 62 63 /** 64 * 重設區塊大小和排列下屬格子的位置 65 */ 66 BlocksUI.prototype.reset = function() { 67 var self = this, o = self.gameObject; 68 for (var pos in self._blocks) { 69 var p = qc.Tetris.readPos(pos); 70 var pt = qc.Tetris.board.toWorld(p, qc.Tetris.POOL_DISTANCE_NORMAL); 71 var block = self._blocks[pos]; 72 block.anchoredX = pt.x; 73 block.anchoredY = pt.y; 74 } 75 };
- 本腳本根據形狀的數據,動態創建出格子並顯示出來
- blockPrefab為格子的預置,后續步驟創建
- 形狀的大小會比棋盤中顯示小,因此計算格子的屏幕坐標時,指明了常量:qc.Tetris.POOL_DISTANCE_NORMAL
- 在初始化時,設置了非PC模式下需要做偏移為50。(在手機上拖拽時方式被手指完全擋住,從而看不清形狀)
3. 修改Tetris.js,加入POOL_DISTANCE_NORMAL配置:
1 window.Tetris = qc.Tetris = { 2 // 棋盤的大小(半徑) 3 SIZE: 4, 4 5 // 棋盤中,每個格子的寬度和高度 6 BLOCK_W: 61, 7 BLOCK_H: 67, 8 9 // 沒有點擊時,格子之間的距離 10 POOL_DISTANCE_NORMAL: 45, 11 12 ...
4.在上述Blocks節點下,創建空的節點Node,名字修改為block2,屬性為:
- 相對於父親居中顯示
- pivot=(0.5, 0.5)
5. 在block2節點下,創建Image節點,名字修改為shadow,屬性設置如下:
- 相對於父親居中顯示
- pivot=(0.5, 0.5)
- 大小為60*65
- 使用ui圖集
6. 在block2節點下,創建Image節點,名字修改為block,屬性設置如下:
- 相對於父親居中顯示
- pivot=(0.5, 0.5)
- 大小為40*45
- 使用ui圖集
至此,你的場景應該是:
7. 將block2節點拖入到目錄Assets/prefab,創建預置。然后將節點從場景中刪除。
8. 將BlocksUI.js掛載到Blocks節點,並設置blockPrefab的值為上步驟創建的預制。設置完成后,將此節點拖入Assets/prefab目錄,創建預置。
9. 至此,形狀的預置創建完畢了