Game Client Source code overview
Tutorial Source code :
In this post i will teach you about the client class that handles the game logic , as you remember
The "Real" game is played on the server BUT we as players need to visualize the game.
The "Real" game is played on the server BUT we as players need to visualize the game.
The Game logic in the client have many tasks to do but all the tasks are stateless .
That means in simple words there is no data saved on the client each action made
That means in simple words there is no data saved on the client each action made
will first make request to the server and after confirmation it will make its move ,
of course all the actions will be informed in real time to the other players.
also in our game , this Game layer will take the control of the WebSockets callbacks .
and will handle the building the request JSON protocol and the Decoding the returned
JSON from the server And Act upon the events received .
Here is JSON communication example :
User Login to the game with user name send login request to the server:
The JSON format:
{
"event": 2,
"username": "Pla"
}
The Server confirms the new Player and it send back to the current client and other client:
response JSON format :
{
"winnercards": "",
"winner": -1,
"activecardid": "c47",
"endgame": -1,
"players": [], //Here is the array of the other players in the room
"deck": "",
"id": 0,
"event": 3,
"activeplayerid": 0,
"registertionnum": 0,
"username": "Pla",
"numcardsleft": 25
}
In this end of this tutorial the login should look like this :
2 browsers open , to simulate 2 players doing login
and each one see the other in real time
GameScene.js
Game logic class
also in our game , this Game layer will take the control of the WebSockets callbacks .
and will handle the building the request JSON protocol and the Decoding the returned
JSON from the server And Act upon the events received .
Here is JSON communication example :
User Login to the game with user name send login request to the server:
The JSON format:
{
"event": 2,
"username": "Pla"
}
The Server confirms the new Player and it send back to the current client and other client:
response JSON format :
{
"winnercards": "",
"winner": -1,
"activecardid": "c47",
"endgame": -1,
"players": [], //Here is the array of the other players in the room
"deck": "",
"id": 0,
"event": 3,
"activeplayerid": 0,
"registertionnum": 0,
"username": "Pla",
"numcardsleft": 25
}
In this end of this tutorial the login should look like this :
2 browsers open , to simulate 2 players doing login
and each one see the other in real time
GameScene.js
Game logic class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 | var spriteFrameCache = cc.spriteFrameCache; var size = null; var MENU_TAG = 1; var CENTER_DECK = 2; var CARD = 3; var TEXT_INPUT_FONT_SIZE_PLAYER = 20; var GameLayer = cc.Layer.extend({ cardDeckSprite:null, sprite:null, listener1:null, jsonData:null, textFieldUserNameCapton:null, currentPlayer:null, otherPlayers:[], ctor:function (_jsondata) { this.jsonData = _jsondata; //after succesful login we want to take control on massages coming from server //so we attache this new callback function to the websocket onmessage ws.onmessage = this.ongamestatus.bind(this); ws.onclose = this.onclose.bind(this); ws.onerror = this.onerror.bind(this); this._super(); size = cc.winSize; return true; }, onEnter:function () { this._super(); // Make sprite1 touchable this.listener1 = cc.EventListener.create({ event: cc.EventListener.TOUCH_ONE_BY_ONE, swallowTouches: true, onTouchBegan: function (touch, event) { event.getCurrentTarget().invokeTurn(); return true; }, onTouchEnded: function (touch, event) { } }); this.setUserObject(this.listener1); cc.eventManager.addListener(this.listener1, this); spriteFrameCache.addSpriteFrames(res.sprites_plist, res.sprites_png); var userName = this.jsonData.username; this.textFieldUserNameCapton = new cc.TextFieldTTF("Hello "+userName, TEXT_INPUT_FONT_NAME, TEXT_INPUT_FONT_SIZE_PLAYER); this.textFieldUserNameCapton.setTextColor(cc.color.RED); this.textFieldUserNameCapton.x = size.width / 2; this.textFieldUserNameCapton.y = size.height-(TEXT_INPUT_FONT_SIZE_PLAYER+100); this.addChild(this.textFieldUserNameCapton,10); this.eventHandler(this.jsonData.event); }, onExit:function () { this._super(); spriteFrameCache.removeSpriteFramesFromFile(res.sprites_plist); spriteFrameCache.removeSpriteFramesFromFile(res.sprites_png); cc.eventManager.removeListener(this.listener1); }, eventHandler:function(event) { switch (event) { case Events.LOGIN_DONE: { this.setupCurrentPlayer(); break; } case Events.NEW_USER_LOGIN_DONE: { this.setupOtherPlayerS(); break; } case Events.PLAY_DONE: { this.setPlayState(); break; } } this.setTurnMassage(); }, setTurnMassage:function() { var userName = this.jsonData.username; var activePlayerId = this.jsonData.activeplayerid; if(activePlayerId === this.currentPlayer.id) { this.textFieldUserNameCapton.setString("Hello "+userName+" Its your turn"); } else { this.textFieldUserNameCapton.setString("Hello "+userName); } }, onCallbackMoveTo:function (nodeExecutingAction,player) { this.currentPlayer.updatePlayerNumberOfCardsCaption(this.jsonData.numcardsleft); this.otherPlayers[0].updatePlayerNumberOfCardsCaption(this.jsonData.players[0].numcardsleft); }, setPlayState:function() { this.currentPlayer.setNewCardById(this.jsonData.activecardid); this.updatePlayer(this.currentPlayer,this.jsonData); if(this.jsonData.players.length>0) { for(var i=0;i<this.jsonData.players.length;i++) { if(this.jsonData.players[i].event === Events.PLAY_DONE) { this.otherPlayers[i].setNewCardById(this.jsonData.players[i].activecardid); this.updatePlayer(this.otherPlayers[i],this.jsonData.players[i]); } } } //handle animation var pos = null; var activePlayerId = this.jsonData.activeplayerid; if(activePlayerId !== this.currentPlayer.id) { pos = this.currentPlayer.getPosition(); } else { //TODO this fix this hard coded position getter pos = this.otherPlayers[0].getPosition(); } var cardInDeck = this.jsonData.deck; this.animateCard(cardInDeck,pos); }, animateCard:function(_cardInDeckId,_pos) { var cardName = cards[_cardInDeckId]; this.cardDeckSprite = new cc.Sprite("#"+cardName); this.cardDeckSprite.attr({ x: _pos.x,//(cc.winSize.width / 2) , y: _pos.y//(cc.winSize.height / 2) }); this.addChild(this.cardDeckSprite,1,CENTER_DECK); //TODO handel removeble when not needed by tag name var posMid = cc.p(size.width/2,size.height/2); var action = cc.sequence( cc.moveTo(0.5, posMid), cc.callFunc(this.onCallbackMoveTo,this,this.cardDeckSprite)); this.cardDeckSprite.runAction(action); }, invokeTurn:function() { if(this.currentPlayer.id == this.currentPlayer.activeplayerid) { var config = { event:Events.PLAY, username:this.currentPlayer.username, id:this.currentPlayer.id, }; var message = Encode(config); ws.send(message); } else { console.log("GameScene->invokeTurn() not its turn:"+this.currentPlayer.id); } }, setupCurrentPlayer:function() { this.currentPlayer = new Player(this.jsonData.id,this.jsonData.username, this.jsonData.activecardid); this.updatePlayer(this.currentPlayer,this.jsonData); this.addChild(this.currentPlayer,1); this.positionPlayer(this.currentPlayer); if(this.jsonData.players.length>0) { for(var i=0;i<this.jsonData.players.length;i++) { if(this.jsonData.players[i].event === Events.NEW_USER_LOGIN_DONE) { this.setupOtherPlayer(i); } } } }, setupOtherPlayerS:function() { if(this.jsonData.players.length>0) { for(var i=0;i<this.jsonData.players.length;i++) { if(this.jsonData.players[i].event === Events.LOGIN_DONE) { this.setupOtherPlayer(i); } } } }, setupOtherPlayer:function(inx) { this.otherPlayers[inx] = new Player(this.jsonData.players[inx].id, this.jsonData.players[inx].username, this.jsonData.players[inx].activecardid); this.updatePlayer(this.otherPlayers[inx],this.jsonData.players[inx]); this.addChild(this.otherPlayers[inx],1); this.positionPlayer(this.otherPlayers[inx]); }, updatePlayer:function(_player,jsonObj) { _player.activeplayerid = jsonObj.activeplayerid; _player.activecardid = jsonObj.activecardid; _player.event = jsonObj.event; _player.registertionnum = jsonObj.registertionnum; _player.winner = jsonObj.winner; _player.winnercards = jsonObj.winnercards; _player.numcardsleft = jsonObj.numcardsleft; }, positionPlayer:function(_player) { if(_player.registertionnum === 0) { _player.attr({ x: (cc.winSize.width / 2) + 150, y: (cc.winSize.height / 2) }); } else if (_player.registertionnum === 1) { _player.attr({ x: (cc.winSize.width / 2) - 150, y: (cc.winSize.height / 2) }); } }, ongamestatus:function(e) { console.log("GameScene->.ws.onmessage():"+e.data); if(e.data!==null || e.data !== 'undefined') { this.jsonData = Decode(e.data); this.eventHandler(this.jsonData.event); } } , onclose:function (e) { }, onerror:function (e) { } }); var EnterWorldScene = cc.Scene.extend({ session:null, ctor:function (_session) { this.session = _session; this._super(); size = cc.winSize; return true; }, onEnter:function () { this._super(); var layer = new GameLayer(this.session); this.addChild(layer); } }); |
- Lines 9 - 15 : members of the class ,
cardDeckSprite : the sprite that holds the middle deck cards.
listener1: the cocos2d-x object which listen to touch events from teh user
jsonData: the JSON data received from the server.
textFieldUserNameCapton : User name of the current user
currentPlayer : current user that login
otherPlayers: array that holds the Other players in the room data , so wee can see the updates they made - Lines 16 -26 : class constructor that do several important things ,
First - it gets the initial JSON data from the server that received when User LOGIN event
confirmed.
Second - 20 to 22 it takes control of the WebSocket build in callbacks . and bind them to
this class local callbacks . - Lines 32 - 41 : set up Cocos2d-x new EventListener to detect on devices that enable touches
The touch event and mouse events when it disabled . will invoke the invokeTurn() function . - Lines 47 - 54 : set the user name according to the data received from the server
- Lines 63 -83 : Event Handler function that is triggered first time when user enter the game room in line 54 .
and then it triggered each time there is massage from the server line 241. - Line 84 : after each event there is need to update the players with who turn is now
- Lines 99 - 101 : this callback function is called to invoke the players updatePlayerNumberOfCardsCaption function after the animation of the cards is finished. ( see lines 143 - 145 )
- Lines 103 -131 : The function is called when the PALY_DONE event is received from the server.
Lines 105 -117 : the current player get updated , and also the other players in the room get updated with new states .
Lines 119 -131 : trigger the animation of the cards. - Lines 133 - 147 : card animation function , animate the moving card into the middle card deck.
- Lines 148 - 164 : this function is triggered when player touch the screen or by mouse in desktop or finger on mobile device see paragraph 3 above .
it will only be invoked if the current player is the active player . that means the one which is his turn in the game .
then it will prepare the JSON massage to send to the server . - Lines 165 - 183 : setup the current player this is called once when the LOGIN_DONE event
is received from the server. - Lines 184 - 196 : set up the other players in he game room , this is for us to see what is the status of the other players . this is triggered when the server sends the client NEW_USER_LOGIN_DONE event .
- Lines 197 - 207 : create new other player and set it to the otherPlayers client array .
- Lines 208 - 217 : update player object with new data.
- Lines 218 -235 : position the players in the game room based on order they joined the game
the data is kept in the JSON registertionnum member and set to the player registertionnum member . - Lines 236 - 250 : those functions are the new calback functions that are bound to our client
WebSocket object , see paragraph 2 above .
there are things i still need to fix and refine will do those over time.
I hope it was informative .
Thank you for your step by step guide and overview on game client source code. It gives an insight on so many things!
ReplyDelete