Friday, October 2, 2015

Snake Game Using Cocos2d-x HTML5 - PART 3


Lets see the finished source code first and then i will explain the logic of each section in the code


  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
//global varibales with default values 
var SnakeArray = null; 
var SnakeFood = null; 
var spriteBackGround = null; 
var BackGroundWidth = 0;;
var BackGroundHeight = 0;
var BackGroundPos;
var Direction ="";
var score = 0;  
var cellWidth = 30;
var Enum = {snakecell:0, snakefood:1 , background:2};
var snakeJSGameLayer = null;
var snakeLength = 5; //Length of the snake
var ScoreLabel = null;
var GameOverLabel = null;
var bCollisionDetected = false;

//The Background sprite class holding the game nodes
var SpriteBackGround = cc.Sprite.extend({
 ctor: function(texture, rect) {           
  this._super(texture, rect);
 }
});
//Snake cell class 
var SpriteSnakeCell = cc.Sprite.extend({
 ctor: function(texture) {           
  this._super(texture);
 }
});
//Snake food class 
var SpriteSnakeFood = cc.Sprite.extend({
 ctor: function(texture) {           
  this._super(texture);
 }
});
//Game Main container class, holding all game logic functions
var SnakeJSGameLayer = cc.Layer.extend({
    sprite:null,
    ctor:function () {
      
       this._super();
       var winSize = cc.winSize;
       //Create the background sprite 
       spriteBackGround = new SpriteBackGround(res.blank_png,
                                cc.rect(0,0,winSize.width-50,winSize.height-90));
       spriteBackGround.setAnchorPoint(0,0);
       spriteBackGround.setTag(Enum.background);
       BackGroundWidth = spriteBackGround.getBoundingBox().width;
       BackGroundHeight = spriteBackGround.getBoundingBox().height;
       //Calculate the background sprite positon by subtracting SpriteBackGround position from Main layer position 
       spriteBackGround.x = (winSize.width - BackGroundWidth)/2;
       spriteBackGround.y = (winSize.height - BackGroundHeight)/2;       
       this.addChild(spriteBackGround,1);
       BackGroundPos = {x:spriteBackGround.x, y:spriteBackGround.y};      
       //Add the score lable
       ScoreLabel = new cc.LabelTTF(setLabelString(score), "Arial", 38); 
       ScoreLabel.x = winSize.width / 2;
       ScoreLabel.y = winSize.height / 2 + 200;       
       this.addChild(ScoreLabel, 5); 
       //Add the game over lable as none visible 
       var redColor = cc.color(0, 0, 0);
       GameOverLabel = new cc.LabelTTF("Game Over press SPACE to restart!", "Arial", 38); 
       GameOverLabel.x = winSize.width / 2;
       GameOverLabel.y = winSize.height / 2;  
       GameOverLabel.fillStyle = redColor
       this.addChild(GameOverLabel, 5); 
       GameOverLabel.visible = false;
       //create the snake and the food 
       this.CreateSnake();
       this.CreateFood();
       
       if ('keyboard' in cc.sys.capabilities) {
            cc.eventManager.addListener({
                event: cc.EventListener.KEYBOARD,
                onKeyPressed: function (key, event) {
                   var target = event.getCurrentTarget();
                   //Only if space key is pressed and Collision Detected the game restart
                   if(bCollisionDetected && key == "32")
                   {
                        bCollisionDetected = false;
                        GameOverLabel.visible = false;
                        cc.director.resume();
                        target.CreateSnake();
                        target.CreateFood();
                        return;      
                   }
                   if(key == "37" && direction != "right") direction = "left";
                   else if(key == "38" && direction != "down") direction = "up";
                   else if(key == "39" && direction != "left") direction = "right";
                   else if(key == "40" && direction != "up") direction = "down";
                 } 
            }, this);
        } else {
            cc.log("KEYBOARD Not supported");
        }        
         
        //This is the main game loop
        this.schedule(this.GameLoop,0.2);
        
        return true;
    },
    CreateSnake:function() {
        score = 0;
        ScoreLabel.setString(setLabelString(score));
        direction = "right";
        if(( typeof SnakeArray != 'undefined' && SnakeArray instanceof Array ) && SnakeArray.length > 0 )
        {                
            for(var i = 0; i< SnakeArray.length; i++)             
            {
                    this.removeChild(SnakeArray[i],true);                   
            }
        } 
        SnakeArray = [];   
        var elmsToRemove = SnakeArray.length - length;
        if(elmsToRemove>1)
        {
            SnakeArray.splice(snakeLength-1,elmsToRemove);
        }
        for(var i = snakeLength-1; i>=0; i--)
        {
           var spriteSnakeCell = new SpriteSnakeCell(res.snakecell_png);
           spriteSnakeCell.setAnchorPoint(0,0);
           spriteSnakeCell.setTag(Enum.snakecell);          
           var xMov = (i*cellWidth)+BackGroundPos.x;
           var yMov = (spriteBackGround.y+BackGroundHeight)-cellWidth;
           spriteSnakeCell.x = xMov;
           spriteSnakeCell.y = yMov;
           this.addChild(spriteSnakeCell,2);           
           SnakeArray.push(spriteSnakeCell); 
        }
    },            
    CreateFood:function() {   
        //Check if food Exist , remove it from the game sprite
        if(this.getChildByTag(Enum.snakefood)!=null)
        {
            this.removeChildByTag(Enum.snakefood,true); 
        }       
        var spriteSnakeFood = new SpriteSnakeFood(res.snakefood_png);
        spriteSnakeFood.setAnchorPoint(0,0);
        spriteSnakeFood.setTag(Enum.snakefood);     
        this.addChild(spriteSnakeFood,2); 
        var rndValX = 0;
        var rndValY = 0;
        var min = 0; 
        var maxWidth = BackGroundWidth;
        var maxHeight = BackGroundHeight;
        var multiple = cellWidth;
        //Place it in some random position 
        rndValX = generate(min,maxWidth,multiple);
        rndValY = generate(min,maxHeight,multiple);
        var irndX = rndValX+BackGroundPos.x;
        var irndY = rndValY+BackGroundPos.y;
        SnakeFood = {
            x: irndX , 
            y: irndY  
        };
     
       spriteSnakeFood.x = SnakeFood.x; 
       spriteSnakeFood.y = SnakeFood.y;  
    },
    //The function will be called every 0.2 milii secound
    GameLoop:function (dt) {
        //get the snake head cell 
        var SnakeHeadX = SnakeArray[0].x;
        var SnakeHeadY = SnakeArray[0].y;
        //check which direction it is heading  
        switch(direction)
        {
            case "right":
                 SnakeHeadX+=cellWidth;
            break;
            case "left":
                 SnakeHeadX-=cellWidth;
            break;
            case "up":
                 SnakeHeadY+=cellWidth;
            break;
            case "down":
                  SnakeHeadY-=cellWidth;
            break;
            default:
                cc.log("direction not defind");
        }
        //Check if the snake head Collided with the walls or with it self 
        if(CollisionDetector(SnakeHeadX, SnakeHeadY, SnakeArray))
        {                 
                bCollisionDetected = true; 
                GameOverLabel.visible = true;
                cc.director.pause();
                return;
        }
        //Check if the snake head Collided with the food
        if(SnakeHeadX == SnakeFood.x && SnakeHeadY == SnakeFood.y)
        {
                //Add snake cell after the head position
                var spriteSnaketail = new SpriteSnakeCell(res.snakecell_png);
                spriteSnaketail.setAnchorPoint(0,0);
                spriteSnaketail.setTag(Enum.snakecell);
                this.addChild(spriteSnaketail,2);
                spriteSnaketail.x = SnakeHeadX;
                spriteSnaketail.y = SnakeHeadY;
                SnakeArray.unshift(spriteSnaketail);  
                //Add point to the points display
                ScoreLabel.setString(setLabelString(score++));
                //Create new food in new position
                this.CreateFood();         
        }
        else
        {       
                var spriteSnakeCellLast = SnakeArray.pop(); //pops out the last cell
                spriteSnakeCellLast.x = SnakeHeadX; 
                spriteSnakeCellLast.y = SnakeHeadY;
                SnakeArray.unshift(spriteSnakeCellLast);
        }
 
    }    
});
//The snake Collision Obstacles are side walls  and itself
function CollisionDetector(snakeHeadX,snakeHeadY,snakeArray)
{
        if(snakeHeadX < spriteBackGround.x || 
                snakeHeadX > BackGroundWidth || 
                snakeHeadY < spriteBackGround.y || 
                snakeHeadY > ((spriteBackGround.y+BackGroundHeight)-cellWidth))
        {
            return true;
        }
        for(var i = 0; i < snakeArray.length; i++)
        {
                if(snakeArray[i].x == snakeHeadX && snakeArray[i].y == snakeHeadY)
                {
                 return true;
                }
        }
        return false;
}
//Rendom number generator 
function generate(min, max, multiple) 
{
    var res = Math.floor(Math.random() * ((max - min) / multiple)) * multiple + min;
    return res;
}
//Convert number to string
function setLabelString(str)
{
    var stringScore = parseInt(score).toString();
    return stringScore;   
}
//Main Game container 
var SnakeJSScene = cc.Scene.extend({
    onEnter:function () {
        this._super();
        snakeJSGameLayer = new SnakeJSGameLayer();
        this.addChild(snakeJSGameLayer);
    }
});

  1. Lines 1 - 16 global variables , the scope of those will be accessible to all functions and objects.
  2. Lines 19 - 35 the game class's ,
    SpriteBackGround: contains all the games players snake and food.
    SpriteSnakeCell: the snake is build from these objetcs.
    SpriteSnakeFood: the red square which the snake need to eat.
  3. Lines 37 - 217 is the main layer that hold all the games element , all of them .
  4. Lines 39 the Layer constructor , it will initialize only once when the game start
    so all the initials setup of the game for example creating the snake (line 69) and the food (line 70)  and the main game sprite (line 44) will happen here.
  5. Lines 72 - 95 capture the keyboard events from the user.
    lines 78 - 86 when collision detected and the space key is pressed , restart the game .
    lines  87 - 90 when keyboard left / right /top /down keys pressed detected.
  6. Line 98 this is one of the most important functions in Cocos2d-x or any game in general.
    AKA : The Game Main Loop scheduler which fire the "Main Game loop function"
    in our case it is : GameLoop function (line 162).
  7. Lines 102 - 131 CreateSnake function creates the snake from SpriteSnakeCell objects .
    lines 106 -102 checks to see if there is already snake array defined and visible in the game before removing it , this function creates new snake ,only 1 snake allowed in the game.
    lines 119 - 130 the snake creation loop.
  8. Lines 132 -156 CreateFood function , create 1 food square each time called on random location on the game layer . only 1 food object allowed
  9. Lines 162 - 216 GameLoop function , in my view the most important function in the game logic it is function that invoked each N second in our case every 0.2 millisecond from the schedule function Line 98 . this is not always accurate it depends on the game structure and CPU.
    lines 167 - 183 detect which direction is pressed by the user so the program could direct it in the right direction 
  10. Lines 185 - 191 call the CollisionDetector if collision detected do as follow :
    1 . set the global variable bCollisionDetected to true.
    2.  set GameOverLabel to be visible so there is indication the game is over.
    3.  pause all cocos2d-x engine work as result the game stop,
  11. Lines 193 - 217 if the snake "head" cell that is the element in place 0 in the SnakeArray array
    remember we are working only with the snake head see lines 164 - 165.
    if the location of the head going to as the food location the program need to remove the food object and increase the snake size lines 196 - 206 .
    if not just continue the snake move lines 210 - 213.
  12. lines 219 -236 CollisionDetector function checks if the snake head collide the game spriteBackGround boundaries .
    checks also if the snake head doesn't collide it self lines 228 - 234 .
  13. Lines 238 -242 generate function simple random number generator. used to place the food object.
  14.  Lines 244 - 248 setLabelString function convert int value type to string type 
  15. Lines 250 - 255 create the game Scene , this is the root node of all our game stage nodes.
We are ready to publish our "SnakeJS" game in the next tutorial :
Snake Game Using Cocos2d-x HTML5  - PART 4


Play the final SnakeJS game:
http://meiry.github.io/SnakeJS-html5/publish/html5/
The SnakeJS source code:
https://github.com/meiry/SnakeJS-html5

3 comments:

  1. The game development tutorials look great. One of my friend has been looking for some professional tutorials for game development and I am definitely suggesting this blog to him.

    ReplyDelete
  2. Although I don't know how to work with codes and use them in the game somehow I was looking for these. Thank-you, better show this to my brother so he can help with it.

    ReplyDelete
  3. Wow, this is a very cool snake, the algorithm is matched reasonably enough.

    I wrote an applet program in Java thanks to javascript snake tutorial https://explainjava.com/javascript-snake/ but this solution was much easier than you suggest... I still have to learn a lot of theoretical and practical material on Java, in fact. Other tutorial developed the most effective for learning Java, it's for such beginners as I. All those who are on the first step of learning, this script will suit you the best.

    ReplyDelete

Note: Only a member of this blog may post a comment.