So this is going to be Part3 of my series on creating a retro style platform runner game for mobile devices. Originally I wanted to write about adding animations in this part, however I rethought the who whole part and find it more suitable to first add some movement and make the collision detection complete, as it is still just a very basic algorithm in the previous part.
So this is going to be the result of this part:
Refining the collision detection
Even though the collision detection works fine with one other object, there are some flaws that you will notice when adding more than one object the hero can collide with. So for example in the last part I described how we can prevent the hero from “ghosting through” an obstacle when he’s going really fast – and the described way to check this works fine, so I checked if the hero’s y-position changes to the other side of the obstacle without him detecting a collision – however what I forgot to check there was whether the hero was even on the same x-position as the obstacle. I now created a new method for the utils-class called ‘calculateCollision’, that I will also use for calculating horizontal collisions later:
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 |
function calculateCollision(obj, direction, collideables, moveBy) { moveBy = moveBy || {x:0,y:0}; if ( direction != 'x' && direction != 'y' ) { direction = 'x'; } var measure = direction == 'x' ? 'width' : 'height', oppositeDirection = direction == 'x' ? 'y' : 'x', oppositeMeasure = direction == 'x' ? 'height' : 'width', bounds = getBounds(obj), cbounds, collision = null, 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, moveBy.x, moveBy.y); } 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:125};' // -> it should still collide even with very high values var wentThroughForwards = ( bounds[direction] < cbounds[direction] && bounds[direction] + moveBy[direction] > cbounds[direction] ), wentThroughBackwards = ( bounds[direction] > cbounds[direction] && bounds[direction] + moveBy[direction] < cbounds[direction] ), withinOppositeBounds = !(bounds[oppositeDirection]+bounds[oppositeMeasure] < cbounds[oppositeDirection]) && !(bounds[oppositeDirection] > cbounds[oppositeDirection]+cbounds[oppositeMeasure]); if ( (wentThroughForwards || wentThroughBackwards) && withinOppositeBounds ) { moveBy[direction] = cbounds[direction] - bounds[direction]; } else { cc++; } } } if ( collision ) { var sign = Math.abs(moveBy[direction]) / moveBy[direction]; moveBy[direction] -= collision[measure] * sign; } return collision; } |
I won’t go much into detail about the method, as there is just one major change, beside the fix of “ghosting” that I mentioned above: The method now takes care of the ‘moveBy’ object, e.g.: If your hero wants to move by {x:0, y:15} and there is a collision detected at y=5, the method changes the move by to {x:0, y:5} so you allways automatically know how far you can move the hero until it collides.
This makes the hero’s tick-method a lot cleaner:
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 |
Hero.prototype.tick = function () { this.velocity.y += 1; var moveBy = {x:0, y:this.velocity.y}, collision = null, collideables = Game.getCollideables(); collision = calculateCollision(this, 'y', collideables, moveBy); // moveBy is now handled by 'calculateCollision' // and can also be 0 - therefore we won't have to worry this.y += moveBy.y; if ( !collision ) { if ( this.onGround ) { this.onGround = false; this.doubleJump = true; } } else { // the hero can only be 'onGround' // when he's hitting floor and not // some ceiling if ( moveBy.y > 0 ) { this.onGround = true; this.doubleJump = false; } this.velocity.y = 0; } // and now handle the x-movement moveBy = {x:this.velocity.x, y:0}; collision = calculateCollision(this, 'x', collideables, moveBy); this.x += moveBy.x; }; |
Movement
Adding movement is probably the most easiest thing to do here, just go to the initialize-method of the Hero-class and assign an x-value to the velocity property:
1 |
this.velocity = {x:10,y:25}; |
And because we already added the handling of the x-movement to the hero’s tick()-method, this is done. However if we look at the result, I guess it’s pretty obvious whats missing:
We need more platforms!
So we already added one platform in the initializeGame-method, to add some more platform we just need to create a loop:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// add a platform for the hero to collide with self.addPlatform(50 - assets[PLATFORM_IMAGE].width/2, h/1.25); // the number of platforms to be added depends on the screen-width var c, l = w / assets[PLATFORM_IMAGE].width * 1.5, atX=0, atY = h/1.25; for ( c = 1; c < l; c++ ) { // define a random x-koordinate relative to the last one var atX = (c-.5) * assets[PLATFORM_IMAGE].width*2 + (Math.random()*assets[PLATFORM_IMAGE].width-assets[PLATFORM_IMAGE].width/2); // define a random y-koordinate relative to the last one var atY = atY + Math.random() * 300 - 150; // add the platform self.addPlatform(atX,atY); } |
Now that we’ve added all the platform, we can jump around, however our hero still keeps running out of the view, to follow the hero with the camera, we simply reposition the “world” every frame according to the hero’s position – add this to the main-tick-method:
1 2 3 4 5 6 7 8 9 10 11 12 |
// if the hero "leaves" it's bounds of // screenWidth * 0.3 and screenHeight * 0.3(to both ends) // we will reposition the "world-container", so our hero // is allways visible if ( hero.x > w*.3 ) { world.x = -hero.x + w*.3; } if ( hero.y > h*.7 ) { world.y = -hero.y + h*.7; } else if ( hero.y < h*.3 ) { world.y = -hero.y + h*.3; } |
Now we have: a few platforms, the “camera” following our hero, but the course is not infinite yet!
There are a quite a few ways to make the course infinite, I’m just going to describe one: Since the hero cannot walk to the left, everything to the left of the visible screen can be “thrown” away, since it won’t become visible again! – However we are not going to throw away the platform that move out of the screen, we are just going to reposition them at the end of the track:
So in the jumpy.js with every tick() we also execute this check:
1 2 3 4 5 6 |
for ( var c = 0; c < collideables.length;; c++ ) { var p = collideables[c]; if ( p.localToGlobal(p.image.width,0).x < -10 ) { self.movePlatformToEnd(p); } } |
and then we of course need to define the method “movePlatformToEnd()”:
1 2 3 4 5 |
self.movePlatformToEnd = function(platform) { platform.x = self.lastPlatform.x + platform.image.width*2 + Math.random()*platform.image.width*2 - platform.image.width; platform.y = self.lastPlatform.y + Math.random() * 300 - 150; self.lastPlatform = platform; } |
As you can see – the platform will be positioned according to the “lastPlatform’s” position and then made the ‘lastPlatform’ itself – so when we first create all the platforms with ‘addPlatform()’ we also have to add this last line self.lastPlatform = platform;.
And that’s pretty much it – to round everything up, you can add a “reset()” method – to reset everything once the hero drops below a certain y-koordinate for example. Such a method should be rather trivial, so I’m not gonna go into detail here but I implemented such a method, so checkout the sources if you want to take a look at it.
Download the sources: here.
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 Platform Runner Game for mobile with EaselJS (Part 2) | indiegamr
Pingback: Retro Style Platform Runner Game for mobile with EaselJS (Part 1) | 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