This is Part 2 of my tutorial on How to create a Runner Game for mobile with EaselJS – in this part i’m going to cover the collision-detection between two objects in EaselJS.
Types of Collision Detection
There are several types of collision detection methods, I’m going to explain two of the most commong ways to detect a collision – if you want more/detailed information on the subject I would encourage you to google for it or try this article as an introduction to the topic.
1) Distance Based- or Circle-Collision
This is probably the simplest type of collision detection. What you basically have to do is to set a collision-radius for each object, then calculate the distance between the objects and see if the distrance is lower than the radii combined. However the following image shows, that this kind of detection won’t be suitable for our platform game:
As you can see, with this method, a collision is detected way before an actual collision occures. This is due to the fact, that the platform has a very different hight and width. However a circle-collision-detection would be perfect for any Pool-game for example.
2) Bounding-Box-Collision
This type of detection is (is many cases, not all) more accurate than the circle-based detection and it basically works by defining a rectangle of the boundaries for each object and then check if those two rectangles intersect in any way.
There are two(or probably more, but i’m only going to point out two of them) ways to collect a collision this way.
The first method is actually not checking for a collision, but for non-collisions. This simply requires four if clauses:
- a) is the space between rect1.left and rect2.right ?
- b) is there space between rect1.right and rect2.left ?
- c) is there space between rect1.top and rect2.bottom ?
- d) is there space between rect1.bottom and rect2.top ?
If any (1 or more) of the statements above are true, then there is no intersection between the rectangles, however if all of the statements are false, there must be an intersection.
And here is the according piece of code:
1 2 3 4 5 6 7 8 9 |
function checkRectIntersection(rect1,rect2) { if ( rect1.x >= rect2.x + rect2.width // a) || rect1.x + rect1.width <= rect2.x // b) || rect1.y >= rect2.y + rect2.height // c) || rect1.y + rect1.height <= rect2.y ) // d) return false; return true; }; |
Even though this method looks super-simple – we are unfortunately NOT going to use this method. Instead we will use a method that checks for an intersection AND returns the size of the intersecting area.
To explain this a little better i drew up a graphic for this:
So what we are going to do, is calculate the X- and the Y-overlapping value, if both values are greater than 0, there is an intersecting area.
1 2 |
dx = |cx1 - cx2| - (hw1 + hw2) dy = |cy1 - cy2| - (hh1 + hh2) |
=> if dx < 0 and dy < 0 then there is a collision, in this case we can return -dx and -dy as overlapping values
And the method in JS looks like this – I also added two parameters x and y, to calculate where the rect1 wants to move to:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
function calculateIntersection(rect1, rect2, x, y) { // prevent x|y from being null||undefined x = x || 0; y = y || 0; // first we have to calculate the // center of each rectangle and half of // width and height var dx, dy, r1={}, r2={}; r1.cx = rect1.x+x+(r1.hw = (rect1.width /2)); r1.cy = rect1.y+y+(r1.hh = (rect1.height/2)); r2.cx = rect2.x + (r2.hw = (rect2.width /2)); r2.cy = rect2.y + (r2.hh = (rect2.height/2)); dx = Math.abs(r1.cx-r2.cx) - (r1.hw + r2.hw); dy = Math.abs(r1.cy-r2.cy) - (r1.hh + r2.hh); if (dx < 0 && dy < 0) { return {width:-dx,height:-dy}; } else { return null; } } |
This is also the method we are going to use in the game. There are plenty of other methods that are probably more accurate(a mix of Circle/BoundBox-collision, Pixel-perfect collision ect.), but usually take longer for their calculation. And for the sake of keeping everything simple our current method will do a pretty good job and you won’t really notice any flaws of the collisions in the game. I could spend a lot more time on figuring out a pixel-perfect collision algorithm ect., just to get rid of the last “free spaces” – however it won’t look that much more realistic to be worth the effort. This is actually a good tipp for new game-developers:
Go with simple!:
In 90% of the cases a simple version works just (almost) as good as a ‘perfect’ version, that would have taken much much longer to develop. The same with making a method as universial as possible because you say “I might need it later for something else” – there’s a very good chance, that you will not need it- and if you do: Do it when you need it, you won’t loose any time.
So now we’ve got our collision-method – how do we use it in the game?
Implementation
Before we can acutally use the detection-method in the game, we need the bounding rectangle of our EaselJS DisplayObject – however since EaselJS is not providing such a function(yet?), we will use the method from here and put it into our utils.js(included in the zip-file from Part 1) as well as the “calculateIntersection()” method.
We then will update the tick-method of our Hero-class with the following:
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 |
Hero.prototype.tick = function () { this.velocity.y += 1; // preparing the variables var c = 0, cc = 0, addY = this.velocity.y, bounds = getBounds(this), cbounds, collision = null, collideables = Game.getCollideables(); cc=0; // for each collideable object we will calculate the // bounding-rectangle and then check for an intersection // of the hero's future position's bounding-rectangle while ( !collision && cc < collideables.length ) { cbounds = getBounds(collideables[cc]); if ( collideables[cc].isVisible ) { collision = calculateIntersection(bounds, cbounds, 0, addY); } if ( !collision && collideables[cc].isVisible ) { // if there was NO collision detected, but somehow // the hero got onto the "other side" of an object (high velocity e.g.), // then we will detect this here, and adjust the velocity according to // it to prevent the Hero from "ghosting" through objects // try messing with the 'this.velocity = {x:0,y:25};' // -> it should still collide even with very high values if ( ( bounds.y < cbounds.y && bounds.y + addY > cbounds.y ) || ( bounds.y > cbounds.y && bounds.y + addY < cbounds.y ) ) { addY = cbounds.y - bounds.y; } else { cc++; } } } // if no collision was to be found, just // move the hero to it's new position if ( !collision ) { this.y += addY; if ( this.onGround ) { this.onGround = false; this.doubleJump = true; } // else move the hero as far as possible // and then make it stop and tell the // game, that the hero is now "an the ground" } else { this.y += addY - collision.height; if ( addY > 0 ) { this.onGround = true; this.doubleJump = false; } this.velocity.y = 0; } } |
now we have to add a platform for the hero to collide with, I therefor create a method called “addPlatform”, that should mostly explain itself:
1 2 3 4 5 6 7 8 9 10 11 12 |
this.addPlatform = function(x,y) { x = Math.round(x); y = Math.round(y); var platform = new Bitmap(assets[PLATFORM_IMAGE]); platform.x = x; platform.y = y; platform.snapToPixel = true; world.addChild(platform); collideables.push(platform); } |
also I did update the javascript-file holding the main methods and the game loop ect. quite a bit(to make the post not too big, I left out a few parts – download the ZIP-file to get the full source):
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 |
var HERO_IMAGE = 'assets/hero.png', PLATFORM_IMAGE = 'assets/platform.png'; function _game() { window.Game = this; var self = this, ticks = 0, canvas, stage, world, hero, w = getWidth(), h = getHeight(), assets = [], keyDown = false; // holds all collideable objects var collideables = []; this.getCollideables = function() { return collideables; }; // starts to load all the assets this.preloadResources = function() { self.loadImage(HERO_IMAGE); self.loadImage(PLATFORM_IMAGE); } var requestedAssets = 0, loadedAssets = 0; // loads the assets and keeps track // of how many assets where there to // be loaded this.loadImage = function(e) { var img = new Image(); img.onload = self.onLoadedAsset; img.src = e; assets[e] = img; ++requestedAssets; } // each time an asset is loaded // check if all assets are complete // and initialize the game, if so this.onLoadedAsset = function(e) { ++loadedAssets; if ( loadedAssets == requestedAssets ) { self.initializeGame(); } } this.initializeGame = function() { /* setting up stage&listeners - download ZIP */ // creating the Hero, and assign an image // also position the hero in the middle of the screen hero = new Hero(assets[HERO_IMAGE]); hero.x = w/2 hero.y = h/2; world.addChild(hero); // add a platform for the hero to collide with self.addPlatform(w/2 - assets[PLATFORM_IMAGE].width/2, h/1.25); } this.tick = function(e) { ticks++; hero.tick(); stage.update(); } // this method adds a platform at the // given x- and y-coordinates and adds // it to the collideables-array this.addPlatform = function(x,y) { x = Math.round(x); y = Math.round(y); var platform = new Bitmap(assets[PLATFORM_IMAGE]); platform.x = x; platform.y = y; platform.snapToPixel = true; world.addChild(platform); collideables.push(platform); } /* input-listeners - download ZIP */ self.preloadResources(); }; new _game(); |
When you try this out – it should look similar to this:
Some of you might noticed that, the hero is also able to jump twice now: I removed the reset()-method from Hero.js and replaced it with a similar jump()-method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Hero.prototype.jump = function() { // if the hero is "on the ground" // let him jump, physically correct! if ( this.onGround ) { this.velocity.y = -17; this.onGround = false; this.doubleJump = true; // we want the hero to be able to // jump once more when he is in the // air - after that, he has to wait // to lang somewhere on the ground } else if ( this.doubleJump ) { this.velocity.y = -17; this.doubleJump = false; } } |
And that’s pretty much all there is to get a basic collision detection working – Again: There are PLENTY of other methods, some of them maybe better in performance and/or accuracy, however I find this approach to be both fairly simple, effective and easy to follow – but suggestions for different approaches are allways welcome, just leave a comment!
The sources for this part can be found right here: Download Part2.zip
Agenda
- Part 1: User-Input (Keystrokes, MouseClick, Touch) & Movement
- Part 2: Collisions between objects
- Part 3: Movement&More Collision
- Part 4: Adjustments for mobile devices
- Part 5: Polishing up the game with animations & eye candy
Pingback: Retro Style Plattform Runner Game for mobile with EaselJS (Part 1) | indiegamr
Pingback: Retro Style Platform Runner Game for mobile with EaselJS (Part 3) – adding movement & more collision | indiegamr
Pingback: Retro Style Platform Runner Game for mobile with EaselJS (Part 4) – adjusting to mobile displays | indiegamr
Pingback: Retro Style Platform Runner Game for mobile with EaselJS (Part 5) – Animations and polishing | indiegamr
Pingback: The Big List of HTML5 Game Tutorials | Game Venture