
In last issue’s tutorial we set the scene up and did all of the preliminary work so that now we can actually put everything together into a working game.
We’re working on creating a platform game that has a simple premise: collect the key and get out before the water rises and touches the player. To force the player into avoiding the water we made the player’s character a robot, as machinery and water don’t tend to mix particularly well – ask anyone whose mobile phone has fallen into a puddle, bath or met some other watery grave!
The game is controlled using cursor keys, with the up cursor key allowing players to jump. It features two levels to demonstrate how to go about creating multi levels, which is why the code in the game is so large. There is a lot of grunt work to do in terms of removing certain scene elements from memory and replacing them and we also have to create a way for the scene to be reset if the player dies without completing the game successfully. It is possible to add even more levels to the game and we give a brief overview of how you can achieve this at the end of the tutorial. Ready? Let’s get started!
Start the project
Either open the folder that you completed last issue or copy the tutorial folder to your web server, your local server or, if you are using Brackets, to anywhere on your hard drive from the cover CD. Open the ‘index.html’ page and before the closing script tag add this code.
001 function jump(){
002 if (jumping == false && inAir == false){
003 player.position.y += 10;
004 vy = -8;
005 jumping = true;
006 }
007 }
Check for collisions
The previous code enables the player to jump if they are not already jumping or already in the air, so we increase the player’s upward velocity, enabling them to jump. Now directly below that add the following code, which will check for collisions when we call this every frame for other objects such as keys.
001 function collisionPlayer (xPos, yPos, Radius){
002 var distX = xPos - player.position.x;
003 var distY = yPos - player.position.y;
004 var distR = Radius + 25;
005 if (distX * distX + distY * distY <= distR * distR){
006 return true;
007 }
008 }
Update the screen
The previous code works by passing in the current position of an object such as a key and drawing an invisible radius around that point. We do the same with the player and if they overlap then we know a collision has taken place. Our next code calls the renderer to update the screen; this is called every frame of the game.
001 function render() {
002 renderer.clear();
003 composer.render(0.01);
004 }
Extend JavaScript’s array
It’s actually quite difficult in JavaScript to remove an object from the middle of an array as we have to slice the array, remove the object and then stitch it back together. Here we are creating a function to do that, as this will be called when we want to remove the platforms to go from level to level.
001 Array.prototype.remove = function(from, to) {
002 var rest = this.slice((to || from) + 1 || this.length);
003 this.length = from < 0 ? this.length + from : from;
004 return this.push.apply(this, rest);
005 };
Easy insert for the array
Between levels we have to remove our collideable objects from an array. These are placed back in with the new ones so we use the following function to do so, as it makes it a little easier to call this at any other parts of the game. Save the game at this point so that you have your code up to here.
001 Array.prototype.insert = function (index, item) {
002 this.splice(index, 0, item);
003 };
Animate the game
The next function that we add animates the game and runs it at the desired frame rate. Here we call the animate function every frame so this becomes a self-repeating loop. We check that the game is in play and, if it is, we render the scene so it displays for the player. We call the update function so the game works.
001 function animate() {
002 requestAnimationFrame( animate );
003 if (inPlay){
004 render();
005 update();
006 }
007 }
Update the game
The main game loop is the update function and here we are about to start that. This is where all the game logic actually happens. The first thing we do here is add gravity, so we always have a downward velocity; if the player is in the air then we limit the downward velocity, or we’d fall too fast and miss the platforms!
001 function update(){
002 vy+=gravity;
003 if (inAir){
004 if (vy>5){
005 vy=5;
006 }
007 }
Check the movement
Now we check if the player is moving left or right and if they are within the edges of our room. Our room model just happens to have its walls positioned at -42 for the left and 521 for the right-hand side. If the player is within here, then we move the player in the appropriate direction by applying the velocity on the x axis (vx).
001 if (direction == “left” && player.position.x > -42) {
002 player.position.x+=vx;
003 }
004 if (direction == “right” && player.position.x < 521) {
005 player.position.x+=vx;
006 }
Hitting platforms
In order to check if our player is hitting platforms, we fire an invisible ray downwards and upwards to see if any of the objects in the collidableMeshList are there. This is commonly known as raycasting. This will bring back a list and distance of any objects above or below.
001 var originPoint = player.position.clone(); 002 ray.ray.origin.copy( originPoint ); 003 rayUp.ray.origin.copy( originPoint ); 004 var intersections2 = rayUp.intersectObjects ( collidableMeshList );
Check above the player
We check if there are objects above the player so that if the player hits a platform above their head, they fall back. We get the distance to objects above and then work out if the player is falling. If not, the player must be jumping and, if the distance is relatively close, then we turn off the upward velocity and allow gravity to turn off. This makes the player stop jumping and fall down.
001 if ( intersections2.length > 0 ) {
002 var distance = intersections2
[ 0 ].distance;
003 if ( falling == false && distance > 0
&& distance <= 23 ) {
004 vy = 0;
005 falling = true;
006 }
007 }
Check below the player
We check if there is anything below the player to work out if they are on a platform or not. This works similarly to the last step except that it applies gravity if the player is greater than a small distance from the platform. We will continue the opposite of this in the next step.
001 var intersections = ray.intersectObjects
( collidableMeshList );
002 if ( intersections.length > 0 ) {
003 var distance = intersections
[ 0 ].distance;
004 if ( distance > 0 && distance > 23 ) {
005 player.position.y -= vy;
006 inAir = true;
007 }else{
A watery grave
If the object below is relatively close, we need to check exactly what that object is. If it’s the water model then we will want to end the gameplay here. We call the levelUp function but pass through ‘false’. The levelUp code then knows that this is just a level reset and not an upgrade to the next level and appropriately resets all objects in this level for the player to try again.
001 if(intersections[ 0 ].object.name == “Water”){
002 levelUp(false);
003 }
Stop falling down
As we’ve established that we are now not falling onto water, then the only logical conclusion must be that this is a platform, so we turn off all of our variables that state we are in the air, jumping or falling. We also set our downward velocity to 0 so we actually stop falling.
001 falling = false; 002 inAir = false; 003 vy=0; 004 jumping = false; 005 } 006 }
Landing awkwardly
Sometimes we can land and end up being halfway through a platform. If that is the case then we need to use the first ‘if statement’ here in order to push the player back upwards, so they sit on top of the platform. We then check our player’s movement and if they are moving left then we rotate the player to face that direction and apply the appropriate velocity.
001 if (inAir == false && distance < 22.5){
002 player.position.y+=1;
003 }
004 if ( direction == “left” ){
005 vx = -2;
006 player.rotation.y = 3.14;
007 }
Moving right
To finish off our code we complete the right-hand movement. At this point save and test the game on a web server. The game will work and you can move left, right and also jump. The problem is, the camera doesn’t follow the player – and it’s quite hard to play off screen!
001 if ( direction == “right” ){
002 vx = 2;
003 player.rotation.y = 0;
004 }
005 }
Camera follows player
Please note that all the rest of the code for the tutorial has to go inside the last bracket of Step 15. Here we set the camera to look at the player and also to follow the player on the x and y-axis. We also push the water up every frame and rotate the key. Test again and we can see the player at all times.
001 camera.lookAt(player.position) 002 camera.position.x += ((player.position.x - camera.position.x))* .02; 003 camera.position.y += (((player.position. y+30 ) - camera.position.y)) * .08 004 water.position.y += rise; 005 key.rotation.y += 0.02; 006
The doors of perception
At this stage the player can collide with the water but not the key, enemy or exit. Let’s fix that by colliding with the key first of all. If the player collides with the key then we make that object invisible in the scene and set our open door variable to true. Test this now and you should be able to collect the key.
001 if (collisionPlayer (key.position.x, key.
position.y, 30)){
002 key.traverse( function ( object )
{ object.visible = false; } );
003 openDoor = true;
004 }
Exit through the door
Now as the open door animation works we have to let the player out of the door into the next room. Here we check if the player is touching the door and whether they have the key. If so, we increase the level by 1 and the speed of water rising is increased, we then call the levelUp function so we load the new level in.
001 if (collisionPlayer (door.position.x,
door.position.y, 20)){
002 if (openDoor){
003 level++;
004 rise += 0.1;
005 levelUp(true);
006 }
007 }
Moving the enemy
Testing the game, everything works except the enemy, so let’s start moving the enemy on the platform. We also make the enemy scale up and down so that it pulses as it moves. With the enemy being red, it should be obvious to the player that they should avoid it.
001 var time = Date.now() * 0.0009; 002 enemy.scale.x = enemy.scale.y = enemy. scale.z = (0.4 + 0.1 * Math.sin ( 7*time ) ); 003 enemy.position.x += eSpeed;
Move between points
We want the enemy to move back and forth so here we check the position of the enemy and reverse the direction when it reaches those points. Save and check the game to see the enemy fully moving now. The last step for us is to make the enemy kill the player if they are touch at any point on screen.
001 if (enemy.position.x < 100 || enemy.
position.x > 400 ){
002 eSpeed=eSpeed * -1;
003 }
Code finished
We add the collision code for the player and the enemy. All we need to do is call the levelUp function, telling it ‘false’, which makes it reset the current layer rather than the next level. We only set this to true when the player has successfully completed the level – see Step 18 for this.
001 if (collisionPlayer (enemy.position.x, enemy.position.y, 4)){
002 levelUp(false);
003 }
Game finished
Save the game now and test it in the browser via a web server, which can be a local web host or using the in-built Node.js server with Brackets. The game should be fully working with all collisions between the player and object, the levels should load and, while there are only two levels, the infrastructure is here to add more.
