⬆️ ⬇️

Develop HTML5 games in Intel XDK. Part 6. Snake treat and collision handling



Part 1 » Part 2 » Part 3 » Part 4 » Part 5 » Part 6 » Part 7 // End)





Today we will add something tasty for the snake to the game and implement a collision handling system.





Biscuit



Cookies (biscuit) is the main goal of the game. The player's task is to, by directing the snake, to allow her to eat as many of these treats as possible. In our implementation, a cookie is always the same sprite, the purpose of which is to appear on the screen, be eaten and appear again in another place.

')

How exactly the appearance of cookies is realized in the new position is one of the factors determining the complexity of the game. In our case, this is a completely random process. Create a cookie.



  1. Optional step. Create a new file - “biscuit.js” in the src folder. If you have done this, add it to the list in the “project.json” file. The list should look like the one below.



    "jsList" : [    "src/resource.js",    "src/biscuit.js",    "src/game.js" ] 




  2. Add the following code to biscuit.js to create a Biscuit sprite. If you missed the first step, place it in game.js



     var Biscuit = cc.Sprite.extend({   winSize: 0,   ctor: function(snakeParts) {       /*    */              this._super(asset.SnakeBiscuit_png);                /*  winSize */       this.winSize = cc.view.getDesignResolutionSize();       /*    */       this.randPosition(snakeParts);   },   }); 


    Notice that at the end of the constructor, the randPosition () method is called. When creating a sprite is placed on the screen at random.



  3. Add an implementation of the randPosition method.



     randPosition: function(snakeParts) {                  var step = 20;       var randNum = function(range) {           /*       range */           return Math.floor(Math.random() * range);              };       /*   ,     */       var range = {           x: (this.winSize.width / step) - 1,           y: (this.winSize.height / step) - 1                   }                                /*   */       var possible = {            x: randNum(range.x) * step,            y: randNum(range.y) * step       }                              var flag = true;       var hit = false;             /*     */       while (flag) {                      /*     */           for (var part = 0; part < snakeParts.length; part++) {    /*          */ if (snakeParts[part].x == possible.x &&   snakeParts[part].y == possible.y)   {                   /*   ,   hit */                   hit = true;               }           }           /*     */           if (hit == true) {                              /*   */               possible.x = randNum(range.x) * step;               possible.y = randNum(range.y) * step;                              hit = false;           } else { /*    */               /*    */               flag = false;               this.x = possible.x;               this.y = possible.y;           }                  }          }, 


The first part of the method is quite simple. Using a step that completely divides the width and height (20), we have created something like an invisible grid along which the snake moves, and in accordance with which the placement is made on the playing field of cookies. The game screen is divided into cells of 20x20 pixels.



The range object accepts the number of these cells, so we have information about the maximum number of cells along the x and y axes. Moreover, in order for the cookie not to pass over the edge of the screen, which does not look very good, the number of available cells is reduced in width and height by one.



After the maximum is known, a random number is generated in the available range and multiplied by the step size in order to determine the x and y coordinates of the cell to which we want to move the cookie.



Then, the obtained coordinates are checked for their coincidence with one of the fragments of the body of the snake. This is done so that the cookie does not appear directly on the snake. If it turns out that in the proposed position the cookie will intersect with the snake, new coordinates are generated and the check is performed again. This is done until a suitable position is obtained, to which the sprite symbolizing the treat will be moved.



Add cookies to the game screen



The code implementing the cookie is ready. It remains only to add it to the game screen.



  1. Add a new class member to the SnakeLayer layer. It will store a reference to the Biscuit object.



     biscuit: null, //    


  2. Add a new method, updateBisquit, which is designed to update the state of the cookie.



     updateBiscuit: function() { /*        */ if (this.biscuit) { /*   */ this.biscuit.randPosition(this.snakeParts);       /*   */ } else { /*    */ this.biscuit = new Biscuit(this.snakeParts); /*       - */ this.addChild(this.biscuit); } }, 


  3. Add an updateBiscuit call to the ctor method of the SnakeLayer layer.



     ctor: function () { ...               /*      */ this.updateBiscuit(); ... }, 


  4. Run the project in the emulator and take a look at the cookie, which a snake controlled by a player can soon try!





    Cookies and Snake



Now, even if a snake is sent to a cookie, it cannot yet eat it. Fix it.



Collision detection



We need to deal with a few questions. First, with what should happen when the snake finds a cookie. Secondly - with the behavior of the game character when it reaches the borders of the screen. Third, what happens when a snake bumps into its own tail.



Taking into account the peculiarities of the game's architecture, it is possible to perform a simple equality test for each coordinate for the coordinates of the snake's head, fragments of its body and cookies. This is possible because the ShakeParts and Biscuit objects can be located only in coordinates, which are divided completely by the move step (20 pixels). As a result, you have to compare the integers. If you had to work with fractional values, you would need a much more complex system.



Let's deal with the implementation of a collision detection system.



  1. Add the following method to SnakeLayer.



     checkCollision: function() { var winSize = cc.view.getDesignResolutionSize(); var head = this.snakeParts[0]; var body = this.snakeParts; /*      */ if (head.x < 0) { head.x = winSize.width; } else if (head.x > winSize.width) { head.x = 0; } if (head.y < 0) { head.y = winSize.height; } else if (head.y > winSize.height) { head.y = 0; } /*     */ for (var part = 1; part < body.length; part++) { if (head.x == body[part].x && head.y == body[part].y) { /*   GameOver */ } }   /*     */ if (head.x == this.biscuit.x && head.y == this.biscuit.y) { /*    */ this.updateBiscuit(); /*    */ this.addPart(); } }, 


  2. Add a new method call to the update method of the SnakeLayer object.



     update: function(dt) { /*  ,    */ var up = 1; /*       */ if (this.counter < this.interval) { this.counter += dt;  } else { this.counter = 0; /*   */ this.moveSnake(this.curDir);                       /* ,       ,      */ this.checkCollision();           } }, 


The checkCollision method performs three checks. He checks if the ShakeHead object has crossed one of the edges of the screen, and if this happens, moves the snake so that it continues moving from the other side of the screen. He checks to see if the snake's head has collided with one of the fragments of its body. Now we do nothing if this happens, but in the next part we will deal with it. And, finally, a test is conducted on the collision of a snake with a cookie. If this happens, the cookie is transferred to a new location, and the snake grows.



Since now the snake, while eating cookies, is growing, you can remove the code that sets its initial length from the ctor method. Here is a loop that is no longer needed.



 ctor: function () { ... for (var parts = 0; parts < 10; parts++) { this.addPart(); } }, 


Correction of control deficiencies



In the management of the snake there is a fundamental flaw. She can crawl on herself. And, if only it is not the very beginning, and the snake does not consist of only one head, it should not, even when the corresponding button is pressed, move in the direction opposite to that in which it is currently moving. This will prevent an unexpected loss when the snake begins to move in the direction of its own body and, of course, its head faces one of its fragments.



In order to fix this, you need to track the current state of the snake, namely the direction in which it moves.



  1. Add a new member of the class nextDir to SnakeLayer.



     var SnakeLayer = cc.Layer.extend({   ...   curDir: 0,   nextDir: 0,   ... }); 


  2. Remove the dir parameter from moveSnake.



     var SnakeLayer = cc.Layer.extend({   ...   moveSnake: function() {       ...                      },   update: function(dt) {       ...       this.moveSnake(this.nextDir);       this.moveSnake();                             ...   }  }); 


  3. Replace all curDir references with nextDir.



     var SnakeLayer = cc.Layer.extend({   ...   ctor: function () {                  /*     */       cc.eventManager.addListener({ ...               /*     */               if (keyMap[keyCode] !== undefined) {                   //targ.curDir = keyMap[keyCode]; //   targ.nextDir = keyMap[keyCode];  //                 }                                     }                  }, this);             /*     */       cc.eventManager.addListener({           onTouchMoved: function(touch, event) { ... /*    */ if (delta.x !== 0 && delta.y !== 0) { if (Math.abs(delta.x) > Math.abs(delta.y)) {                   /*     */ //targ.curDir = Math.sign(delta.x) * right;  //   targ.nextDir = Math.sign(delta.x) * right;   //   } else if (Math.abs(delta.x) < Math.abs(delta.y)) { /*     */ //targ.curDir = Math.sign(delta.y) * up; //                         targ.nextDir = Math.sign(delta.y) * up;  //                       }                           }           }                        }, this);   },      moveSnake: function() {       ...                           /*    */ /*       */          //if (dirMap[dir] !== undefined) {       //   dirMap[dir]();          //} /*    */       /*       */          if (dirMap[this.curDir] !== undefined) {           dirMap[this.curDir]();          } ...   },   ... }); 


  4. Add a block of code to moveSnake that prevents the snake from “crawling” on itself. This code block will check two conditions:



    a. Is the new direction of movement (nextDir) not opposite to the current direction (curDir)?

    b. Does the snake consist of only one head (the length is 1)?



    If any of the statements is true, curDir is replaced with nextDir.



     var SnakeLayer = cc.Layer.extend({   ...   moveSnake: function() {             /*   ,              */       if ((this.nextDir * -1) != this.curDir || this.snakeParts.length == 1)  {                      this.curDir = this.nextDir;       }             /*      */          if (dirMap[this.curDir] !== undefined) {           dirMap[this.curDir]();          }        ...   },   ... }); 


findings



Let's summarize today's lesson. You have learned the following.



Here's what you could do, having mastered the material.





The changes made it possible to make our version of the classic Snake an almost finished game. The snake can be controlled without the risk of unexpectedly losing, it regularly eats cookies, grows. But the project still lacks something. Namely, this is the main menu, scoring system, setting the complexity of the game. About it - next time.





Part 1 » Part 2 » Part 3 » Part 4 » Part 5 » Part 6 » Part 7 // End)

Source: https://habr.com/ru/post/281873/



All Articles