繼上一次介紹了《神奇的六邊形》的完整遊戲開發流程後(可點擊這裏查看),此次將爲你們介紹另一款魔性遊戲《跳躍的方塊》的完整開發流程。php
(點擊圖片可進入遊戲體驗)html
因內容太多,爲方便你們閱讀,因此分屢次來說解。node
若要一次性查看全部文檔,也可點擊這裏。瀏覽器
接上回(《跳躍的方塊》Part 2)app
(三)控制展現遊戲世界編輯器
通過前面的準備,虛擬遊戲世界已經構建完成,開始着手將虛擬世界呈現出來。ide
導入全部的圖形資源到Assets/atlas/main@atlas下,並從新打包圖集。
佈局
在game下添加引導界面、暫停按鈕(UIImage)、分數顯示(UIText)、暫停界面(Node)、暫停界面下的恢復按鈕(UIImage)和一個半透明層(UIImage)post
暫停按鈕
暫停按鈕的範圍爲相對於父節點左上角(20, 20, 70, 70),而且須要能接受事件。測試
分數顯示
分數顯示區域爲相對於父節點右上角(width - 90, 20, width - 110, 70)的範圍,並設置文本向右對齊。
暫停界面
暫停界面有一個屏蔽操做用的半透明遮罩和一個恢復按鈕組成。遮罩須要鋪滿全屏,恢復按鈕以父節點的中心爲錨點,向下偏移40。 遮罩和恢復按鈕都須要接受事件。
引導界面 引導界面提示玩家應該如何操做,以屏幕中心爲描點,進行佈局。
一點關於佈局的理解
顯示的內容能夠看作一個矩形區域,Pivot控制節點原點在矩形區域中的位置,Anchors和偏移值(left,right,top, bottom, anchoredX, anchoredY, width, height)則控制矩形四個頂點的位置。
因此設置時,先肯定但願節點原點的位置,設置好Pivot後,根據但願的矩形設置四個頂點的位置。
假設:父節點的高度、寬度分別爲h、w。那麼當四個邊能夠根據公式表達分別爲:
就能夠經過以下設置達到但願的效果:
在前面章節中建立的game節點,是一個鋪滿屏幕的節點,能夠理解爲對應屏幕,且Pivot爲(0,0),那麼座標系爲從左上到右下的一個座標系,這個座標系和虛擬世界的不一樣,須要轉換下。 在game節點下建立一個節點origin,把origin做爲顯示虛擬世界的原點。
在虛擬世界的建立過程當中,分析了關卡的特性,在顯示時只須要顯示屏幕中的關卡,甚至連建立也不須要,而且關卡是一個連着一個,有點相似於單列表格的形式。因而這裏選擇使用官方插件中的TableView來實現關卡效果。 使用TableView時,須要爲全部的關卡建立一個父節點,和建立方塊相似,咱們建立一個levels的Node節點,做爲全部關卡的父節點。 佈局參數以下:
在origin節點下建立一個UIImage節點:brick。設置它相對於父節點的上邊緣水平中心爲錨點,以本身的中心爲中心,旋轉45度。 最終佈局參數以下:
建立座標系、關卡父節點、方塊的具體操做以下:
使用TableView時,還須要一個數據適配器(TableViewAdapter)來提供關卡數據。 先引入插件ExtraUI(com.qici.extraUI),創建一個腳本LevelAdapter。內容以下:
1 var LevelAdapter = qc.defineBehaviour('qc.engine.LevelAdapter', com.qici.extraUI.TableViewAdapter, function() { 2 var self = this; 3 4 // 載入配置和遊戲世界 5 self.config = JumpingBrick.gameConfig; 6 self.world = JumpingBrick.gameWorld; 7 }, { 8 }); 9 10 LevelAdapter.prototype.awake = function() { 11 }; 12 13 /** 14 * 獲取表格大小,x、y同時只能有一個爲Infinity 15 * @return {{x: number|Infinity, y: number| Infinity}} 16 */ 17 LevelAdapter.prototype.getTableSize = function() { 18 // 關卡爲無限的 19 return { x: 1, y: Infinity}; 20 }; 21 22 /** 23 * 根據在Table中的點返回對應的單元格 24 * @param {number} x - x軸座標 25 * @param {number} y - y軸座標 26 * @return {{x: number, y: number}}} 返回點所在的單元格信息 27 */ 28 LevelAdapter.prototype.findCellWithPos = function(x, y) { 29 // 第一個格子爲第一屏960的高度,第二個格子爲第一關 30 return { 31 x: 0, 32 y: y < 960 ? 0 : (1 + Math.floor((y - 960) / this.config.levelInterval)) 33 }; 34 }; 35 36 /** 37 * 獲取節點的顯示位置 38 */ 39 LevelAdapter.prototype.getCellRect = function(col, row) { 40 if (row === 0) 41 return new qc.Rectangle(0, 0, 100, 960); 42 else 43 return new qc.Rectangle(0, 960 + (row - 1) * this.config.levelInterval, 100, this.config.levelInterval); 44 }; 45 46 /** 47 * 節點處於不可見時,回收節點, 48 * @param {qc.Node} cell - 節點 49 * @param {number} col - 所在列 50 * @param {number} row - 所在行 51 */ 52 LevelAdapter.prototype.revokeCell = function(cell, col, row) { 53 // 關卡不可見時,刪除已經生成的關卡數據 54 this.world.deleteLevelInfo(row - 1); 55 }; 56 57 /** 58 * 節點處於可見時,建立節點, 59 * @param {qc.Node} cell - 節點 60 * @param {number} col - 所在列 61 * @param {number} row - 所在行 62 */ 63 LevelAdapter.prototype.createCell = function(cell, col, row) { 64 // 建立關卡時,設置關卡信息 65 var self = this, 66 levelInfo = self.world.getLevelInfo(row - 1); 67 cell.levelShow.setLevelInfo(levelInfo); 68 };
建立腳本LevelShow,用來控制關卡預製的顯示方式。 內容以下:
1 var LevelShow = qc.defineBehaviour('qc.engine.LevelShow', qc.Behaviour, function() { 2 // 將腳本對象關聯到節點上 3 this.gameObject.levelShow = this; 4 }, { 5 leftLevel : qc.Serializer.NODE, // 關卡左邊阻擋 6 rightLevel : qc.Serializer.NODE, // 關卡右邊阻擋 7 block : qc.Serializer.NODE // 阻擋塊的父節點 8 }); 9 10 LevelShow.prototype.onDestory = function() { 11 // 釋放關聯 12 this.gameObject.levelShow = null; 13 }; 14 15 LevelShow.prototype.update = function() { 16 var self = this, 17 width = JumpingBrick.gameConfig.getGameWidth(); 18 // 若是是電腦瀏覽器打開,遊戲顯示的寬度可能會變化,因此須要根據屏幕寬度的變化,動態調整關卡阻擋的範圍。 19 // 防止左右兩邊出現空白區域 20 if (width !== self.recordWidth) { 21 var diff = (width - self.recordWidth) / 2; 22 self.recordWidth = width; 23 24 if (diff + self.leftLevel.width > 0) { 25 self.leftLevel.x -= diff; 26 self.leftLevel.width += diff; 27 } 28 29 if (diff + self.rightLevel.width > 0) { 30 self.rightLevel.width += diff; 31 } 32 } 33 }; 34 35 LevelShow.prototype.setLevelInfo = function(levelInfo) { 36 var self = this, 37 width = JumpingBrick.gameConfig.getGameWidth(); 38 var blockChildren = self.block.children; 39 var blockLen = blockChildren.length; 40 41 self.recordWidth = width; 42 if (!levelInfo) { 43 self.leftLevel.visible = self.rightLevel.visible = false; 44 while (blockLen--) { 45 blockChildren[blockLen].visible = false; 46 } 47 return; 48 } 49 var passArea = levelInfo.passArea, 50 color = new qc.Color(levelInfo.color); 51 52 self.leftLevel.visible = self.rightLevel.visible = true; 53 // 設置左邊阻擋 54 self.leftLevel.x = -0.5 * width; 55 self.leftLevel.y = passArea.y; 56 self.leftLevel.width = passArea.x - self.leftLevel.x; 57 self.leftLevel.height = passArea.height; 58 self.leftLevel.colorTint = color; 59 60 // 設置右邊阻擋 61 self.rightLevel.x = passArea.x + passArea.width; 62 self.rightLevel.y = passArea.y; 63 self.rightLevel.width = 0.5 * width - self.rightLevel.x; 64 self.rightLevel.height = passArea.height; 65 self.rightLevel.colorTint = color; 66 67 // 確保塊夠用 68 while (blockLen < levelInfo.block.length) { 69 blockLen++; 70 self.game.add.clone(self.leftLevel, self.block); 71 } 72 73 blockChildren = self.block.children; 74 blockLen = blockChildren.length; 75 var idx = -1; 76 while (++idx < blockLen) { 77 var blockInfo = levelInfo.block[idx]; 78 if (!blockInfo) { 79 blockChildren[idx].visible = false; 80 } 81 else { 82 blockChildren[idx].colorTint = color; 83 blockChildren[idx].visible = true; 84 blockChildren[idx].x = blockInfo.x; 85 blockChildren[idx].y = blockInfo.y; 86 blockChildren[idx].width = blockInfo.width; 87 blockChildren[idx].height = blockInfo.height; 88 } 89 } 90 };
建立一個預製level,level下有三個節點:leftLevel(UIImage),rightLevel(UIImage),block(Node)。 併爲其添加上一步建立的腳本LevelShow。
建立腳本,並預設功能相關的節點,監聽相關事件。具體實現以下:
1 /** 2 * 遊戲控制,將虛擬世界投影到遊戲世界,並管理暫停等處理 3 */ 4 var GameControl = qc.defineBehaviour('qc.JumpingBrick.GameControl', qc.Behaviour, function() { 5 var self = this; 6 7 // 設置到全局中 8 JumpingBrick.gameControl = self; 9 10 // 方塊 11 self.brick = null; 12 13 // 關卡的父節點,用於動態掛載關卡節點 14 self.levelParent = null; 15 16 // 開始指引界面 17 self.startManual = null; 18 19 // 暫停界面 20 self.pausePanel = null; 21 22 // 暫停按鈕 23 self.pauseButton = null; 24 25 // 回到遊戲按鈕 26 self.resumeButton = null; 27 28 // 當前的狀態 29 self._state = 0; 30 }, { 31 brick: qc.Serializer.NODE, 32 tableViewNode : qc.Serializer.NODE, 33 scoreText: qc.Serializer.NODE, 34 levelParent: qc.Serializer.NODE, 35 startManual: qc.Serializer.NODE, 36 pausePanel: qc.Serializer.NODE, 37 pauseButton: qc.Serializer.NODE, 38 resumeButton: qc.Serializer.NODE 39 }); 40 41 /** 42 * 初始化 43 */ 44 GameControl.prototype.awake = function() { 45 var self = this, 46 config = JumpingBrick.gameConfig; 47 48 // 監聽節點的鼠標或者觸摸按下事件 49 self.addListener(self.gameObject.onDown, self.doPointDown, self); 50 // 監聽鍵盤事件 51 self.addListener(self.game.input.onKeyDown, self.doKeyDown, self); 52 // 監聽暫停按鈕 53 self.pauseButton && self.addListener(self.pauseButton.onClick, self.doPause, self); 54 // 監聽恢復按鈕 55 self.resumeButton && self.addListener(self.resumeButton.onClick, self.doResume, self); 56 // 監聽遊戲結束 57 self.addListener(JumpingBrick.gameWorld.onGameOver, self.doGameOver, self); 58 59 // 監聽分數變化 60 self.addListener(JumpingBrick.gameWorld.onScoreChanged, self.doScoreChanged, self); 61 62 // 獲取Brick上的結束時播放的TweenPosition 63 self._brickTweenPosition = self.brick.getScript('qc.TweenPosition'); 64 if (self._brickTweenPosition) 65 self.addListener(self._brickTweenPosition.onFinished, self.doGameFinished, self); 66 67 // 獲取levelParent上的結束時播放的TweenPosition 68 self._levelTweenPosition = self.levelParent.getScript('qc.TweenPosition'); 69 70 // 根據配置初始化方塊信息 71 if (self.brick) { 72 self.brick.width = self.brick.height = config.brickSide; 73 self.brick.rotation = Math.PI / 4; 74 } 75 76 // 初始化 77 self.switchState(GameControl.STATE_MANUEL); 78 }; 79 80 /** 81 * 銷燬時 82 */ 83 GameControl.prototype.onDestroy = function() { 84 // 預生成的關卡節點清理 85 this._blockPool = []; 86 87 // 使用中的關卡節點清理 88 this._showLevel = []; 89 };
遊戲運行時,分爲開始引導、遊戲運行、遊戲暫停、遊戲結束4個狀態,對這四個狀態進行統一管理。代碼以下:
1 /** 2 * 遊戲開始時,指引界面狀態 3 */ 4 GameControl.STATE_MANUEL = 0; 5 /** 6 * 遊戲運行狀態 7 */ 8 GameControl.STATE_RUN = 1; 9 /** 10 * 遊戲暫停狀態 11 */ 12 GameControl.STATE_PAUSE = 2; 13 14 /** 15 * 遊戲結束處理 16 */ 17 GameControl.STATE_GAMEOVER = 3; 18 19 /** 20 * 切換狀態 21 */ 22 GameControl.prototype.switchState = function(state) { 23 var self = this; 24 self.state = state; 25 self.startManual.visible = self.state === GameControl.STATE_MANUEL; 26 if (self.startManual.visible) { 27 // 進入開始引導時,必須重置遊戲世界 28 JumpingBrick.gameWorld.resetWorld(); 29 self.tableViewNode.getScript('com.qici.extraUI.TableView').revokeAllCell(); 30 } 31 32 self.pausePanel.visible = self.state === GameControl.STATE_PAUSE; 33 // 同步虛擬世界和顯示 34 self.syncWorld(); 35 };
1 /** 2 * 保存遊戲 3 */ 4 GameControl.prototype.saveGameState = function() { 5 var self = this, 6 gameWorld = JumpingBrick.gameWorld, 7 data = JumpingBrick.data; 8 if (!data) 9 return; 10 var saveData = gameWorld.saveGameState(); 11 data.saveGameState(saveData); 12 }; 13 14 /** 15 * 恢復遊戲 16 */ 17 GameControl.prototype.restoreGameState = function() { 18 var self = this, 19 gameWorld = JumpingBrick.gameWorld, 20 data = JumpingBrick.data; 21 if (!data) 22 return; 23 var saveData = data.restoreGameState(); 24 if (saveData) { 25 gameWorld.restoreGameState(saveData); 26 self.switchState(GameControl.STATE_PAUSE); 27 } 28 }; 29 30 /** 31 * 清理遊戲 32 */ 33 GameControl.prototype.clearGameState = function() { 34 var self = this, 35 data = JumpingBrick.data; 36 if (!data) 37 return; 38 data.clearGameState(); 39 };
對暫停、恢復進行處理。
1 /** 2 * 處理暫停 3 */ 4 GameControl.prototype.doPause = function() { 5 var self = this; 6 self.saveGameState(); 7 self.switchState(GameControl.STATE_PAUSE); 8 }; 9 10 /** 11 * 處理恢復 12 */ 13 GameControl.prototype.doResume = function() { 14 var self = this; 15 self.clearGameState(); 16 self.switchState(GameControl.STATE_RUN); 17 };
讓遊戲支持輸入。
1 ** 2 * 處理方塊跳躍 3 */ 4 GameControl.prototype.doBrickJump = function(direction) { 5 var self = this, 6 world = JumpingBrick.gameWorld; 7 8 if (self.state === GameControl.STATE_MANUEL) { 9 // 引導狀態跳躍直接切換到運行狀態 10 self.switchState(GameControl.STATE_RUN); 11 } 12 13 world.brickJump(direction); 14 }; 15 16 /** 17 * 處理點擊 18 */ 19 GameGControl.prototype.doPointDown = function(node, event) { 20 var self = this; 21 if (self.state !== GameControl.STATE_MANUEL && 22 self.state !== GameControl.STATE_RUN) { 23 return; 24 } 25 var localPoint = self.gameObject.toLocal({x: event.source.x, y: event.source.y}); 26 var halfWidth = self.gameObject.width * 0.5; 27 self.doBrickJump(localPoint.x - halfWidth); 28 }; 29 30 /** 31 * 處理鍵盤 32 */ 33 GameControl.prototype.doKeyDown = function(keycode) { 34 var self = this; 35 if (keycode === qc.Keyboard.LEFT || keycode === qc.Keyboard.RIGHT) { 36 if (self.state !== GameControl.STATE_MANUEL && 37 self.state !== GameControl.STATE_RUN) { 38 return; 39 } 40 self.doBrickJump(keycode === qc.Keyboard.LEFT ? -1 : 1); 41 } 42 else if (keycode === qc.Keyboard.ENTER || keycode === qc.Keyboard.SPACEBAR) { 43 if (self.state === GameControl.STATE_RUN) { 44 self.doPause(); 45 } 46 else if (self.state === GameControl.STATE_PAUSE) { 47 self.doResume(); 48 } 49 } 50 };
須要處理遊戲世界反饋回來的分數變動和遊戲結束事件。
1 /** 2 * 分數變動 3 */ 4 GameControl.prototype.doScoreChanged = function(score) { 5 var self = this; 6 if (self.scoreText) { 7 self.scoreText.text = '' + score; 8 } 9 JumpingBrick.data.buildShareContent(score); 10 }; 11 12 /** 13 * 處理遊戲結束 14 */ 15 GameControl.prototype.doGameOver = function(type) { 16 var self = this; 17 // 切換狀態 18 self.switchState(GameControl.STATE_GAMEOVER); 19 // 播放結束動畫 20 if (type !== qc.JumpingBrick.GameWorld.GAMEOVER_DEADLINE && self._brickTweenPosition) { 21 if (self._levelTweenPosition) { 22 self._levelTweenPosition.setCurrToStartValue(); 23 self._levelTweenPosition.setCurrToEndValue(); 24 self._levelTweenPosition.to.x += 6; 25 self._levelTweenPosition.to.y += 6; 26 self._levelTweenPosition.resetToBeginning(); 27 qc.Tween.playGroup(self.levelParent, 1); 28 } 29 self._brickTweenPosition.setCurrToStartValue(); 30 self._brickTweenPosition.setCurrToEndValue(); 31 self._brickTweenPosition.to.y = -2 * JumpingBrick.gameConfig.brickRadius; 32 self._brickTweenPosition.duration = Math.max(0.01, Math.sqrt(Math.abs(2 * (self._brickTweenPosition.to.y - self._brickTweenPosition.from.y) / JumpingBrick.gameConfig.gravity))); 33 self._brickTweenPosition.resetToBeginning(); 34 qc.Tween.playGroup(self.brick, 1); 35 } 36 else { 37 self.doGameFinished(); 38 } 39 40 }; 41 42 /** 43 * 處理遊戲完結 44 */ 45 GameControl.prototype.doGameFinished = function() { 46 var self = this; 47 // 更新數據 48 if (JumpingBrick.data) 49 JumpingBrick.data.saveScore(JumpingBrick.gameWorld.score); 50 51 // 切換到結算界面 52 qc.Tween.stopGroup(self.brick, 1); 53 qc.Tween.stopGroup(self.levelParent, 1); 54 self.brick.rotation = Math.PI / 4; 55 // 當不存在界面管理時,直接從新開始遊戲 56 if (JumpingBrick.uiManager) 57 JumpingBrick.uiManager.switchStateTo(qc.JumpingBrick.UIManager.GameOver); 58 else 59 self.switchState(GameControl.STATE_MANUEL); 60 };
1 GameControl.prototype.resetFPS = function() { 2 var self = this; 3 self.game.debug.total = 0; 4 self._fpsCount = 1; 5 }; 6 7 /** 8 * 每幀更新 9 */ 10 GameControl.prototype.update = function() { 11 var self = this; 12 13 if (self.state === GameControl.STATE_RUN) { 14 // 只有運行狀態才處理虛擬世界更新 15 var delta = self.game.time.deltaTime * 0.001; 16 JumpingBrick.gameWorld.updateLogic(delta); 17 self.syncWorld(); 18 } 19 20 // 幀率分析,若是當前能支持60幀則60幀調度 21 if (self._fpsCount > 50) { 22 var cost = self.game.debug.total / self._fpsCount; 23 self._fpsCount = 1; 24 self.game.debug.total = 0; 25 if (cost < 10) { 26 self.game.time.frameRate = 60; 27 } 28 else { 29 self.game.time.frameRate = 30; 30 } 31 } 32 else { 33 self._fpsCount++; 34 } 35 }; 36 37 38 /** 39 * 同步世界數據 40 */ 41 GameControl.prototype.syncWorld = function() { 42 var self = this, 43 world = JumpingBrick.gameWorld; 44 45 // 同步方塊 46 self.brick.x = world.x; 47 self.brick.y = world.y - world.deadline; 48 49 self.levelParent.y = -world.deadline; 50 };
brick添加TweenRotaion後,設置如圖:
brick添加TweenPosition後,設置如圖:
添加腳本並設置關聯的操做以下(未包含Tween添加和設置):
至此,遊戲部分就已經基本完成了。由於代碼處理中兼容了其餘模塊不存在的狀況,因此如今已經能夠提供給其餘人進行測試、反饋,而後進行優化調整了。
下次將繼續講解如何進行遊戲當中的數據處理,敬請期待!
另外,今天是平安夜,祝你們聖誕快樂!
★
聖 誕 快 樂 .★ * ★.
.*★ *. *..* ★
★ Marry X'mas *
★ & ‧°∴°﹒☆°.
‘*. Happy New Year *
‘★ ★
‘*..★'
其餘相關連接