(點擊圖片可進入試玩)javascript
本篇文章爲第二部份內容,這篇文章將具體介紹遊戲的實現,這篇文章的的主要內容有:php
三、建立工程與場景html
四、玩家分數管理java
五、開始界面node
六、遊戲界面git
3、建立工程與場景web
建立工程Koala和空的主場景main,設置以下:json
遊戲入口與遊戲初始化c#
在Scripts文件夾下建立文件:Koala.js。代碼以下:api
1 var Koala = qc.Koala = { 2 ui : {}, 3 logic : {}, 4 5 // 遊戲對象 6 game : null, 7 8 // 遊戲寬度 9 GAMEWIDTH : 640 10 11 }; 12 13 Koala.initLogic = function(excel, game) { 14 15 // 設置遊戲對象引用 16 this.game = game; 17 18 // 設置遊戲幀率爲60幀 19 game.time.frameRate = 60; 20 21 };
此腳本定義了名字空間,用於記錄全局數據。遊戲入口中,記錄了game的實例。並將幀率限定爲60幀(默認在手機下爲30幀),這句代碼也能夠不用寫,咱們能夠在Project/Settings中設置,以下圖:
4、玩家分數管理
建立腳本:Scripts/logic/Me.js,腳本代碼以下:
1 var Me = qc.Koala.logic.Me = function() { 2 // 當前關卡 3 this.level = 1; 4 5 // 當前分數 6 this._score = 0; 7 8 // 歷史最高分 9 this._best = 0; 10 11 // 遊戲是否結束 12 this.isDie = false; 13 14 // 遊戲是否暫停 15 this.paused = false; 16 17 // 用戶相關信息 18 this.token = ''; 19 this.rid = ''; 20 this.userInfo = null; 21 this.channel = ''; 22 }; 23 24 Me.prototype = {}; 25 Me.prototype.constructor = Me; 26 27 Object.defineProperties(Me.prototype, { 28 'score' : { 29 get : function() { return this._score; }, 30 set : function(v) { 31 if (this._score === v) return; 32 this._score = v; 33 34 this.best = v; 35 36 qc.Koala.onScoreChange.dispatch(v); 37 } 38 }, 39 40 'best' : { 41 get : function() { return this._best; }, 42 set : function(v) { 43 if (this._best >= v) return; 44 this._best = v; 45 46 var key = 'best_' + this.rid; 47 qc.Koala.game.storage.set(key, v); 48 } 49 } 50 }); 51 52 Me.prototype.reset = function() { 53 this.level = 1; 54 this.score = 0; 55 56 this.isDie = false; 57 this.paused = false; 58 }; 59 60 /** 61 * 加分 62 * @param {number} score - 增量 63 */ 64 Me.prototype.addScore = function(score) { 65 if (typeof score !== 'number' || score <= 0) return; 66 67 this.score = this._score + score; 68 }; 69 70 /** 71 * 校訂最高分 72 */ 73 Me.prototype.adjustBest = function () { 74 if (!this.userInfo) return; 75 76 var score = this.userInfo.scorers; 77 this.readFromStorage(); 78 if (score > this._best) 79 this.best = score; 80 }; 81 82 /** 83 * 讀取記錄 84 */ 85 Me.prototype.readFromStorage = function () { 86 var key = 'best_' + this.rid; 87 var best = qc.Koala.game.storage.get(key); 88 if (best) this.best = best; 89 }; 90 91 /** 92 * 保存記錄 93 */ 94 Me.prototype.saveToStorage = function () { 95 qc.Koala.game.storage.save(); 96 };
Me類維護了兩個數據:score(當前玩家的分數)、best(玩家的歷史最高分)
實例化Me類
打開Koala.js腳本,在initLogic方法中,加入代碼:
1 Koala.initLogic = function(excel, game) { 2 3 // 設置遊戲對象引用 4 this.game = game; 5 6 // 設置遊戲幀率爲60幀 7 game.time.frameRate = 60; 8 9 // 遊戲相關數據邏輯類 10 this.logic.me = new qc.Koala.logic.Me(); 11 }
5、開始界面
在遊戲還沒開始時,我構思的登陸界面應該是,有兩個登陸按鈕,一個是用於快速登陸按鈕,另外一個是提供微信登陸的按鈕,以及其它的一些界面元素,效果圖以下:
如今我講下該登陸的界面實現。先說背景界面。
5.1背景界面
背景界面由藍天、白雲、山峯及樹組成。
藍天背景:首先在引擎編輯器的Hierarchy面板建立一個UIRoot節點取名"遊戲背景",在"遊戲背景"節點下建立一個Image節點取名"藍天背景",我想讓這個節點圖片鋪滿整個屏幕,因此我設置該節點的屬性以下:
白雲區域:遊戲運行時,爲了讓遊戲更逼真一點,就想讓雲在天空中一直漂浮,在設計中使用三朵雲讓它們循環移動。爲了使遊戲可以在各類不一樣分辨率的屏幕下能正常顯示,我是這麼作的,首先是在"遊戲背景"節點下建立一個Empty Node取名"白雲區域",向上對齊左右拉伸,屬性值設置以下:
將三朵雲掛載到"白雲區域"節點下,因爲方法相似,我只以其中的一朵雲做爲講解,首先在"白雲區域"節點下建立一個Image取名爲"白雲1",在運行時,三朵雲循環移動我採用引擎提供的TweenPosition動畫,掛載完成後以下所示:
TweenPosition動畫的屬性From值爲從哪一個位置開始,To值爲到哪一個位置,play Style設置爲Loop(循環移動),Duration持續的時間爲9秒,更多Tween動畫可查看官方文檔《Tween動畫》。
在講山峯區域以前,我先要講下預製:在場景中能夠很容易建立遊戲對象並設置屬性,但當有大量相同的遊戲對象須要在場景中複用就成了問題,但該引擎提供了預製類型資源,能夠完整保存遊戲對象屬性、組件及其子孫對象。預製至關於模板,可用於在場景中建立新出的遊戲對象實例,達到複用的效果。在遊戲中,我在"山峯區域"節點下須要三個一樣的節點,故使用預製,在後續講的樹區域也須要用到預製。
山峯區域:在"山峯區域"節點下建立一個Image節點取名"mountain",須要把山峯節點放到屏幕的左下位置,將該節點拖入到"prefab"文件夾,即完成預製的製做,在遊戲中,三個山峯是一個接連一個,爲的是在遊戲場景移動時產生連貫的效果,在代碼中已經設置了山與山的距離,第一個山峯節點的屬性值設置以下:
樹區域:樹預製的製做與山峯預製同樣,這裏就不一一講述了。
此時,咱們已經把背景界面搭建起來了,但是在運行時,咱們須要雲移動、產生山峯預製、樹預製,這些就交給代碼來執行吧。在Scripts/ui文件下建立腳本:Background.js,代碼以下:
1 var Background = qc.defineBehaviour('qc.Koala.ui.Background', qc.Behaviour, function() { 2 // 動畫播放距離 3 this.tweenDistance = 0; 4 5 // 山與山之間的距離 6 this.mountainDistance = 635; 7 8 // 樹與樹之間的距離 9 this.treeDistance = 340; 10 11 this.mountains = []; 12 13 this.trees = []; 14 15 this.treeIcons = [ 'tree_1.bin', 'tree_2.bin', 'tree_3.bin' ]; 16 }, { 17 // 雲列表 18 clouds : qc.Serializer.NODES, 19 // 山峯區域 20 mountainRect : qc.Serializer.NODE, 21 // 山峯預製 22 mountainPrefab : qc.Serializer.PREFAB, 23 // 樹區域 24 treeRect : qc.Serializer.NODE, 25 // 樹預製 26 treePrefab : qc.Serializer.PREFAB 27 }); 28 29 Background.prototype.awake = function() { 30 31 this.addListener(this.game.world.onSizeChange, function() { 32 this.initMountain(); 33 this.initTree(); 34 }, this); 35 36 this.game.timer.add(1000, this.init, this); 37 }; 38 39 Background.prototype.init = function () { 40 this.clouds.forEach(function(cloud) { 41 var s = cloud.getScript('qc.TweenPosition'); 42 s.from.setTo(cloud.parent.width, s.from.y); 43 s.resetToBeginning(); 44 s.play(); 45 cloud.visible = true; 46 }, this); 47 48 // 初始化山 49 this.initMountain(); 50 51 // 初始化樹 52 this.initTree(); 53 }; 54 55 /** 56 * 初始化山 57 * @method qc.Koala.ui.Background#initMountain 58 */ 59 Background.prototype.initMountain = function () { 60 var mountainCount = Math.ceil(this.gameObject.width / this.mountainDistance) + 1, 61 count = mountainCount - this.mountains.length; 62 if (count <= 0) return; 63 this._createMountain(count); 64 }; 65 66 /** 67 * 建立山 68 * @method createMountain 69 * @param {number} count - 要建立的個數 70 */ 71 Background.prototype._createMountain = function (count) { 72 while (count--) { 73 var m = this.game.add.clone(this.mountainPrefab, this.mountainRect); 74 m.x = this.mountainDistance * this.mountains.length; 75 this.mountains.push(m); 76 } 77 }; 78 79 /** 80 * 初始化樹 81 * @method qc.Koala.ui.Background#initTree 82 */ 83 Background.prototype.initTree = function () { 84 var treeCount = Math.ceil(this.gameObject.width / this.treeDistance) + 1, 85 count = treeCount - this.trees.length; 86 if (count <= 0) return; 87 this._createTree(count); 88 }; 89 90 /** 91 * 建立樹 92 * @method qc.Koala.ui.Background#createTree 93 * @param {number} count - 建立個數 94 */ 95 Background.prototype._createTree = function (count) { 96 while (count--) { 97 var t = this.game.add.clone(this.treePrefab, this.treeRect); 98 t.x = this.treeDistance * this.trees.length; 99 this.trees.push(t); 100 101 var icon = this.treeIcons[qc.Koala.Math.random(0, this.treeIcons.length - 1)]; 102 this.game.assets.load( 103 'treeIcon_' + this.trees.length, 104 'Assets/texture/' + icon, 105 (function(texture) { 106 this.texture = texture; 107 }).bind(t) 108 ); 109 t.height += qc.Koala.Math.random(-10, 40); 110 } 111 };
將該腳本掛載到"遊戲背景"節點上,並將對應的節點拖入到對應的屬性值,以下圖所示:
至此,咱們已經把背景界面弄好了,但我想把登陸界面與背景界面分離出來,故我在Hierarchy面板另建立一個UIRoot取名爲"界面",將登陸界面節點掛載到"界面"節點。如今咱們須要弄按鈕顯示及其它的一些界面顯示,除去背景界面,效果圖以下:
5.2歡迎界面
首先在"界面"節點下建立一個Empty Node取名爲"歡迎界面",目的是將上圖中顯示的界面元素都掛載到該節點下,"歡迎界面"的屬性設置以下:
左邊柱子:在遊戲中,使用了大量柱子對象,故咱們能夠把柱子作成預製,前面已經講述瞭如何製做預製,這裏就不一一贅述。須要說明的是,柱子由柱子軀幹與柱頭所組成,這樣作的目的是,在遊戲中,咱們根據等級相應的改變柱子的粗細。將作好的柱子預製拖入到"歡迎界面"節點下取名"左邊柱子",柱子是傾斜的,咱們能夠設置它的Rotation值,節點屬性值設置以下:
其他的界面元素建立相似,就不一一介紹了,更多的界面佈局也能夠參考《界面佈局》。此時,咱們的開始界面已經完成了,但是當咱們點擊快速登陸、或者微信登陸按鈕時,咱們須要作相應的操做。這些就交給代碼作吧!建立腳本Welcome.js,該腳本主要功能是監聽按鈕是否按下,如微信登陸按鈕按下時,則作微信登陸處理,代碼以下,須要說明的是代碼中有微信API函數,暫時不用去管它,後續會講述。
1 var Welcome = qc.defineBehaviour('qc.Koala.ui.Welcome', qc.Behaviour, function() { 2 }, { 3 // 快速登陸按鈕 4 quickBtn : qc.Serializer.NODE, 5 // 微信登陸按鈕 6 wechatBtn : qc.Serializer.NODE, 7 // 配置文件 8 config : qc.Serializer.EXCELASSET, 9 // 登陸提示區域 10 loginMask : qc.Serializer.NODE 11 }); 12 13 Welcome.prototype.awake = function() { 14 // 初始化邏輯腳本 15 qc.Koala.initLogic(this.config, this.game); 16 17 // 監聽快速登陸事件 18 this.addListener(this.quickBtn.onClick, this._onStart, this); 19 20 // 監聽微信登陸按鈕點擊事件 21 this.addListener(this.wechatBtn.onClick, this._wechatLogin, this); 22 23 // 監聽正在登陸中事件 24 this.addListener(qc.Koala.onLogining, this._logining, this); 25 26 // 監聽登陸失敗事件 27 this.addListener(qc.Koala.onLoginFail, this._loginFail, this); 28 29 // 監聽登陸成功事件 30 this.addListener(qc.Koala.onLogin, this.hide, this); 31 32 // 獲取微信插件對象 33 var wx = this.getScript('qc.QcWeChat'); 34 35 // 設置快速登陸按鈕的可見狀況 36 this.quickBtn.visible = !wx.isWeChat(); 37 38 // 從新佈局按鈕 39 this.quickBtn.parent.getScript('qc.TableLayout').rebuildTable(); 40 41 // 監聽開始登陸事件 42 this.addListener(wx.onStartLogin, function() { 43 qc.Koala.onLogining.dispatch(); 44 }, this); 45 46 // 設置微信登錄結果監聽 47 this.addListener(wx.onLogin, function(flag) { 48 if (flag === "success") { 49 this._loginSuccess(); 50 } 51 else { 52 // 派發登陸失敗事件 53 qc.Koala.onLoginFail.dispatch(); 54 } 55 }, this); 56 57 }; 58 59 Welcome.prototype._onStart = function() { 60 qc.Koala.onStart.dispatch(); 61 this.hide(); 62 }; 63 64 Welcome.prototype._wechatLogin = function () { 65 //微信登錄 66 this.getScript('qc.QcWeChat').login(); 67 }; 68 69 /** 70 * 微信登陸成功回調 71 */ 72 Welcome.prototype._loginSuccess = function () { 73 var wx = this.getScript('qc.QcWeChat'); 74 if (wx.user) { 75 qc.Koala.logic.me.token = wx.user.token; 76 qc.Koala.logic.me.rid = wx.user.rid; 77 qc.Koala.logic.me.userInfo = wx.user; 78 } 79 // 設置爲微信渠道 80 qc.Koala.logic.me.channel = "weixin"; 81 82 // 開始遊戲 83 this._onStart(); 84 85 // 校訂最高分 86 qc.Koala.logic.me.adjustBest(); 87 }; 88 89 Welcome.prototype._logining = function () { 90 this.loginMask.visible = true; 91 }; 92 93 Welcome.prototype._loginFail = function () { 94 this.loginMask.visible = false; 95 }; 96 97 Welcome.prototype.hide = function() { 98 this.gameObject.visible = false; 99 };
把該腳本掛載到"歡迎界面"節點上,並將對應的節點拖入到對應的屬性上,須要說明的是,Config屬性值爲遊戲數據配置表,暫時咱們能夠不去管它,在後續咱們配置的Excel表,須要拖入到該屬性值中,以下圖所示:
須要說明的是該遊戲在手機端運行時,有"微信登陸"功能,故須要建立微信腳本(目前引擎已經有微信插件,可直接掛載),在Scripts/wx文件夾下建立兩個腳本分別是QcWx.js與QcWeChat.js,其中QcWx.js爲微信接口類可用於微信分享、錄音、掃一掃等功能。代碼以下:
1 // version 03.2 2 var QCWX = qc.QCWX = function() { 3 var self = this; 4 5 self.title = ''; 6 self.imgUrl = ''; 7 self.desc = ''; 8 self.url = ''; 9 self.sign = null; 10 self.ready = false; 11 self.debug = false; 12 }; 13 QCWX.prototype = {}; 14 QCWX.prototype.constructor = QCWX; 15 16 /** 17 * 初始化微信接口 18 */ 19 QCWX.prototype.init = function(sign, callback) { 20 var self = this; 21 self.sign = sign; 22 23 // 不支持微信接口? 24 if (!window.wx) { 25 return; 26 } 27 wx.config({ 28 debug: self.debug, 29 appId: sign.appId, 30 timestamp: sign.timeStamp, 31 nonceStr: sign.nonceStr, 32 signature: sign.signature, 33 jsApiList: [ 34 'onMenuShareTimeline', 'onMenuShareQQ', 'onMenuShareQZone', 'onMenuShareAppMessage', 'onMenuShareWeibo', 35 'startRecord', 'stopRecord', 'onVoiceRecordEnd', 'playVoice', 'pauseVoice', 'stopVoice', 'onVoicePlayEnd', 36 'uploadVoice', 'downloadVoice', 'chooseImage', 'previewImage', 'uploadImage', 'downloadImage', 37 'translateVoice', 'getNetworkType', 'openLocation', 'getLocation', 'closeWindow', 'scanQRCode' 38 ] 39 }); 40 41 wx.ready(function() { 42 // 標記下已經初始化完畢了 43 self.ready = true; 44 if (callback) callback(); 45 }); 46 }; 47 48 /** 49 * 分享接口 50 */ 51 QCWX.prototype.share = function(shareSignal) { 52 var self = this; 53 if (!self.ready) { 54 console.error('還沒有初始化完成'); 55 return; 56 } 57 58 var body = { 59 title: self.title, 60 desc: "", 61 trigger: function() { 62 if (!shareSignal) return; 63 shareSignal.dispatch(body); 64 body.link = qc.qcWeChat.shareLink + qc.qcWeChat.shareDir; 65 } 66 }; 67 68 //alert(JSON.stringify(body)); 69 70 // 分享到朋友圈 71 wx.onMenuShareTimeline(body); 72 73 // 分享給朋友 74 wx.onMenuShareAppMessage(body); 75 76 // 分享到QQ 77 wx.onMenuShareQQ(body); 78 79 // 分享到騰訊微博 80 wx.onMenuShareWeibo(body); 81 82 // 分享到QQ空間 83 wx.onMenuShareQZone(body); 84 }; 85 86 /** 87 * 拍照或從手機相冊中選圖 88 * @param {number} count - 圖片的數量 89 */ 90 QCWX.prototype.chooseImage = function(count, sizeType, sourceType, callback) { 91 var self = this; 92 if (!self.ready) { 93 console.error('還沒有初始化完成'); 94 return; 95 } 96 97 if (!sizeType) sizeType = ['original', 'compressed']; 98 if (!sourceType) sourceType = ['album', 'camera']; 99 100 wx.chooseImage({ 101 count: count, 102 sizeType: sizeType, 103 sourceType: sourceType, 104 success: function(res) { 105 if (callback) callback(res.localIds); 106 } 107 }); 108 }; 109 110 /** 111 * 預覽圖片 112 */ 113 QCWX.prototype.previewImage = function(current, urls) { 114 var self = this; 115 if (!self.ready) { 116 console.error('還沒有初始化完成'); 117 return; 118 } 119 120 current = current || ''; 121 urls = urls || []; 122 wx.previewImage({ 123 current: current, 124 urls: urls 125 }); 126 }; 127 128 /** 129 * 上傳圖片,有效期爲3天 130 */ 131 QCWX.prototype.uploadImage = function(localId, isShowProgressTips, callback) { 132 var self = this; 133 if (!self.ready) { 134 console.error('還沒有初始化完成'); 135 return; 136 } 137 wx.uploadImage({ 138 localId: localId, 139 isShowProgressTips: isShowProgressTips ? 1 : 0, 140 success: function(res) { 141 if (callback) callback(res.serverId); 142 } 143 }); 144 }; 145 146 /** 147 * 下載圖片 148 */ 149 QCWX.prototype.downloadImage = function(serverId, isShowProgressTips, callback) { 150 var self = this; 151 if (!self.ready) { 152 console.error('還沒有初始化完成'); 153 return; 154 } 155 wx.downloadImage({ 156 serverId: serverId, 157 isShowProgressTips: isShowProgressTips ? 1 : 0, 158 success: function(res) { 159 if (callback) callback(res.localId); 160 } 161 }); 162 }; 163 164 /** 165 * 開始錄音 166 */ 167 QCWX.prototype.startRecord = function() { 168 var self = this; 169 if (!self.ready) { 170 console.error('還沒有初始化完成'); 171 return; 172 } 173 wx.startRecord(); 174 }; 175 176 /** 177 * 中止錄音 178 */ 179 QCWX.prototype.stopRecord = function(callback) { 180 var self = this; 181 if (!self.ready) { 182 console.error('還沒有初始化完成'); 183 return; 184 } 185 wx.stopRecord({ 186 success: function(res) { 187 if (callback) callback(res.localId); 188 } 189 }); 190 }; 191 192 /** 193 * 監聽錄音自動中止 194 */ 195 QCWX.prototype.onVoiceRecordEnd = function(callback) { 196 var self = this; 197 if (!self.ready) { 198 console.error('還沒有初始化完成'); 199 return; 200 } 201 wx.onVoiceRecordEnd({ 202 complete: function(res) { 203 if (callback) callback(res.localId); 204 } 205 }); 206 }; 207 208 /** 209 * 播放語音 210 */ 211 QCWX.prototype.playVoice = function(localId) { 212 var self = this; 213 if (!self.ready) { 214 console.error('還沒有初始化完成'); 215 return; 216 } 217 wx.playVoice({ 218 localId: localId 219 }); 220 }; 221 222 /** 223 * 暫停播放語音 224 */ 225 QCWX.prototype.pauseVoice = function(localId) { 226 var self = this; 227 if (!self.ready) { 228 console.error('還沒有初始化完成'); 229 return; 230 } 231 wx.pauseVoice({ 232 localId: localId 233 }); 234 }; 235 236 /** 237 * 暫停播放語音 238 */ 239 QCWX.prototype.stopVoice = function(localId) { 240 var self = this; 241 if (!self.ready) { 242 console.error('還沒有初始化完成'); 243 return; 244 } 245 wx.stopVoice({ 246 localId: localId 247 }); 248 }; 249 250 /** 251 * 監聽語音播放完畢 252 */ 253 QCWX.prototype.onVoicePlayEnd = function(callback) { 254 var self = this; 255 if (!self.ready) { 256 console.error('還沒有初始化完成'); 257 return; 258 } 259 wx.onVoicePlayEnd({ 260 success: function (res) { 261 if (callback) callback(res.localId); 262 } 263 }); 264 }; 265 266 /** 267 * 上傳語音,有效期爲3天 268 */ 269 QCWX.prototype.uploadVoice = function(localId, isShowProgressTips, callback) { 270 var self = this; 271 if (!self.ready) { 272 console.error('還沒有初始化完成'); 273 return; 274 } 275 wx.uploadVoice({ 276 localId: localId, 277 isShowProgressTips: isShowProgressTips ? 1 : 0, 278 success: function(res) { 279 if (callback) callback(res.serverId); 280 } 281 }); 282 }; 283 284 /** 285 * 下載語音 286 */ 287 QCWX.prototype.downloadVoice = function(serverId, isShowProgressTips, callback) { 288 var self = this; 289 if (!self.ready) { 290 console.error('還沒有初始化完成'); 291 return; 292 } 293 wx.downloadVoice({ 294 serverId: serverId, 295 isShowProgressTips: isShowProgressTips ? 1 : 0, 296 success: function(res) { 297 if (callback) callback(res.localId); 298 } 299 }); 300 }; 301 302 /** 303 * 語音識別 304 */ 305 QCWX.prototype.translateVoice = function(localId, isShowProgressTips, callback) { 306 var self = this; 307 if (!self.ready) { 308 console.error('還沒有初始化完成'); 309 return; 310 } 311 wx.translateVoice({ 312 localId: localId, 313 isShowProgressTips: isShowProgressTips ? 1 : 0, 314 success: function(res) { 315 if (callback) callback(res.translateResult); 316 } 317 }); 318 }; 319 320 /** 321 * 獲取網絡狀態:2g 3g 4g wifi 322 */ 323 QCWX.prototype.getNetworkType = function(callback) { 324 var self = this; 325 if (!self.ready) { 326 console.error('還沒有初始化完成'); 327 return; 328 } 329 wx.getNetworkType({ 330 success: function(res) { 331 if (callback) callback(res.networkType); 332 } 333 }); 334 }; 335 336 /** 337 * 查看位置 338 */ 339 QCWX.prototype.openLocation = function(lat, lng, name, address, scale, infoUrl) { 340 var self = this; 341 if (!self.ready) { 342 console.error('還沒有初始化完成'); 343 return; 344 } 345 lat = lat || 0; 346 lng = lng || 0; 347 scale = scale || 1; 348 name = name || ''; 349 address = address || ''; 350 infoUrl = infoUrl || ''; 351 wx.openLocation({ 352 latitude: lat, 353 longitude: lng, 354 name: name, 355 address: address, 356 scale: scale, 357 infoUrl: infoUrl 358 }); 359 }; 360 361 /** 362 * 獲取當前位置 363 * @param {string} type - 'wgs84'(默認),'gcj02'(火星座標) 364 * 返回的結果中,包含以下信息: 365 * latitude 366 * longitude 367 * speed 368 * accuracy 369 */ 370 QCWX.prototype.getLocation = function(type, callback) { 371 var self = this; 372 if (!self.ready) { 373 console.error('還沒有初始化完成'); 374 return; 375 } 376 type = type || 'wgs84'; 377 wx.getLocation({ 378 type: type, 379 success: callback 380 }); 381 }; 382 383 /** 384 * 微信掃一掃 385 */ 386 QCWX.prototype.scanQRCode = function(needResult, callback) { 387 var self = this; 388 if (!self.ready) { 389 console.error('還沒有初始化完成'); 390 return; 391 } 392 wx.scanQRCode({ 393 needResult: needResult, 394 scanType: ["qrCode","barCode"], 395 success: function(res) { 396 if (callback) callback(res.resultStr); 397 } 398 }); 399 }; 400 401 /** 402 * 關閉當前網頁 403 */ 404 QCWX.prototype.closeWindow = function() { 405 var self = this; 406 if (!self.ready) { 407 console.error('還沒有初始化完成'); 408 return; 409 } 410 wx.closeWindow(); 411 }; 412 413 /** 414 * 微信支付 415 */ 416 QCWX.prototype.chooseWXPay = function() { 417 // 後續增長 418 };
而QcWeChat.js腳本的主要功能是微信登陸、配置遊戲服務器存放的域名、獲取登陸用戶的信息等,腳本代碼以下:
1 var QcWeChat = qc.defineBehaviour('qc.QcWeChat', qc.Behaviour, function() { 2 var self = this; 3 4 qc.qcWeChat = this; 5 6 /** 7 * @property {string} shareAppId - 用於分享的微信公衆號的appid 8 */ 9 self.shareAppId = ''; 10 11 /** 12 * @property {string} gameName - 遊戲名字 13 */ 14 self.gameName = ''; 15 16 /** 17 * @property {string} wxAppId - 用於登陸的微信公衆號的appid 18 */ 19 self.wxAppId = ''; 20 21 /** 22 * @property {string} webAppId - 網站應用的appid 23 */ 24 self.webAppId = ''; 25 26 /** 27 * @property {string} domain 28 * 域名(存放php文件的域名地址,例如:http://engine.zuoyouxi.com/wx/) 29 * 域名最後面以 '/' 結束 30 */ 31 self.domain = ''; 32 33 /** 34 * @property {string} gameDomain 35 * 遊戲服務器存放的域名(即放game_client文件的域名地址) 36 * 例如: http://engine.zuoyouxi.com/teris/ 37 */ 38 self.gameDomain = ''; 39 40 /** 41 * @property {string} extendParams 42 * 微信登陸時的擴展參數,格式爲json字符串,可用於傳遞一些自定義信息 43 * 例如: {"game":1} 44 */ 45 self.extendParams = ''; 46 47 /** 48 * @property {boolean} redirectCurrentUrl 49 * = true:使用遊戲頁直接換取code。當在微信公衆號後臺配置了遊戲域名(gameDomain)爲回調地址時採用 50 * = false:使用this.domain + 'code.php'做爲接收code的回調頁,以後再跳轉到本頁面。當微信公衆號後臺配置的是domain時採用 51 * 這種狀況下,遊戲的域名和公衆號後臺配置的能夠是不同的,而且多個遊戲能夠共用一個公衆號的信息。缺點是瀏覽器會有兩次跳轉 52 */ 53 self.redirectCurrentUrl = true; 54 55 /** 56 * @property {boolean} debug - 微信接口的debug是否打開,在發佈時必定要關閉哦 57 */ 58 self.debug = false; 59 60 /** 61 * 微信分享的接口實例 62 */ 63 self.wx = new qc.QCWX(); 64 window.QcWx = self.wx; 65 66 /** 67 * @property {qc.Signal} onInitWx - 初始化微信成功 68 */ 69 self.onInitWx = new qc.Signal(); 70 71 /** 72 * @property {qc.Signal} onStartLogin - 開始登陸的事件 73 */ 74 self.onStartLogin = new qc.Signal(); 75 76 /** 77 * @property {qc.Signal} onLogin - 登陸成功/失敗的事件 78 */ 79 self.onLogin = new qc.Signal(); 80 81 /** 82 * @property {qc.Signal} sessionExpired - 會話過時的事件 83 */ 84 self.sessionExpired = new qc.Signal(); 85 86 /** 87 * @type {qc.Signal} onShare - 用戶點擊分享的事件 88 */ 89 self.onShare = new qc.Signal(); 90 91 /** 92 * @property {object} user - 微信的用戶信息 93 * @readonly 94 */ 95 self.user = null; 96 97 /** 98 * @property {string} status - 當前的登陸狀態 99 * loggingIn - 登陸中 100 * loggedIn - 已登陸 101 * expired - 會話過時 102 */ 103 self.status = ''; 104 105 /** 106 * @property {string} shareLink - 分享連接地址 107 */ 108 self.shareLink = ""; 109 110 /** 111 * @property {object} _shareBody - 分享的內容 112 */ 113 self._shareBody = null; 114 115 /** 116 * @property {boolean} shareSignSuccess - 獲取分享簽名狀態 117 */ 118 self.shareSignSuccess = false; 119 120 /** 121 * @property {string} shareDir - 分享連接的目錄 122 */ 123 self.shareDir = ""; 124 125 }, { 126 gameName: qc.Serializer.STRING, 127 shareAppId: qc.Serializer.STRING, 128 wxAppId: qc.Serializer.STRING, 129 webAppId: qc.Serializer.STRING, 130 domain: qc.Serializer.STRING, 131 gameDomain: qc.Serializer.STRING, 132 shareDir: qc.Serializer.STRING, 133 redirectCurrentUrl: qc.Serializer.BOOLEAN, 134 debug: qc.Serializer.BOOLEAN 135 }); 136 //QcWeChat.__menu = 'Plugins/QcWeChat'; 137 138 // 初始化處理 139 QcWeChat.prototype.awake = function() { 140 // 請求籤名信息 141 var self = this; 142 if (!self.domain) return; 143 144 var url = self.domain + 'index.php?cmd=sign&appid=' + self.shareAppId + '&url=' + encodeURIComponent(window.location.href); 145 self.game.log.trace('開始請求微信分享的簽名信息:{0}', url); 146 qc.AssetUtil.get(url, function(r) { 147 self.game.log.trace('獲取簽名成功:' + r); 148 self.parseSign(r); 149 }, function() { 150 console.error('獲取簽名信息失敗'); 151 }); 152 153 // 加載js庫 154 self.loadWXLib(); 155 156 // 獲取code 157 self._code = this.getParam('code'); 158 159 self._state = this.getParam('state'); 160 if (self._code && (self.isWeChat() || this.game.device.desktop)) { 161 // 請求換取token,若是失敗須要從新請求登陸 162 self.status = 'loggingIn'; 163 self.game.timer.add(1, function() { 164 self.requestToken(self._code); 165 }); 166 } 167 }; 168 169 // 析構的處理 170 QcWeChat.prototype.onDestroy = function() { 171 if (this.timer) { 172 this.game.timer.remove(this.timer); 173 } 174 }; 175 176 /** 177 * 請求微信登陸 178 */ 179 QcWeChat.prototype.login = function() { 180 //if (this.isWeChat()) { 181 if (!this.game.device.desktop) { 182 this.loginInWX(); 183 return; 184 } 185 this.loginInWeb(); 186 }; 187 188 /** 189 * 調用微信受權 190 * @private 191 */ 192 QcWeChat.prototype._gotoAuth = function() { 193 var url = '', 194 redirectUri = window.location.origin + window.location.pathname; 195 if (this.redirectCurrentUrl) { 196 url = "https://open.weixin.qq.com/connect/oauth2/authorize?" + 197 "appid=" + this.wxAppId + 198 "&redirect_uri=" + encodeURIComponent(redirectUri) + 199 "&response_type=code&scope=snsapi_userinfo&state=weixin#wechat_redirect"; 200 } 201 else { 202 // 跳轉到code.php頁面,再跳轉回本頁面 203 url = "https://open.weixin.qq.com/connect/oauth2/authorize?" + 204 "appid=" + this.wxAppId + 205 "&redirect_uri=" + encodeURIComponent(this.domain + 'code.php') + 206 "&response_type=code&scope=snsapi_userinfo" + 207 "&state=" + encodeURIComponent(redirectUri) + 208 "#wechat_redirect"; 209 } 210 window.location.href = url; 211 } 212 // 微信內登錄 213 QcWeChat.prototype.loginInWX = function() { 214 // 若是在微信瀏覽器上 215 if (this.isWeChat()) { 216 this.requestToken(this._code); 217 return; 218 } 219 this._gotoAuth(); 220 }; 221 222 // 微信外登陸 223 QcWeChat.prototype.loginInWeb = function() { 224 var url = '', 225 redirectUri = window.location.origin + window.location.pathname; 226 if (this.redirectCurrentUrl) { 227 url = "https://open.weixin.qq.com/connect/qrconnect?" + 228 "appid=" + this.webAppId + 229 "&redirect_uri=" + encodeURIComponent(redirectUri) + 230 "&response_type=code&scope=snsapi_login&state=pc#wechat_redirect"; 231 } 232 else { 233 // 跳轉到code.php頁面,再跳轉回本頁面 234 url = "https://open.weixin.qq.com/connect/qrconnect?" + 235 "appid=" + this.webAppId + 236 "&redirect_uri=" + encodeURIComponent(this.domain + 'code.php') + 237 "&response_type=code&scope=snsapi_login" + 238 "&state=" + encodeURIComponent(redirectUri) + 239 "#wechat_redirect"; 240 } 241 window.location.href = url; 242 }; 243 244 // 解析簽名信息 245 QcWeChat.prototype.parseSign = function(r) { 246 var self = this; 247 var sign = JSON.parse(r); 248 self.timeStamp = sign.timestamp; 249 self.nonceStr = sign.nonceStr; 250 self.signature = sign.signature; 251 self.shareLink = sign.shareLink; 252 //window.QcWx.shareLink = self.shareLink; 253 254 if (!self.jweixin) { 255 // 微信接口還沒有載入,延遲繼續檢測 256 self.game.timer.add(500, function() { 257 self.parseSign(r); 258 }); 259 return; 260 } 261 262 // 調用微信的初始化接口 263 self.game.log.trace('開始初始化微信接口'); 264 self.wx.debug = self.debug; 265 self.wx.init({ 266 timeStamp: self.timeStamp, 267 nonceStr: self.nonceStr, 268 signature: self.signature, 269 appId: self.shareAppId 270 }, function() { 271 self.game.log.trace('初始化微信接口完成。'); 272 self.shareSignSuccess = true; 273 self.wx.share(self.onShare); 274 self.onInitWx.dispatch(); 275 }); 276 }; 277 278 // 動態加載wx的庫 279 QcWeChat.prototype.loadWXLib = function() { 280 var self = this; 281 var src = "http://res.wx.qq.com/open/js/jweixin-1.0.0.js"; 282 var js = document.createElement('script'); 283 js.onerror = function() { 284 console.error('加載jweixin庫失敗'); 285 }; 286 js.onload = function() { 287 // 標記加載完成了 288 self.game.log.trace('微信接口下載完成'); 289 self.jweixin = true; 290 }; 291 js.setAttribute('src', src); 292 js.setAttribute('type', 'text/javascript'); 293 document.getElementsByTagName('head')[0].appendChild(js); 294 }; 295 296 // 當前是否運行在微信客戶端 297 QcWeChat.prototype.isWeChat = function() { 298 var ua = window.navigator.userAgent.toLowerCase(); 299 return ua.match(/MicroMessenger/i) == 'micromessenger'; 300 }; 301 302 // 獲取url的參數 303 QcWeChat.prototype.getParam = function(key) { 304 var r = new RegExp("(\\?|#|&)" + key + "=([^&#]*)(&|#|$)"); 305 var m = location.href.match(r); 306 return decodeURIComponent(!m ? "" : m[2]); 307 }; 308 309 // 使用code換取token 310 QcWeChat.prototype.requestToken = function(code) { 311 //this.gameName = "Koala"; 312 var self = this, 313 url = self.gameDomain + "login03.php?code=" + code + "&gameName=" + self.gameName; 314 //if (!self.isWeChat()) url += "&web=1"; 315 if (this.game.device.desktop) url += "&web=1"; 316 317 self.onStartLogin.dispatch(); 318 qc.AssetUtil.get(url, function(r) { 319 var data = JSON.parse(r); 320 if (data.error) { 321 if (data.errorCode && data.errorCode == 301) { 322 // 跳轉到受權頁面 323 if (self.game.device.desktop) { 324 self.loginInWeb(); 325 return; 326 } 327 self._gotoAuth(); 328 return; 329 } 330 331 // 換取token失敗,從新請求登陸 332 self.game.log.error('換取token失敗,從新請求登陸'); 333 // 登錄失敗 不從新登錄 334 //self.login(); 335 self.onLogin.dispatch("fail"); 336 return; 337 } 338 339 // 登陸成功了,拋出事件 340 self.game.log.trace('登陸成功:{0}', r); 341 self.status = 'loggedIn'; 342 self.user = data; 343 self.onLogin.dispatch("success"); 344 345 // 按期刷新access_token,並保持會話 346 self.timer = self.game.timer.loop(5 * 60000, self.refreshToken, self); 347 }, function(r) { 348 self.onLogin.dispatch("fail"); 349 }); 350 }; 351 352 // 刷新token 353 QcWeChat.prototype.refreshToken = function() { 354 var self = this, 355 url = self.gameDomain + "refresh.php"; 356 //if (!self.isWeChat()) url += "?web=1"; 357 if (this.game.device.desktop) url += "?web=1"; 358 qc.AssetUtil.get(url, function(r) { 359 var data = JSON.parse(r); 360 if (data.error) { 361 // 刷新token失敗了,拋出事件 362 self.status = 'expired'; 363 self.game.timer.remove(self.timer); 364 delete self.timer; 365 self.sessionExpired.dispatch(); 366 return; 367 } 368 369 // 成功了,啥也不用處理 370 self.game.log.trace('刷新Access Token成功。'); 371 }); 372 };
將該腳本掛載到"歡迎界面"節點,掛載完成後以下圖所示:
其中Share App Id爲用於分享的微信公衆號的appid,Wx App Id 爲用於登陸的微信公衆號的appid,Web App Id爲網站應用的appid,Domain爲域名,Game Domain爲遊戲服務器存放的域名,更多詳細信息可查看《微信》。
6、遊戲界面
在前面咱們已經搭建好了開始界面,接下來咱們須要進入遊戲界面。遊戲界面我是這樣構思的,遊戲運行時,在登陸界面(即歡迎界面),遊戲界面是不顯示的,點擊"快速登陸"按鈕才讓遊戲界面顯示出來,此時相應的將開始界面隱藏。這個比較好實現,只要設置對象的visible屬性便可完成。首先咱們來介紹遊戲界面的佈局,遊戲界面的效果圖以下:
爲了與"遊戲場景"節點及"登陸界面(歡迎界面)"節點分離出來,我又新建立了一個UIRoot節點取名"遊戲場景","遊戲場景"節點下的子節點以下所示:
在"遊戲場景"節點下建立node/相機,node節點與相機節點(node與相機節點都是Empty Node)的節點屬性值設置以下:
下圖分別爲node節點與相機節點的屬性值:
這樣作的目的是,悠悠考拉是一個無盡的虛擬世界,世界的寬度不限。在遊戲中,爲了讓考拉一直處於屏幕中,即屏幕一直跟隨考拉,此時採用相機。在遊戲界面的效果圖咱們能夠看到有柱子、鞦韆、考拉、暫停按鈕及得分顯示區域。下面一一介紹:
6.1 柱子
柱子:根據策劃要求,在悠悠考拉遊戲中,有關卡概念,在不一樣的關卡,柱子的粗細是不一樣的,而且考拉跳的柱子(跳臺)高度也不盡相同,並且考拉跳到柱子上時有個得分區域,降落離中心區域越近就得分越高。若是將這些數據配置在程序中的話,將不便於修改及查看,故我將這些數據配置在Excel表格中,須要說明的是,我在Excel表格中配置了兩張sheet表,分別爲config、pillar表,其中config中的配置的柱子數據爲默認數據,pillar表中的數據爲關卡數據會根據config中默認數據做相應改變,具體如表所示:
config表:
表中#爲註釋,其他數據按字面意思便可知道,須要說明的是pillarTopMin是指三根柱子的父親節點(遊戲中我使用三根柱子循環移動),pillarTopMax是指跳臺的高度(即考拉降落的柱子),pillarHeadIcon是指柱帽默認圖片資源名稱。
pillar表:
其中minLv與maxLv爲關卡等級,看字面意思應該能夠理解。thickness爲柱子粗細百分比,在遊戲中,是這樣計算的,好比關卡等級爲3級,則柱子的寬度爲0.75*200(config表中的柱子默認寬度),而top表示柱子上邊距百分比,如關卡等級爲3級,則跳臺的高度爲1*510(config表中的跳臺默認高度),而headIcon爲柱帽對應柱子粗細的圖片資源名稱,scoreRect爲得分區域。
配置了這些數據後,咱們須要將這些數據利用代碼讀取出來並存放到數組中,以便咱們在遊戲中讀取。首先解析config sheet表數據,在Scripts/logic文件夾下建立腳本:Config.js,代碼以下:
1 var Config = qc.Koala.logic.Config = function(excel) { 2 if (!excel) { 3 excel = qc.Koala.game.assets.load('config'); 4 } 5 6 var sheet = excel.findSheet('config'); 7 if (sheet) { 8 sheet.rows.forEach(function(row) { 9 var val = row.value; 10 if (row.type === 'number') 11 val *= 1; 12 this[row.key] = val; 13 }, this); 14 } 15 };
而後咱們也須要解析pillar sheet表數據,在Scripts/login文件夾下建立腳本:Pillar.js,代碼以下:
1 var PillarInfo = function(row) { 2 this.id = row.id * 1; 3 this.minLv = row.minLv * 1; 4 this.maxLv = row.maxLv * 1 || Infinity; 5 this.thickness = row.thickness * 1; 6 this.top = row.top * 1; 7 this.headIcon = row.headIcon; 8 this.scoreRect = row.scoreRect * 1 || Infinity; 9 }; 10 11 var Pillar = qc.Koala.logic.Pillar = function(excel) { 12 // 柱子信息列表 13 this.infoList = []; 14 15 // 關卡與柱子粗細值對應表 16 this.infoMap = {}; 17 18 if (!excel) { 19 excel = qc.Koala.game.assets.load('config'); 20 } 21 22 var sheet = excel.findSheet('pillar'); 23 if (sheet) { 24 sheet.rows.forEach(function(row) { 25 this.infoList.push(new PillarInfo(row)); 26 }, this); 27 } 28 };
將Pillar類與Config類實例化,在入口腳本Koala.js的Koala.initLogic方法中加入代碼,以下:
1 Koala.initLogic = function(excel, game) { 2 3 // 設置遊戲對象引用 4 this.game = game; 5 6 // 設置遊戲幀率爲60幀 7 game.time.frameRate = 60; 8 9 // 初始化系統配置 10 this.logic.config = new qc.Koala.logic.Config(excel); 11 12 // 遊戲相關數據邏輯類 13 this.logic.me = new qc.Koala.logic.Me(); 14 15 // 柱子相關邏輯類 16 this.logic.pillar = new qc.Koala.logic.Pillar(excel); 17 18 };
根據策劃要求,但願在遊戲中可以模擬現實世界,考拉在盪鞦韆的時候會有風速,風速對考拉的速度是會有影響的,並且隨着關卡的不一樣其風速也不相同,故咱們也能夠將這些數據配置到Excel表中,以下:
表中的數據不難理解,由表中配置的數據咱們可知,在關卡等級1-3級是沒有風速的,其它等級風速則是隨着關卡等級的越大相應增大。此時咱們也須要建立腳本用於解析風速(wind sheet表),在Scripts/logic文件夾下建立腳本:Wind.js,代碼以下:
1 var WindInfo = function(row) { 2 this.id = row.id * 1; 3 this.minLv = row.minLv * 1; 4 this.maxLv = row.maxLv * 1 || Infinity; 5 this.minWind = row.minWind * 1; 6 this.maxWind = row.maxWind * 1; 7 }; 8 9 var Wind = qc.Koala.logic.Wind = function(excel) { 10 // 風力信息列表 11 this.infoList = []; 12 13 // 風力範圍速查表 14 this.infoMap = {}; 15 16 if (!excel) { 17 excel = qc.Koala.game.assets.load('config'); 18 } 19 20 var sheet = excel.findSheet('wind'); 21 if (sheet) { 22 sheet.rows.forEach(function(row) { 23 this.infoList.push(new WindInfo(row)); 24 }, this); 25 } 26 };
一樣地,咱們也須要在入口腳本Koala.js的Koala.initLogic方法中加入代碼,將Wind類實例化,代碼以下:
1 Koala.initLogic = function(excel, game) { 2 3 // 設置遊戲對象引用 4 this.game = game; 5 6 // 設置遊戲幀率爲60幀 7 game.time.frameRate = 60; 8 9 // 初始化系統配置 10 this.logic.config = new qc.Koala.logic.Config(excel); 11 12 // 遊戲相關數據邏輯類 13 this.logic.me = new qc.Koala.logic.Me(); 14 15 // 柱子相關邏輯類 16 this.logic.pillar = new qc.Koala.logic.Pillar(excel); 17 18 // 風力值邏輯類 19 this.logic.wind = new qc.Koala.logic.Wind(excel); 20 21 };
在前面咱們將柱子作成了預製,此時能夠直接拿來用,遊戲中使用三個柱子循環移動位置。在"相機"節點下建立一個Empty Node取名"柱子集",遊戲中產生的柱子將直接掛載該節點下。須要說明的是遊戲時鞦韆與柱子是成對出現的,故咱們也能夠將鞦韆弄成預製,鞦韆的預製取名爲"swing"。思路是,在建立柱子的同時也建立秋千,故咱們能夠在柱子及鞦韆上分別掛載腳本。須要說明的是,將Config類、Pillar類實例化後,咱們能夠在入口腳本中的Koala.initLogic方法中添加一個事件派發this.onLogicReady.dispatch(),而相應地咱們能夠在"柱子集"節點上掛載一個腳本,用於監聽事件派發,建立柱子,腳本代碼以下:
1 // 柱子池 2 var PillarPool = qc.defineBehaviour('qc.Koala.ui.PillarPool', qc.Behaviour, function() { 3 this.pillarList = []; 4 }, { 5 // 柱子預製 6 pillarPrefab : qc.Serializer.PREFAB 7 }); 8 9 PillarPool.prototype.awake = function() { 10 this.addListener(qc.Koala.onLogicReady, this._init, this); 11 }; 12 13 /** 14 * 初始化柱子 15 */ 16 PillarPool.prototype._init = function() { 17 // 設置柱子池高度 18 this.gameObject.top = qc.Koala.logic.config.pillarTopMin; 19 20 var prePillar = null; 21 for (var i = 0; i < 3; i++) { 22 var pillar = this.pillarList[i] || this.createPillar(); 23 if (prePillar) { 24 pillar.init(prePillar, i); 25 } 26 else { 27 pillar.init(null, i); 28 } 29 prePillar = pillar; 30 this.pillarList[i] = pillar; 31 } 32 33 qc.Koala.onPillarReady.dispatch(this.pillarList); 34 }; 35 36 /** 37 * 建立柱子 38 */ 39 PillarPool.prototype.createPillar = function() { 40 var node = this.game.add.clone(this.pillarPrefab, this.gameObject); 41 return node.getScript('qc.Koala.ui.Pillar'); 42 };
將該腳本掛載到"柱子集"節點上,將柱子預製拖入對對應屬性值,以下圖:
柱子預製腳本:咱們在上面的柱子配置表能夠看出不一樣的關卡等級,柱子的粗細不盡相同,故咱們能夠建立一個腳本用於在不一樣的關卡等級正確的顯示柱子,在Scripts/ui文件夾下建立腳本:Pillar.js,該腳本的主要功能是初始化柱子自己及建立秋千對象,代碼以下:
1 // 柱子類 2 var Pillar = qc.defineBehaviour('qc.Koala.ui.Pillar', qc.Behaviour, function() { 3 this.swing = null; 4 5 // 分數區域 6 this.scoreRect = Infinity; 7 }, { 8 // 鞦韆預製 9 swingPrefab : qc.Serializer.PREFAB, 10 // 柱子背景 11 bg : qc.Serializer.NODE, 12 // 柱頭 13 head : qc.Serializer.NODE 14 }); 15 16 /** 17 * 初始化柱子 18 * @param {number} start - 上一個柱子的x軸座標 19 */ 20 Pillar.prototype.init = function(prePillar, level) { 21 // 獲取柱子的寬度和上邊距信息 22 var info = qc.Koala.logic.pillar.getInfo(level); 23 this.gameObject.width = info.thickness; 24 25 // 初始化分數區域 26 this.scoreRect = info.scoreRect; 27 28 // 初始化柱子背景 29 this.initBg(info.thickness); 30 31 // 初始化柱帽 32 this.initHead(info.headIcon); 33 34 // 設置柱子的上邊距和左邊距 35 this.gameObject.y = info.top - this.gameObject.parent.y; 36 if (prePillar == null) { 37 this.gameObject.x = 0; 38 } 39 else { 40 this.gameObject.x = prePillar.gameObject.x + qc.Koala.GAMEWIDTH - this.gameObject.width; 41 this.gameObject.y += prePillar.gameObject.y; 42 } 43 44 // 建立秋千對象 45 if (!this.swing) 46 this.swing = this.createSwing(); 47 48 // 初始化鞦韆 49 this.initSwing(); 50 }; 51 52 /** 53 * 初始化柱子背景 54 * @param {number} width - 柱子寬度 55 */ 56 Pillar.prototype.initBg = function (width) { 57 var nativeWidth = this.bg.nativeSize.width, 58 ratio = width / nativeWidth, 59 bottom = this.bg.parent.height * (1 - ratio), 60 right = nativeWidth * (1 - ratio); 61 this.bg.scaleX = this.bg.scaleY = ratio; 62 this.bg.bottom = -bottom; 63 this.bg.right = -right; 64 }; 65 66 /** 67 * 初始化柱帽圖片資源 68 * @param {string} headIcon - 柱帽圖片資源名稱 69 */ 70 Pillar.prototype.initHead = function (headIcon) { 71 this.head.frame = headIcon + '.png'; 72 }; 73 74 /** 75 * 建立秋千對象 76 * @return {qc.Koala.ui.Swing} 77 */ 78 Pillar.prototype.createSwing = function() { 79 var node = this.game.add.clone(this.swingPrefab, this.gameObject.parent.parent); 80 return node.getScript('qc.Koala.ui.Swing'); 81 }; 82 83 /** 84 * 初始化鞦韆 85 */ 86 Pillar.prototype.initSwing = function() { 87 this.swing.init(this); 88 };
將該腳本掛載到柱子預製上,並將鞦韆預製拖入到對應的屬性,其中Bg、Head爲柱子軀幹及柱頭,以下圖:
但是咱們剛纔配置的柱子數據所有保存在Scripts/logic文件夾中的Pillar類中,在Scripts/ui文件夾下的Pillar.js怎麼才能獲取正確的柱子信息呢?咱們能夠這樣作,在Scripts/logic文件夾中的Pillar類中加入方法,經過Scripts/ui文件夾下Pillar.js提供的參數level從而查詢配置表返回相應關卡等級的柱子信息,代碼以下:
1 var PillarInfo = function(row) { 2 this.id = row.id * 1; 3 this.minLv = row.minLv * 1; 4 this.maxLv = row.maxLv * 1 || Infinity; 5 this.thickness = row.thickness * 1; 6 this.top = row.top * 1; 7 this.headIcon = row.headIcon; 8 this.scoreRect = row.scoreRect * 1 || Infinity; 9 }; 10 11 var Pillar = qc.Koala.logic.Pillar = function(excel) { 12 // 柱子信息列表 13 this.infoList = []; 14 15 // 關卡與柱子粗細值對應表 16 this.infoMap = {}; 17 18 if (!excel) { 19 excel = qc.Koala.game.assets.load('config'); 20 } 21 22 var sheet = excel.findSheet('pillar'); 23 if (sheet) { 24 sheet.rows.forEach(function(row) { 25 this.infoList.push(new PillarInfo(row)); 26 }, this); 27 } 28 }; 29 30 /** 31 * 獲取柱子粗細值 32 * @return {number} 33 */ 34 Pillar.prototype.getInfo = function(level) { 35 var info = this.infoMap[level]; 36 if (!info) { 37 var p = this._find(level); 38 info = { 39 thickness : qc.Koala.logic.config.pillarWidth, 40 top : qc.Koala.logic.config.pillarTopMin, 41 headIcon : qc.Koala.logic.config.pillarHeadIcon, 42 scoreRect : Infinity 43 }; 44 if (p) { 45 info.thickness *= p.thickness; 46 info.top = p.top * qc.Koala.logic.config.pillarTopMax; 47 info.headIcon = p.headIcon; 48 info.scoreRect = p.scoreRect; 49 } 50 this.infoMap[level] = info; 51 } 52 return info; 53 }; 54 55 /** 56 * 遍歷獲取柱子粗細百分比 57 * @param {number} level - 關卡數 58 * @return {number} 59 */ 60 Pillar.prototype._find = function(level) { 61 for (var i = 0, len = this.infoList.length; i < len; i++) { 62 var info = this.infoList[i]; 63 if (level < info.minLv) 64 continue; 65 if (level >= info.minLv && level <= info.maxLv) 66 return info; 67 } 68 return null; 69 };
6.2 鞦韆
鞦韆:在遊戲中,當考拉尚未抓住鞦韆時,鞦韆是有一個初始的狀態。考拉抓住鞦韆時,鞦韆要作搖擺運動,搖擺運動咱們可使用引擎提供的TweenRotation動畫,效果圖分別以下所示:
考拉沒有抓住鞦韆時,鞦韆的狀態效果圖:
考拉抓住鞦韆時,鞦韆的效果圖:
爲了實現這兩種效果,咱們能夠根據鞦韆對應的柱子肯定鞦韆的位置及旋轉的角度。首先掛載一個TweenRotation動畫,以下圖所示:
該TweenRotation動畫主要的功能是讓鞦韆作搖擺運動,其中From值與To值是根據鞦韆對應的柱子所決定的,設置其Play Style爲PingPong(來回播放),持續時間是1.1秒,更多關於Tween動畫屬性可參看《Tween動畫》。
爲了實現上述的效果,咱們能夠經過腳原本控制,在Scripts/ui文件夾下建立腳本:Swing.js,將該腳本掛載到"swing"節點上,腳本代碼以下:
1 var Swing = qc.defineBehaviour('qc.Koala.ui.Swing', qc.Behaviour, function() { 2 // 鞦韆最大擺角 3 this._maxRotation = 0; 4 5 // 方向 6 this.direction = 1; 7 8 this.deltaRotation = Math.PI / 180 * 5; 9 10 this.beginRotation = 0; 11 }, { 12 }); 13 14 Object.defineProperties(Swing.prototype, { 15 /** 16 * 鞦韆最大擺角 17 * @type {number} 18 */ 19 maxRotation : { 20 get : function() { return this._maxRotation; }, 21 set : function(v) { 22 if (this._maxRotation === v) return; 23 24 this._maxRotation = v; 25 26 var s = this.gameObject.getScript('qc.TweenRotation'); 27 s.from = v; 28 s.to = -v; 29 } 30 } 31 }); 32 33 /** 34 * 初始化 35 */ 36 Swing.prototype.awake = function() { 37 var s = this.gameObject.getScript('qc.TweenRotation'); 38 this.addListener(s.onLoopFinished, this._onSwingFinished, this); 39 }; 40 41 /** 42 * 鐘擺循環結束後,更新方向值 43 */ 44 Swing.prototype._onSwingFinished = function() { 45 this.direction *= -1; 46 }; 47 48 /** 49 * 初始化鞦韆 50 * @param {qc.Koala.ui.Pillar} pillar - 柱子對象 51 */ 52 Swing.prototype.init = function(pillar) { 53 this.gameObject.anchoredX = pillar.gameObject.x; 54 this.gameObject.y = pillar.gameObject.y; 55 56 // 計算三角形的寬高 57 var height = pillar.gameObject.parent.y; 58 var width = qc.Koala.GAMEWIDTH * 0.5 - pillar.gameObject.width; 59 60 // 計算鞦韆最大擺角 61 this.beginRotation = Math.atan(width / height); 62 63 this.maxRotation = this.beginRotation + this.deltaRotation; 64 65 this.gameObject.height = Math.sqrt(width * width + height * height); 66 67 // 重置鞦韆位置 68 this.reset(); 69 }; 70 71 /** 72 * 播放鐘擺動畫 73 * @param {boolean} con - 是否從上一次暫停的地方開始播放 74 */ 75 Swing.prototype.play = function(con) { 76 if (!con) 77 qc.Tween.resetGroupToBeginning(this.gameObject, 2); 78 qc.Tween.playGroup(this.gameObject, 2); 79 }; 80 81 /** 82 * 中止鐘擺動畫 83 */ 84 Swing.prototype.stop = function () { 85 qc.Tween.stopGroup(this.gameObject, 2); 86 }; 87 88 /** 89 * 回到起點 90 */ 91 Swing.prototype.reset = function() { 92 qc.Tween.stopGroup(this.gameObject, 2); 93 qc.Tween.resetGroupToBeginning(this.gameObject, 2); 94 this.gameObject.rotation = this.beginRotation; 95 96 this.direction = 1; 97 };
把柱子和鞦韆弄完後,咱們此時就須要請出悠悠考拉遊戲的主角登場了。在"相機"節點下建立一個Sprite節點取名"koala",該節點的位置是不固定的,由於在遊戲中,隨着柱子位置的不一樣,koala的位置也不一樣。後續在代碼中會講明。咱們須要建立一個腳本:用於管理考拉的幀動畫(前面咱們已經講述了製做考拉在遊戲中的各類動做),在Scripts/ui下建立一個腳本:Koala.js,將該節點掛載到"koala"節點,代碼以下所示:
1 var Koala = qc.defineBehaviour('qc.Koala.ui.Koala', qc.Behaviour, function() { 2 // 鞦韆對象 3 this.swingScript = null; 4 5 // 考拉當前播放的動做 6 this.currAnimation = 'stand'; 7 }, { 8 // 相機節點 9 camera : qc.Serializer.NODE, 10 // 特效節點 11 effect : qc.Serializer.NODE, 12 // 文本 13 labelImg : qc.Serializer.NODE, 14 // 分數 15 scoreImg : qc.Serializer.NODE, 16 // 死亡效果圖片 17 dieImg : qc.Serializer.NODE, 18 // 剎車效果圖片 19 brakeImg : qc.Serializer.NODE 20 }); 21 22 /** 23 * 站立 24 */ 25 Koala.prototype.stand = function() { 26 this.currAnimation = 'stand'; 27 this.gameObject.playAnimation('stand'); 28 }; 29 30 /** 31 * 走 32 */ 33 Koala.prototype.walk = function() { 34 // 隱藏剎車效果 35 this.brakeImg.visible = false; 36 37 this.labelImg.getScript('qc.TweenAlpha').onFinished.removeAll(this); 38 39 this.currAnimation = 'walk'; 40 this.gameObject.playAnimation('walk'); 41 42 var s = this.gameObject.getScript('qc.TweenPosition'); 43 s.onFinished.addOnce(this.take, this); 44 s.resetToBeginning(); 45 s.play(); 46 }; 47 48 /** 49 * 拿鞦韆 50 */ 51 Koala.prototype.take = function() { 52 // 拿鞦韆動做結束後處理 53 this.gameObject.onFinished.addOnce(function() { 54 // 設置考拉在鞦韆上的位置 55 this.gameObject.parent = this.swingScript.gameObject; 56 this.gameObject.anchoredX = 0; 57 this.gameObject.anchoredY = 0; 58 this.gameObject.rotation = 0; 59 60 // 設置考拉狀態 61 this.swing(); 62 63 // 派發拿起鞦韆事件 64 qc.Koala.onSwingTake.dispatch(); 65 }, this); 66 67 this.currAnimation = 'take'; 68 // 播放拿鞦韆動做 69 this.gameObject.playAnimation('take'); 70 }; 71 72 /** 73 * 盪鞦韆 74 */ 75 Koala.prototype.swing = function() { 76 if (qc.Koala.logic.me.paused) return; 77 this.swingScript.play(true); 78 this.currAnimation = 'swing'; 79 this.gameObject.playAnimation('swing'); 80 }; 81 82 /** 83 * 放開秋千 84 */ 85 Koala.prototype.away = function() { 86 this.gameObject.switchParent(this.camera); 87 this.gameObject.rotation = 0; 88 89 this.currAnimation = 'away'; 90 this.gameObject.playAnimation('away'); 91 };
標題區域:標題區域由暫停按鈕、方向區域、分數區域所組成;在"node"節點下建立一個Empty Node取名"標題區域",爲了讓在該節點下的子節點在不一樣分辨率顯示時,都可以正常顯示。將該節點設置爲向上對齊左右拉伸,故設置該節點的屬性值以下:
在"標題區域"節點下依次建立暫停按鈕節點、方向區域節點信息(其中方向區域由方向標識、風值、風值單位節點所構成)、分數區域節點信息。建立好後,效果圖以下:
怎樣得分:根據策劃要求,考拉成功降落在跳臺上時,做相應的加分,這裏的相應加分,是在前面的柱子pillar sheet配置表中,咱們配置了scoreRect(得分區域),若是考拉降落到柱子上時,離柱子中心區域越近則得分越高,並且策劃還要求要有加分圖標及特效效果,一樣地咱們也能夠將這些數據配置到前面的Excel表中,在前面的Excel表中加入一個sheet表命名score,配置的數據以下:
其中表格中的min與max表示考拉成功降落時離柱子中心的距離從而作相應的加分及貼圖,其中labelImg、scoreImg爲圖片資源的名稱,而effectName爲特效動畫名稱。一樣地咱們也須要建立一個腳本用於解析該表,在Scripts/logic文件夾下建立腳本:Score.js,腳本代碼以下:
1 var ScoreInfo = function(row) { 2 this.id = row.id * 1; 3 this.min = row.min * 1; 4 this.max = row.max * 1; 5 this.value = row.value * 1; 6 7 this.scoreImg = row.scoreImg; 8 this.labelImg = row.labelImg; 9 10 this.effectName = row.effectName; 11 }; 12 13 var Score = qc.Koala.logic.Score = function(excel) { 14 // 分數信息列表 15 this.infoList = []; 16 17 // 默認的分數信息 18 this.defaultInfo = null; 19 20 if (!excel) { 21 excel = qc.Koala.game.assets.load('config'); 22 } 23 24 var sheet = excel.findSheet('score'); 25 if (sheet) { 26 sheet.rows.forEach(function(row) { 27 this.infoList.push(new ScoreInfo(row)); 28 }, this); 29 } 30 };
實例化Score類,在入口腳本Koala.js的Koala.initLogic方法中加入代碼,以下:
1 Koala.initLogic = function(excel, game) { 2 3 // 設置遊戲對象引用 4 this.game = game; 5 6 // 設置遊戲幀率爲60幀 7 game.time.frameRate = 60; 8 9 // 初始化系統配置 10 this.logic.config = new qc.Koala.logic.Config(excel); 11 12 // 遊戲相關數據邏輯類 13 this.logic.me = new qc.Koala.logic.Me(); 14 15 // 柱子相關邏輯類 16 this.logic.pillar = new qc.Koala.logic.Pillar(excel); 17 18 // 風力值邏輯類 19 this.logic.wind = new qc.Koala.logic.Wind(excel); 20 21 // 分數相關邏輯類 22 this.logic.score = new qc.Koala.logic.Score(excel); 23 24 // 分享相關邏輯類 25 this.logic.share = new qc.Koala.logic.Share(excel); 26 27 // 派發腳本準備就緒事件 28 this.onLogicReady.dispatch(); 29 };
但是咱們在遊戲中怎麼去獲取分數呢?咱們能夠在Score類中提供一個接口,該接口會返回玩家所得的分數,代碼以下:
1 /** 2 * 獲取分數 3 * @param {number} distance - 考拉降落點到柱子中心的距離 4 * @param {number} scoreRect - 柱子的分數區域 5 * @return {number} 6 */ 7 Score.prototype.getScore = function(distance, scoreRect) { 8 console.log('scoreRect:',scoreRect); 9 // 若是超出分數區域,則返回遊戲最小分 10 if (distance > scoreRect) { 11 if (!this.defaultInfo) 12 this.defaultInfo = { 13 value : qc.Koala.logic.config.minScore, 14 labelImg : qc.Koala.logic.config.labelImg, 15 scoreImg : qc.Koala.logic.config.scoreImg 16 }; 17 return this.defaultInfo; 18 } 19 20 // 經過距離計算得分 21 var info = null; 22 for (var i = 0, len = this.infoList.length; i < len; i++) { 23 info = this.infoList[i]; 24 if (distance > info.min && distance <= info.max) 25 break; 26 } 27 return info; 28 };
遊戲界面中的大部分節點都已經建立好了,但是怎麼讓考拉鬆手時與柱子發生碰撞呢?怎麼讓屏幕一直跟着考拉呢?怎麼讓遊戲背景也跟着移動呢?。。。。等等等,接下來我就一一講述。
物理表現:在虛擬世界中,考拉有本身的位置,水平和垂直方向上的速度。在遊戲中,考拉在鞦韆上作鐘擺運動,點擊屏幕時考拉鬆手,須要計算它的垂直速度與水平速度,須要說明的是代碼中的qc.Koala.logic.config.g是個常量,我將這個數據配置到config sheet表中,代碼以下:
1 /** 2 * 監聽點擊事件 3 */ 4 Main.prototype.onClick = function() { 5 // 禁止交互 6 this.gameObject.interactive = false; 7 8 // 關卡數加1 9 qc.Koala.logic.me.level++; 10 11 // 計算考拉下落高度 12 var rotation = this.swing.gameObject.rotation, 13 cos = Math.cos(rotation), 14 sin = Math.sin(rotation), 15 radius = this.swing.gameObject.height, 16 h = radius * (cos - Math.cos(this.swing.maxRotation)); 17 18 // 計算橫向及縱向速度 19 var dir = this.swing.direction, 20 v0 = Math.sqrt(2 * qc.Koala.logic.config.g * h) * dir, 21 vx0 = v0 * cos + this.windValue, 22 vy0 = v0 * sin; 23 24 25 // 獲取考拉腳本對象 26 var koalaScript = this.koala.getScript('qc.Koala.ui.Koala'); 27 // 考拉放手 28 koalaScript.away(); 29 30 // 獲取跳臺對象 31 var pool = this.pillarPool.getScript('qc.Koala.ui.PillarPool'); 32 this._step = pool.getStep(); 33 34 // 考拉作拋物線運動 35 this.drop(vx0, vy0); 36 }; 37 38 /** 39 * 考拉脫離繮繩開始掉落 40 * @param {number} vx0 - x方向初始速度 41 * @param {number} vy0 - y方向初始速度 42 */ 43 Main.prototype.drop = function(vx0, vy0) { 44 // 循環定時器刷新考拉位置 45 this.dropTimer = this.game.timer.loop(0, function() { 46 if (qc.Koala.logic.me.paused) 47 return; 48 // 計算縱向速度 49 var t = this.game.time.deltaTime * 0.001; 50 vy0 = vy0 + qc.Koala.logic.config.g * t; 51 52 // 考拉掉落處理 53 this._onDrop(vx0, vy0, t); 54 }, this); 55 }; 56 57 /** 58 * 考拉掉落幀處理 59 * @param {number} vx0 - 橫向速度 60 * @param {number} vy0 - 縱向速度 61 * @param {number} t - 倆幀之間的時間間隔 62 */ 63 Main.prototype._onDrop = function (vx0, vy0, t) { 64 // 計算橫向和縱向偏移值 65 var preY = this.koala.y, 66 deltaX = vx0 * t, 67 deltaY = vy0 * t; 68 69 // 設置考拉位置 70 this.koala.x += deltaX; 71 this.koala.y += deltaY; 72 73 // 調整相機位置 74 this.adjustCamera(deltaX, deltaY); 75 76 // 檢測碰撞 77 var result = this._checkCollide(preY); 78 if (result !== 0) { 79 // 移除定時器 80 this.game.timer.remove(this.dropTimer); 81 this.dropTimer = null; 82 83 // 成功跳到下一個站臺 84 if (result === 1) { 85 this._onStep(); 86 } 87 88 // 遊戲結束 89 if (result < 0) { 90 this.gameOver(result); 91 } 92 } 93 };
視野移動:在遊戲中,爲了讓考拉一直處於屏幕中,即屏幕一直跟隨考拉,此時採用相機,作法是:將柱子、考拉及鞦韆所有掛載到相機節點下,當考拉脫離繮繩開始掉落時,利用考拉的相對位移從而去調整相機位置,代碼以下:
1 1 /** 2 2 * 調整相機位置 3 3 * @param {number} deltaX - x軸偏移值 4 4 * @param {number} deltaY - y軸偏移值 5 5 */ 6 6 Main.prototype.adjustCamera = function(deltaX, deltaY) { 7 7 var camera = this.pillarPool.parent, 8 8 step = this._step.gameObject; 9 9 console.log('camera.x:',camera.x); 10 10 console.log('camera.y:',camera.y); 11 11 camera.x -= deltaX; 12 12 if (camera.y - deltaY < -step.y) 13 13 camera.y = -step.y; 14 14 else 15 15 camera.y -= deltaY; 16 18 // 派發調整相機位置事件 17 19 qc.Koala.onAdjustCamera.dispatch(new qc.Point(deltaX, deltaY)); 18 20 };
遊戲背景:在遊戲中,遊戲背景由白雲、山、樹組成,爲了呈現動態效果,白雲由三朵白雲構成循環移動,而山、樹則是根據相機的位置調整而調整。咱們能夠這樣作,當相機調整了位置時,相應地派發一個事件,讓事件接受者相應地調整山、樹的位置。此時咱們須要在入口腳本Koala.js代碼中建立一個事件(須要說明的是,在後續的處理中,用到了不少的事件如分數改變事件、開始登陸事件、遊戲暫停事件、遊戲結束事件等。因爲事件較多,在後續的代碼中我可能不會到處提到,它們的建立事件、派發事件、接收事件是一致的原理),因此,我將遊戲中要用到的事件所有建立出來,讀者們也能夠須要用哪一個事件就相應地添加哪一個事件,代碼以下:
1 var Koala = qc.Koala = { 2 ui : {}, 3 logic : {}, 4 5 // 遊戲對象 6 game : null, 7 8 // 遊戲寬度 9 GAMEWIDTH : 640, 10 11 // 邏輯腳本準備就緒事件 12 onLogicReady : new qc.Signal(), 13 14 // 柱子建立完成 15 onPillarReady : new qc.Signal(), 16 17 // 遊戲開始事件 18 onStart : new qc.Signal(), 19 20 // 考拉拿起鞦韆事件 21 onSwingTake : new qc.Signal(), 22 23 // 遊戲結束事件 24 onGameOver : new qc.Signal(), 25 26 // 遊戲分數發生變化事件 27 onScoreChange : new qc.Signal(), 28 29 // 遊戲暫停事件 30 onPause : new qc.Signal(), 31 32 // 繼續遊戲事件 33 onContinue : new qc.Signal(), 34 35 // 調整相機事件 36 onAdjustCamera : new qc.Signal(), 37 38 // 相機作Tween動畫事件 39 onTweenCamera : new qc.Signal(), 40 41 // 顯示排行榜事件 42 showRanking : new qc.Signal(), 43 44 // 排行榜關閉事件 45 onRankingClose : new qc.Signal(), 46 47 // 登陸成功事件 48 onLogin : new qc.Signal(), 49 50 // 登陸中事件 51 onLogining : new qc.Signal(), 52 53 // 登陸失敗事件 54 onLoginFail : new qc.Signal(), 55 56 // 顯示關注頁面事件 57 showFollowMsg : new qc.Signal(), 58 59 // 顯示分享提示頁面事件 60 showShareMsg : new qc.Signal() 61 };
此時,相機已經改變了位置也派發了一個事件,咱們能夠在Background.js加入事件監聽,從而改變山、樹的位置,代碼以下:
1 var Background = qc.defineBehaviour('qc.Koala.ui.Background', qc.Behaviour, function() { 2 // 動畫播放距離 3 this.tweenDistance = 0; 4 5 // 山與山之間的距離 6 this.mountainDistance = 635; 7 8 // 樹與樹之間的距離 9 this.treeDistance = 340; 10 11 this.mountains = []; 12 13 this.trees = []; 14 15 this.treeIcons = [ 'tree_1.bin', 'tree_2.bin', 'tree_3.bin' ]; 16 }, { 17 // 雲列表 18 clouds : qc.Serializer.NODES, 19 // 山峯區域 20 mountainRect : qc.Serializer.NODE, 21 // 山峯預製 22 mountainPrefab : qc.Serializer.PREFAB, 23 // 樹區域 24 treeRect : qc.Serializer.NODE, 25 // 樹預製 26 treePrefab : qc.Serializer.PREFAB 27 }); 28 29 Background.prototype.awake = function() { 30 // 監聽調整相機事件 31 this.addListener(qc.Koala.onAdjustCamera, this._adjust, this); 32 33 }; 34 35 /** 36 * 調整山和樹的位置 37 * @param {qc.Point} delta - 調整的距離 38 */ 39 Background.prototype._adjust = function(delta) { 40 var x = delta.x, y = delta.y, 41 width = this.gameObject.width; 42 43 // 更新山的位置 44 var mX = x * qc.Koala.logic.config.mountainCoef, 45 mY = y * qc.Koala.logic.config.mountainCoef, 46 mLen = this.mountains.length; 47 this.mountains.forEach(function(m, index) { 48 m.x -= mX; 49 if (m.x <= -this.mountainDistance) 50 m.x += this.mountainDistance * mLen; 51 }, this); 52 53 // 更新樹的位置 54 var tX = x * qc.Koala.logic.config.treeCoef, 55 tY = y * qc.Koala.logic.config.treeCoef, 56 treeLen = this.trees.length; 57 this.trees.forEach(function(t, index) { 58 t.x -= tX; 59 if (t.x <= -this.treeDistance) 60 t.x += this.treeDistance * treeLen; 61 }, this); 62 };
碰撞檢測:考拉鬆手降低的同時,須要作碰撞檢測以檢測它是否成功的站到柱子上,有下面幾種狀況:
1、考拉碰到柱子左邊緣,結束遊戲;
2、考拉超出遊戲邊界,結束遊戲;
3、考拉成功落到柱子上,則相應處理;
代碼以下:
1 /** 2 * 檢測考拉是否能夠站在平臺上 3 * @param {number} preY - 考拉移動前的y軸位置 4 * @return {number} 返回值定義以下 5 * 1:落在跳臺上; 6 * -1:超出遊戲邊界; 7 * -2:碰到跳臺的左邊緣; 8 * 0:還在掉落 9 */ 10 Main.prototype._checkCollide = function(preY) { 11 var x = this.koala.x, 12 y = this.koala.y, 13 step = this._step.gameObject; 14 15 // 判斷是否落到跳臺上 16 if (x > step.x && 17 x < step.x + step.width && 18 preY <= step.y + step.parent.y && 19 y >= step.y + step.parent.y) 20 return 1; 21 22 // 超出遊戲邊界,由於相機有跟着考拉在動,因此在這邊不須要判斷遊戲屏幕x軸方向超邊 23 if (y > this.gameObject.height + this.koala.height - this.pillarPool.parent.y) 24 return -1; 25 26 // 判斷與跳臺左邊緣碰撞 27 if (x > step.x && 28 x < step.x + step.width && 29 preY > step.y + step.parent.y) 30 return -2; 31 32 return 0; 33 };
當考拉成功跳到柱子上時,咱們須要更新柱子的位置(由於若是不更新柱子的位置,柱子將跟隨相機到屏幕外邊),代碼以下:
1 /** 2 * 考拉成功跳到下一個站臺後處理 3 */ 4 Main.prototype._onStep = function() { 5 var koalaScript = this.koala.getScript('qc.Koala.ui.Koala'); 6 koalaScript.fall(this._step); 7 8 // 矯正柱子的位置 9 this.adjustPillar(); 10 11 // 下一個跳臺 12 var pool = this.pillarPool.getScript('qc.Koala.ui.PillarPool'); 13 pool.next(); 14 15 // 重置鞦韆 16 this.swing.reset(); 17 18 // 更新考拉當前所在跳臺和當前正在使用的鞦韆 19 this.startStep = this._step; 20 this.swing = this._step.swing; 21 22 // 從新獲取風力值 23 this.initWind(); 24 }; 25 /** 26 * 跳到跳臺上後,調整柱子位置 27 */ 28 Main.prototype.adjustPillar = function() { 29 var camera = this.pillarPool.parent, 30 s = camera.getScript('qc.TweenPosition'), 31 step = this._step.gameObject, 32 p = new qc.Point(-step.x - camera.parent.width * 0.5, -step.y); 33 s.to = p.clone(); 34 s.setCurrToStartValue(); 35 s.resetToBeginning(); 36 s.play(); 37 38 p.subtract(s.from.x, s.from.y); 39 qc.Koala.onTweenCamera.dispatch(p); 40 };
當考拉成功降落在柱子上時,咱們也須要作加分並顯示在屏幕上,並飄分,若是踩中得分區域的中心點的話還會播放特效,播放特效的位置咱們能夠根據考拉的位置而肯定,在Scripts/ui文件夾下的Koala.js中加入以下代碼:
1 /** 2 * 掉落在跳臺上 3 * @param {qc.Koala.ui.Pillar} pillar - 柱子對象 4 */ 5 Koala.prototype.fall = function(pillar) { 6 // 設置下一個鞦韆對象引用 7 this.swingScript = pillar.swing; 8 9 // 矯正考拉在柱子上的位置,防止陷到柱子裏面去 10 this.gameObject.y = pillar.gameObject.y + pillar.gameObject.parent.y; 11 12 // 將考拉掛載在柱子上 13 this.gameObject.switchParent(pillar.gameObject); 14 15 // 顯示剎車效果 16 this.brakeImg.parent = this.gameObject.parent; 17 this.brakeImg.x = this.gameObject.x; 18 this.brakeImg.y = this.gameObject.y; 19 this.brakeImg.visible = true; 20 21 this.currAnimation = 'fall'; 22 // 播放動做 23 this.gameObject.playAnimation('fall'); 24 25 // 更新考拉走路曲線參數 26 this.updateTween(pillar); 27 28 // 加分數 29 var distance = Math.abs(this.gameObject.anchoredX); 30 31 scoreInfo = qc.Koala.logic.score.getScore(distance, pillar.scoreRect); 32 qc.Koala.logic.me.addScore(scoreInfo.value); 33 34 this.playEffect(scoreInfo.effectName); 35 36 this.playLabel(scoreInfo); 37 38 qc.Tween.resetGroupToBeginning(this.labelImg, 1); 39 qc.Tween.playGroup(this.labelImg, 1); 40 41 this.currAnimation = 'walk'; 42 }; 43 44 /** 45 * 播放文字效果 46 * @param {object} info - 分數對象 47 */ 48 Koala.prototype.playLabel = function (info) { 49 this.scoreImg.frame = info.scoreImg; 50 this.scoreImg.resetNativeSize(); 51 this.labelImg.frame = info.labelImg; 52 this.labelImg.resetNativeSize(); 53 54 this.labelImg.getScript('qc.TweenAlpha').onFinished.addOnce(this.walk, this); 55 }; 56 57 /** 58 * 播放特效 59 * @param {string} effectName - 動畫名稱 60 */ 61 Koala.prototype.playEffect = function (effectName) { 62 if (!effectName) return; 63 64 this.effect.parent = this.gameObject.parent; 65 this.effect.x = this.gameObject.x; 66 this.effect.y = this.gameObject.y; 67 68 this.effect.playAnimation(effectName); 69 };
將上述代碼整合到一個腳本,在Scripts/ui文件夾下建立腳本:Main.js,腳本代碼以下:
1 var Main = qc.defineBehaviour('qc.Koala.ui.Main', qc.Behaviour, function() { 2 // 風值 3 this.windValue = 0; 4 5 // 掉落事件控制器 6 this.dropTimer = null; 7 8 // 跳臺對象 9 this._step = null; 10 11 // 鞦韆對象 12 this.swing = null; 13 }, { 14 // 柱子池 15 pillarPool : qc.Serializer.NODE, 16 // 考拉節點 17 koala : qc.Serializer.NODE, 18 // 暫停按鈕 19 pauseBtn : qc.Serializer.NODE, 20 // 風值 21 wind : qc.Serializer.NODE, 22 // 風向 23 windDirection : qc.Serializer.NODE, 24 // 分數節點 25 score : qc.Serializer.NODE 26 }); 27 28 /** 29 * 初始化 30 */ 31 Main.prototype.awake = function() { 32 var self = this; 33 var camera = this.pillarPool.parent; 34 35 // 監聽柱子初始化完成事件 36 this.addListener(qc.Koala.onPillarReady, this._onPillarReady, this); 37 38 // 監聽遊戲開始事件 39 this.addListener(qc.Koala.onStart, this.restart, this); 40 41 // 監聽分數改變事件 42 this.addListener(qc.Koala.onScoreChange, this.updateScore, this); 43 44 // 在遊戲開始錢禁止交互 45 this.gameObject.interactive = false; 46 47 // 考拉拿起鞦韆時,啓動交互 48 this.addListener(qc.Koala.onSwingTake, function() { 49 this.gameObject.interactive = true; 50 }, this); 51 52 //// 初始化遊戲邏輯腳本 53 //qc.Koala.initLogic(this.config, this.game); 54 55 // 分數文本中間值,用於TweenProperty組件使用 56 this.score._tempText = 0; 57 Object.defineProperties(this.score, { 58 'tempText' : { 59 get : function() { return this._tempText; }, 60 set : function(v) { 61 if (this._tempText === v) return; 62 63 this._tempText = v; 64 this.text = Math.floor(v) + ''; 65 } 66 } 67 }); 68 }; 69 70 /** 71 * 柱子準備完畢後處理 72 * @param {array} pillarList - 柱子列表 73 */ 74 Main.prototype._onPillarReady = function(pillarList) { 75 this.startStep = pillarList[0]; 76 this.swing = this.startStep.swing; 77 78 var s = this.koala.getScript('qc.Koala.ui.Koala'); 79 s.init(this.startStep); 80 }; 81 82 83 /** 84 * 從新開始遊戲 85 */ 86 Main.prototype.restart = function() { 87 // 重置邏輯腳本 88 qc.Koala.resetLogic(); 89 90 // 若是掉落還沒結束,則強制移除掉落的循環控制器 91 if (this.dropTimer) { 92 this.game.timer.remove(this.dropTimer); 93 this.dropTimer = null; 94 } 95 96 // 監聽相機位置調整完成事件 97 var camera = this.pillarPool.parent, 98 s = camera.getScript('qc.TweenPosition'); 99 s.onFinished.addOnce(this.start, this); 100 101 // 重置相機位置 102 this.resetCamera(); 103 104 // 顯示遊戲界面 105 this.show(); 106 }; 107 108 /** 109 * 開始遊戲 110 * @param {boolean} reset - 是否重置遊戲 111 */ 112 Main.prototype.start = function (reset) { 113 // 是否從新開始遊戲 114 if (reset === true) { 115 this.restart(); 116 return; 117 } 118 119 // 啓動暫停按鈕交互 120 this.pauseBtn.interactive = true; 121 122 // 更新風值 123 this.initWind(); 124 125 // 重置柱子列表 126 var pool = this.pillarPool.getScript('qc.Koala.ui.PillarPool'); 127 pool.reset(); 128 129 // 考拉開始走 130 var koalaScript = this.koala.getScript('qc.Koala.ui.Koala'); 131 koalaScript.walk(); 132 }; 133 134 /** 135 * 更新分數文本 136 * @param {number} score - 當前分數 137 */ 138 Main.prototype.updateScore = function (score) { 139 var s = this.score.getScript('qc.TweenProperty'); 140 s.setCurrToStartValue(); 141 s.to = score; 142 qc.Tween.resetGroupToBeginning(this.score, 1); 143 qc.Tween.playGroup(this.score, 1); 144 }; 145 146 /** 147 * 初始化風值 148 */ 149 Main.prototype.initWind = function() { 150 var windObj = qc.Koala.logic.wind.getWind(qc.Koala.logic.me.level); 151 this.windValue = windObj.value * windObj.direction; 152 this.wind.text = windObj.value + ''; 153 154 this.wind.parent.visible = windObj.value !== 0; 155 this.windDirection.rotation = Math.PI * (windObj.direction - 1) * 0.5; 156 }; 157 158 /** 159 * 監聽點擊事件 160 */ 161 Main.prototype.onClick = function() { 162 // 禁止交互 163 this.gameObject.interactive = false; 164 165 // 關卡數加1 166 qc.Koala.logic.me.level++; 167 168 // 計算考拉下落高度 169 var rotation = this.swing.gameObject.rotation, 170 cos = Math.cos(rotation), 171 sin = Math.sin(rotation), 172 radius = this.swing.gameObject.height, 173 h = radius * (cos - Math.cos(this.swing.maxRotation)); 174 175 // 計算橫向及縱向速度 176 var dir = this.swing.direction, 177 v0 = Math.sqrt(2 * qc.Koala.logic.config.g * h) * dir, 178 vx0 = v0 * cos + this.windValue, 179 vy0 = v0 * sin; 180 181 182 // 獲取考拉腳本對象 183 var koalaScript = this.koala.getScript('qc.Koala.ui.Koala'); 184 // 考拉放手 185 koalaScript.away(); 186 187 // 獲取跳臺對象 188 var pool = this.pillarPool.getScript('qc.Koala.ui.PillarPool'); 189 this._step = pool.getStep(); 190 191 // 考拉作拋物線運動 192 this.drop(vx0, vy0); 193 }; 194 195 /** 196 * 考拉脫離繮繩開始掉落 197 * @param {number} vx0 - x方向初始速度 198 * @param {number} vy0 - y方向初始速度 199 */ 200 Main.prototype.drop = function(vx0, vy0) { 201 // 循環定時器刷新考拉位置 202 this.dropTimer = this.game.timer.loop(0, function() { 203 if (qc.Koala.logic.me.paused) 204 return; 205 // 計算縱向速度 206 var t = this.game.time.deltaTime * 0.001; 207 vy0 = vy0 + qc.Koala.logic.config.g * t; 208 209 // 考拉掉落處理 210 this._onDrop(vx0, vy0, t); 211 }, this); 212 }; 213 214 /** 215 * 考拉掉落幀處理 216 * @param {number} vx0 - 橫向速度 217 * @param {number} vy0 - 縱向速度 218 * @param {number} t - 倆幀之間的時間間隔 219 */ 220 Main.prototype._onDrop = function (vx0, vy0, t) { 221 // 計算橫向和縱向偏移值 222 var preY = this.koala.y, 223 deltaX = vx0 * t, 224 deltaY = vy0 * t; 225 226 // 設置考拉位置 227 this.koala.x += deltaX; 228 this.koala.y += deltaY; 229 230 // 調整相機位置 231 this.adjustCamera(deltaX, deltaY); 232 233 // 檢測考拉位置 234 var result = this._checkCollide(preY); 235 if (result !== 0) { 236 // 移除定時器 237 this.game.timer.remove(this.dropTimer); 238 this.dropTimer = null; 239 240 // 成功跳到下一個站臺 241 if (result === 1) { 242 this._onStep(); 243 } 244 245 // 遊戲結束 246 if (result < 0) { 247 this.gameOver(result); 248 } 249 } 250 }; 251 252 /** 253 * 考拉成功跳到下一個站臺後處理 254 */ 255 Main.prototype._onStep = function() { 256 var koalaScript = this.koala.getScript('qc.Koala.ui.Koala'); 257 koalaScript.fall(this._step); 258 259 // 矯正柱子的位置 260 this.adjustPillar(); 261 262 // 下一個跳臺 263 var pool = this.pillarPool.getScript('qc.Koala.ui.PillarPool'); 264 pool.next(); 265 266 // 重置鞦韆 267 this.swing.reset(); 268 269 // 更新考拉當前所在跳臺和當前正在使用的鞦韆 270 this.startStep = this._step; 271 this.swing = this._step.swing; 272 273 // 從新獲取風力值 274 this.initWind(); 275 }; 276 277 /** 278 * 檢測考拉是否能夠站在平臺上 279 * @param {number} preY - 考拉移動前的y軸位置 280 * @return {number} 返回值定義以下 281 * 1:落在跳臺上; 282 * -1:超出遊戲邊界; 283 * -2:碰到跳臺的左邊緣; 284 * 0:還在掉落 285 */ 286 Main.prototype._checkCollide = function(preY) { 287 var x = this.koala.x, 288 y = this.koala.y, 289 step = this._step.gameObject; 290 291 // 判斷是否落到跳臺上 292 if (x > step.x && 293 x < step.x + step.width && 294 preY <= step.y + step.parent.y && 295 y >= step.y + step.parent.y) 296 return 1; 297 298 // 超出遊戲邊界,由於相機有跟着考拉在動,因此在這邊不須要判斷遊戲屏幕x軸方向超邊 299 if (y > this.gameObject.height + this.koala.height - this.pillarPool.parent.y) 300 return -1; 301 302 // 判斷與跳臺左邊緣碰撞 303 if (x > step.x && 304 x < step.x + step.width && 305 preY > step.y + step.parent.y) 306 return -2; 307 308 return 0; 309 }; 310 311 /** 312 * 調整考拉位置 313 */ 314 Main.prototype.adjustKoala = function() { 315 var step = this._step.gameObject; 316 if (this.koala.y > step.y && 317 this.koala.y < step.y + this.koala.height) 318 this.koala.y = step.y; 319 }; 320 321 /** 322 * 跳到跳臺上後,調整柱子位置 323 */ 324 Main.prototype.adjustPillar = function() { 325 var camera = this.pillarPool.parent, 326 s = camera.getScript('qc.TweenPosition'), 327 step = this._step.gameObject, 328 p = new qc.Point(-step.x - camera.parent.width * 0.5, -step.y); 329 s.to = p.clone(); 330 s.setCurrToStartValue(); 331 s.resetToBeginning(); 332 s.play(); 333 334 p.subtract(s.from.x, s.from.y); 335 qc.Koala.onTweenCamera.dispatch(p); 336 }; 337 338 /** 339 * 調整相機位置 340 * @param {number} deltaX - x軸偏移值 341 * @param {number} deltaY - y軸偏移值 342 */ 343 Main.prototype.adjustCamera = function(deltaX, deltaY) { 344 var camera = this.pillarPool.parent, 345 step = this._step.gameObject; 346 camera.x -= deltaX; 347 if (camera.y - deltaY < -step.y) 348 camera.y = -step.y; 349 else 350 camera.y -= deltaY; 351 352 // 派發調整相機位置事件 353 qc.Koala.onAdjustCamera.dispatch(new qc.Point(deltaX, deltaY)); 354 }; 355 356 /** 357 * 重置相機位置 358 */ 359 Main.prototype.resetCamera = function () { 360 var camera = this.pillarPool.parent, 361 s = camera.getScript('qc.TweenPosition'); 362 s.to = new qc.Point(-camera.parent.width * 0.5, 0); 363 s.setCurrToStartValue(); 364 s.resetToBeginning(); 365 s.play(); 366 }; 367 368 /** 369 * 顯示界面 370 */ 371 Main.prototype.show = function () { 372 this.gameObject.visible = true; 373 };
將該腳本掛載到"遊戲場景"節點,並將對應的節點拖入到對應的屬性中,以下圖:
到此,遊戲界面的元素與腳本就已經講了大部分了,下一篇文章我將講述遊戲暫停、遊戲結束等功能的處理。