javascript開發HTML5遊戲--鬥地主(單機模式part1)

    最近學習使用了一款HTML5遊戲引擎(青瓷引擎),並用它嘗試作了一個鬥地主的遊戲,簡單實現了單機對戰和網絡對戰,代碼可已放到github上,在此談談本身如何經過引擎來開發這款遊戲的。html

 客戶端代碼前端

   服務端代碼java

         (點擊圖片進入遊戲體驗)git

本篇文章爲第一部分,主要包括單機模式的開始佈局設計準備。主要內容以下:github

  1. 鬥地主遊戲介紹
  2. 建立工程與主場景
  3. 單機模式場景佈局
  4. 添加圖形資源

1、鬥地主遊戲介紹

  鬥地主遊戲對於你們應該是算耳熟能詳的遊戲了,我就簡單說明下本身理解的整個遊戲的流程。網絡

  

 

 

遊戲的主體,如圖所示:編輯器

2、建立工程與主場景

  使用青瓷引擎的編輯器很容易幫我完成整個遊戲界面的佈局。作這個遊戲以前我從事java開發,也作過一段時間的前端開發,對js和前端佈局算是比較熟悉。結合官方的手冊、demo,我大概花了兩三天時間把這些過了遍,嘗試修改完善一些demo的內容,接下來我就開始本身嘗試作這個遊戲了。我的感受有強大的編輯器,佈局能夠比較迅速的完成,而後專一地進行邏輯編寫。ide

  個人想法是把單機和多人對戰分紅兩個場景,建立項目工程landlord和主場景,其中main是主場景,single是單機版的場景,online是在線對戰的場景。函數

  入口與遊戲的初始化

    在Scripts下建立start.js,並配置爲入口腳本,此腳本首先定義了名字空間,將全局的數據都記錄在這裏。代碼以下:工具

 

 1 // 定義本工程的名字空間
 2 qc.landlord = {};  3 
 4 // 用來存放全部的全局數據(函數、變量等)
 5 window.G = qc.landlord.G = {};  6 
 7 // 初始化邏輯
 8 qc.initGame = function(game) {  9     game.log.trace('Start the game landlord.'); 10     // 將game實例的引用記錄下來,方便在其餘邏輯腳本模塊中訪問
11     G.game = game; 12     //遊戲規則
13     G.gameRule = new qc.landlord.GameRule(); 14 
15     G.online = new qc.landlord.Online(); 16     //AI邏輯
17     //G.AILogic = new qc.landlord.AILogic();
18     String.prototype.trim = function() {  return this.replace(/(^\s*)|(\s*$)/g,''); }; 19 };
View Code

 

    點擊start.js腳本,在右側的Inspector面板下點擊edit,把start.js拖入並放置到第一的位置就成爲入口腳本了。

  主場景設計

    主場景設計入如圖:

    

    在Scripts/ui下建立MainUI.js腳本並掛載在lock節點下掛載(直接拖動腳本防置到節點),主要實現了兩個按鈕的事件,代碼以下: 

 1 /**  2  * 主場景腳本  3  * @method defineBehaviour  4  */
 5 var MainUI = qc.defineBehaviour('qc.engine.MainUI', qc.Behaviour, function() {  6     this.singleBtn = null;  7     this.multiBtn = null;  8     this.singleScene = null;  9 }, {  10  singleBtn : qc.Serializer.NODE,  11  multiBtn: qc.Serializer.NODE,  12  enterNameBtn : qc.Serializer.NODE,  13  backBtn: qc.Serializer.NODE,  14  nameField: qc.Serializer.NODE,  15  enterNamePanel: qc.Serializer.NODE,  16  message: qc.Serializer.NODE,  17  singleScene: qc.Serializer.STRING,  18  multiScene: qc.Serializer.STRING  19 });  20 
 21 // Called when the script instance is being loaded.
 22 MainUI.prototype.awake = function() {  23     var self = this;  24     //單機按鈕事件
 25     self.addListener(self.singleBtn.onClick, function(){  26         self.game.state.load(self.singleScene, false, function() {  27             //牌組管理
 28             G.cardMgr = new qc.landlord.Card();  29             //玩家本人
 30             G.ownPlayer = new qc.landlord.Player('QCPlayer');  31             G.ownPlayer.isAI = false;  32             var storage = G.game.storage;  33             var ownScore = storage.get('QCPlayer');  34             G.ownPlayer.score = ownScore ? ownScore : 500;  35             //電腦玩家(左)
 36             G.leftPlayer = new qc.landlord.Player('AI_Left');  37             var leftScore = storage.get('AI_Left');  38             G.leftPlayer.score = leftScore ? leftScore : 500;  39             //電腦玩家(右)
 40             G.rightPlayer = new qc.landlord.Player('AI_Right');  41             var rightScore = storage.get('AI_Right');  42             G.rightPlayer.score = rightScore ? rightScore : 500;  43 
 44             //指定玩家順序
 45             G.ownPlayer.nextPlayer = G.rightPlayer;  46             G.rightPlayer.nextPlayer = G.leftPlayer;  47             G.leftPlayer.nextPlayer = G.ownPlayer;  48 
 49             //底牌
 50             G.hiddenCards = [];  51             //當前手牌
 52             G.currentCards = [];  53         }, function() {  54             console.log(self.singleScene + '場景加載完畢。');  55  });  56  }, self);  57 
 58     //多人對戰按鈕事件
 59     self.addListener(self.multiBtn.onClick, function(){  60 
 61         var uid = G.game.storage.get('uid');  62         if(uid){  63  self.showMessage(self.MSG_WAITING);  64  self.enterGame(uid);  65         } else {  66             var np = self.enterNamePanel.getScript('qc.TweenAlpha');  67             np.from = 0;  68             np.to = 1;  69  np.resetToBeginning();  70             self.enterNamePanel.visible = true;  71  np.playForward();  72  }  73 
 74  }, self);  75 
 76     //確認按鈕事件
 77     self.addListener(self.enterNameBtn.onClick, function(){  78         var nickname = self.nameField.text.trim();  79         if(nickname){  80  self.showMessage(self.MSG_WAITING);  81             var result = G.online.register(nickname);  82             result.then(function(data){  83                 if(data.uid){  84                     G.game.storage.set('uid', data.uid);  85  G.game.storage.save();  86  self.enterGame(data.uid);  87                 } else if(err === G.online.ERR_EXIST_NAME){  88  self.showMessage(self.MSG_EXIST_NAME);  89  }  90             }).catch(function(err){  91                 if(err === G.online.ERR_EXIST_NAME){  92  self.showMessage(self.MSG_EXIST_NAME);  93  }  94  });  95  }  96  }, self);  97 
 98     //返回按鈕事件
 99     self.addListener(self.backBtn.onClick, function(){ 100         var np = self.enterNamePanel.getScript('qc.TweenAlpha'); 101         np.from = 1; 102         np.to = 0; 103  np.resetToBeginning(); 104         //self.enterNamePanel.visible = true;
105  np.playForward(); 106         np.onFinished.addOnce(function (){ 107             self.enterNamePanel.visible = false; 108  }, self); 109         window.onbeforeunload = undefined; 110  }, self); 111 }; 112 
113 MainUI.prototype.enterGame = function(uid) { 114     var self = this; 115     G.ownPlayer = new qc.landlord.Player(null); 116     G.ownPlayer.uid = uid; 117     window.onbeforeunload = function (){ 118         var warning="確認退出遊戲?"; 119         return warning; 120  }; 121     G.online.joinGame(G.ownPlayer.uid).then(function(data){ 122         var player = data.seats ? data.seats[data.ownSeatNo] : data.desk.seats[data.ownSeatNo]; 123         G.ownPlayer.deskNo = player.deskNo; 124         G.ownPlayer.name = player.name; 125         G.ownPlayer.seatNo = data.ownSeatNo; 126         G.netInitData = data; 127         self.game.state.load(self.multiScene, false, function() { 128         }, function() { 129             console.log(self.multiScene + '場景加載完畢。'); 130  }); 131  }); 132 }; 133 
134 MainUI.prototype.showMessage = function(m) { 135     var self = this; 136     if(m){ 137         self.message.text = m; 138         self.message.visible = true; 139     } else { 140         self.message.visible = false; 141  } 142 }; 143 
144 MainUI.prototype.MSG_WAITING = '請稍等'; 145 MainUI.prototype.MSG_EXIST_NAME = '您輸入的暱稱已被使用,請重試';
View Code

3、單機模式場景佈局

  遊戲主體

  鬥地主主要的主體就是玩家、紙牌,製做主場景以前,先編寫好這兩個實體類

  玩家類設計:在Scripts/logic建立player.js代碼以下:

 1 // define a user behaviour
 2 var Player = qc.landlord.Player = function (n){  3     var self = this;  4     // 玩家名
 5     self.name = n ? n : 'Player';  6     //是不是地主
 7     self.isLandlord = false;  8     //是不是AI玩家
 9     self.isAI = true; 10     //牌組
11     self.cardList = []; 12     //下一家
13     self.nextPlayer = null; 14     //如下屬性用於多人對戰
15     self.uid = null; 16     self.deskNo = null; 17     self.seatNo = null; 18     //是否已經準備
19     self.isReady = false; 20     //是否已經離開
21     self.isLeave = false; 22 }; 23 Object.defineProperties(Player.prototype, { 24  score: { 25         get: function(){ 26             return this._score; 27  }, 28         set: function(v){ 29             this._score = v; 30             var storage = G.game.storage; 31             storage.set(this.name, v); 32  storage.save(); 33  } 34  } 35 });
View Code

  牌組類設計:在Scripts/logic/clone建立card.js代碼以下:

 1 // define a user behaviour
 2 var Card = qc.landlord.Card = function (){  3     this.data = [  4         {icon: 'j1.jpg', type: '0', val: 17},  5         {icon: 'j2.jpg', type: '0', val: 16},  6         {icon: 't1.jpg', type: '1', val: 14},  7         {icon: 't2.jpg', type: '1', val: 15},  8         {icon: 't3.jpg', type: '1', val: 3},  9         {icon: 't4.jpg', type: '1', val: 4}, 10         {icon: 't5.jpg', type: '1', val: 5}, 11         {icon: 't6.jpg', type: '1', val: 6}, 12         {icon: 't7.jpg', type: '1', val: 7}, 13         {icon: 't8.jpg', type: '1', val: 8}, 14         {icon: 't9.jpg', type: '1', val: 9}, 15         {icon: 't10.jpg', type: '1', val: 10}, 16         {icon: 't11.jpg', type: '1', val: 11}, 17         {icon: 't12.jpg', type: '1', val: 12}, 18         {icon: 't13.jpg', type: '1', val: 13}, 19         {icon: 'x1.jpg', type: '2', val: 14}, 20         {icon: 'x2.jpg', type: '2', val: 15}, 21         {icon: 'x3.jpg', type: '2', val: 3}, 22         {icon: 'x4.jpg', type: '2', val: 4}, 23         {icon: 'x5.jpg', type: '2', val: 5}, 24         {icon: 'x6.jpg', type: '2', val: 6}, 25         {icon: 'x7.jpg', type: '2', val: 7}, 26         {icon: 'x8.jpg', type: '2', val: 8}, 27         {icon: 'x9.jpg', type: '2', val: 9}, 28         {icon: 'x10.jpg', type: '2', val: 10}, 29         {icon: 'x11.jpg', type: '2', val: 11}, 30         {icon: 'x12.jpg', type: '2', val: 12}, 31         {icon: 'x13.jpg', type: '2', val: 13}, 32         {icon: 'h1.jpg', type: '3', val: 14}, 33         {icon: 'h2.jpg', type: '3', val: 15}, 34         {icon: 'h3.jpg', type: '3', val: 3}, 35         {icon: 'h4.jpg', type: '3', val: 4}, 36         {icon: 'h5.jpg', type: '3', val: 5}, 37         {icon: 'h6.jpg', type: '3', val: 6}, 38         {icon: 'h7.jpg', type: '3', val: 7}, 39         {icon: 'h8.jpg', type: '3', val: 8}, 40         {icon: 'h9.jpg', type: '3', val: 9}, 41         {icon: 'h10.jpg', type: '3', val: 10}, 42         {icon: 'h11.jpg', type: '3', val: 11}, 43         {icon: 'h12.jpg', type: '3', val: 12}, 44         {icon: 'h13.jpg', type: '3', val: 13}, 45         {icon: 'k1.jpg', type: '4', val: 14}, 46         {icon: 'k2.jpg', type: '4', val: 15}, 47         {icon: 'k3.jpg', type: '4', val: 3}, 48         {icon: 'k4.jpg', type: '4', val: 4}, 49         {icon: 'k5.jpg', type: '4', val: 5}, 50         {icon: 'k6.jpg', type: '4', val: 6}, 51         {icon: 'k7.jpg', type: '4', val: 7}, 52         {icon: 'k8.jpg', type: '4', val: 8}, 53         {icon: 'k9.jpg', type: '4', val: 9}, 54         {icon: 'k10.jpg', type: '4', val: 10}, 55         {icon: 'k11.jpg', type: '4', val: 11}, 56         {icon: 'k12.jpg', type: '4', val: 12}, 57         {icon: 'k13.jpg', type: '4', val: 13} 58  ]; 59 }; 60 //拷貝牌組,返回一組新的牌組
61 Card.prototype.getNewCards = function () { 62     return this.data.slice(0); 63 };
View Code

  添加預製

  在開始作主場景以前,須要作一些預製(點擊我看文檔),方便後面調用。主要仍是圍繞玩家、紙牌進行,引擎在這方面也給咱們提供了很強大的支持:

  1. 首先是紙牌預製,紙牌比較簡單,只是張圖片,默認顯示的是紙牌的背面圖案,咱們在圖片節點下掛載一個腳本,Scripts/ui下建立CardUI.js並掛載,代碼以下:
 1 /**  2  * 卡牌規則  3  */
 4 var CardUI = qc.defineBehaviour('qc.engine.CardUI', qc.Behaviour, function() {  5     this.isSelected = false;  6     this.info = null;  7 }, {  8     // fields need to be serialized
 9 }); 10 
11 /** 12  *顯示紙牌, 13  *@property info 卡牌信息, 14  *@property isSelect 是否選中 15   */
16 CardUI.prototype.show = function (info, isSelect){ 17     var self = this, 18         o =self.gameObject; 19     if(info){ 20         o.frame = info.icon; 21  o.resetNativeSize(); 22         o.visible = true; 23         self.info = info; 24  } 25 }; 26 
27 /** 28  * 選中紙牌,紙牌上移 29   */
30 CardUI.prototype.onClick = function (){ 31     var self = this; 32     if(self.isSelected){ 33         this.gameObject.anchoredY = 0; 34     } else { 35         this.gameObject.anchoredY = -28; 36  } 37     self.isSelected = !self.isSelected; 38     var ui = window.landlordUI ? window.landlordUI : window.olLandlordUI; 39     if(ui.getReadyCardsKind()){ 40         ui.playBtn.state = qc.UIState.NORMAL; 41     } else { 42         ui.playBtn.state = qc.UIState.DISABLED; 43  } 44 };
View Code

  二、玩家預製:完成以後發現這個並非很必要,由於是一開始都固定的,遊戲中不會添加改動。好比右邊玩家,以下圖:

  

  每一個玩家下都要掛載一個腳本,主要是方便咱們後面去查找它的內容,這個好比修改分數,修改手牌數之類的操做,代碼以下:

 1 // define a user behaviour
 2 var PlayerUI = qc.defineBehaviour('qc.engine.PlayerUI', qc.Behaviour, function() {  3     // need this behaviour be scheduled in editor
 4     //this.runInEditor = true;
 5     this.player = null;  6 }, {  7  headPic : qc.Serializer.NODE,  8  playerScore : qc.Serializer.NODE,  9  cardContainer : qc.Serializer.NODE 10 });
View Code

  三、信息預製:這個預製只是個文字,作這個預製主要用於叫分出牌的時候,好比給某個玩家顯示 2分 、不叫、 不出之類的信息,這樣直接加入不須要再去設置字體大小的信息。

  佈局設計

  單機模式場景,我大體劃分爲如下幾個模塊,具體能夠使用青瓷引擎編輯器打開,這種所見即所得的工具應該很快就能瞭解整個場景界面的佈局設計,如圖:

  

  

4、添加圖形資源

 

  導入圖形資源並打包圖集(點擊我看文檔),

  將全部的撲克牌圖形資源到Assets/atlas/poker@atlas下,身份頭像圖形資源到Assets/atlas/landlord@atlas,按鈕圖形資源到Assets/atlas/btn@atlas。

   作完這些準備咱們就能夠進行作發牌的了,我將會在下一篇文章分享這些內容

相關文章
相關標籤/搜索