JS開發HTML5遊戲《悠悠考拉》(二)

                                             (點擊圖片可進入試玩)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 };
View Code

此腳本定義了名字空間,用於記錄全局數據。遊戲入口中,記錄了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 };
View Code

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 }
View Code

 

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 };
View Code

將該腳本掛載到"遊戲背景"節點上,並將對應的節點拖入到對應的屬性值,以下圖所示:

 

至此,咱們已經把背景界面弄好了,但我想把登陸界面與背景界面分離出來,故我在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 };
View Code

把該腳本掛載到"歡迎界面"節點上,並將對應的節點拖入到對應的屬性上,須要說明的是,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 };
View Code

而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 };
View Code

將該腳本掛載到"歡迎界面"節點,掛載完成後以下圖所示:

其中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 };
View Code

而後咱們也須要解析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 };
View Code

將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 };
View Code

根據策劃要求,但願在遊戲中可以模擬現實世界,考拉在盪鞦韆的時候會有風速,風速對考拉的速度是會有影響的,並且隨着關卡的不一樣其風速也不相同,故咱們也能夠將這些數據配置到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 };
View Code

一樣地,咱們也須要在入口腳本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 };
View Code

在前面咱們將柱子作成了預製,此時能夠直接拿來用,遊戲中使用三個柱子循環移動位置。在"相機"節點下建立一個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 };
View Code

將該腳本掛載到"柱子集"節點上,將柱子預製拖入對對應屬性值,以下圖:

 

柱子預製腳本:咱們在上面的柱子配置表能夠看出不一樣的關卡等級,柱子的粗細不盡相同,故咱們能夠建立一個腳本用於在不一樣的關卡等級正確的顯示柱子,在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 };
View Code

將該腳本掛載到柱子預製上,並將鞦韆預製拖入到對應的屬性,其中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 };
View Code

 

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 };
View Code

把柱子和鞦韆弄完後,咱們此時就須要請出悠悠考拉遊戲的主角登場了。在"相機"節點下建立一個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 };
View Code

 

標題區域:標題區域由暫停按鈕、方向區域、分數區域所組成;在"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 };
View Code

實例化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 };
View Code

但是咱們在遊戲中怎麼去獲取分數呢?咱們能夠在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 };
View Code

 

遊戲界面中的大部分節點都已經建立好了,但是怎麼讓考拉鬆手時與柱子發生碰撞呢?怎麼讓屏幕一直跟着考拉呢?怎麼讓遊戲背景也跟着移動呢?。。。。等等等,接下來我就一一講述。

 

物理表現:在虛擬世界中,考拉有本身的位置,水平和垂直方向上的速度。在遊戲中,考拉在鞦韆上作鐘擺運動,點擊屏幕時考拉鬆手,須要計算它的垂直速度與水平速度,須要說明的是代碼中的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 };
View Code

 

視野移動:在遊戲中,爲了讓考拉一直處於屏幕中,即屏幕一直跟隨考拉,此時採用相機,作法是:將柱子、考拉及鞦韆所有掛載到相機節點下,當考拉脫離繮繩開始掉落時,利用考拉的相對位移從而去調整相機位置,代碼以下:

 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 };
View Code

 

遊戲背景:在遊戲中,遊戲背景由白雲、山、樹組成,爲了呈現動態效果,白雲由三朵白雲構成循環移動,而山、樹則是根據相機的位置調整而調整。咱們能夠這樣作,當相機調整了位置時,相應地派發一個事件,讓事件接受者相應地調整山、樹的位置。此時咱們須要在入口腳本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 };
View Code

此時,相機已經改變了位置也派發了一個事件,咱們能夠在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 };
View Code

 

碰撞檢測:考拉鬆手降低的同時,須要作碰撞檢測以檢測它是否成功的站到柱子上,有下面幾種狀況:

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 };
View Code

當考拉成功跳到柱子上時,咱們須要更新柱子的位置(由於若是不更新柱子的位置,柱子將跟隨相機到屏幕外邊),代碼以下:

 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 };
View Code

當考拉成功降落在柱子上時,咱們也須要作加分並顯示在屏幕上,並飄分,若是踩中得分區域的中心點的話還會播放特效,播放特效的位置咱們能夠根據考拉的位置而肯定,在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 };
View Code

將上述代碼整合到一個腳本,在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 };
View Code

將該腳本掛載到"遊戲場景"節點,並將對應的節點拖入到對應的屬性中,以下圖:

到此,遊戲界面的元素與腳本就已經講了大部分了,下一篇文章我將講述遊戲暫停、遊戲結束等功能的處理。

相關文章
相關標籤/搜索