Thursday, October 31, 2013

Adding Keyboard Input (part 2)

Now that we have included the new keyboard input helper file, how do we access it?  Well, Jerome Etienne designed his 'threex.keyboardstate.js' to work a lot like Three.js does, so we should feel comfortable using it. 

Open up our 'tempGame.js' file and add the following line near the top:    
var keyboard = new THREEx.KeyboardState();
That looks familiar doesn't it?  There IS a small difference however in this variable declaration.  Notice the small letter 'x' placed right after the word 'THREE' .  This tells us that the variable 'keyboard' will be using the new THREEx library rather than the older THREE library that we have had since the beginning of our project.  Other than that, it's identical in syntax, so we should feel right at home.

The 'keyboard' object now holds all the properties and functionality designed by Jerome to aid us in scanning the player's keyboard for keypresses in real time.  And since we want our game to quickly scan the keyboard on every animation frame (as fast as it can), we will put the actual scanning code inside the animation function.  Here's how the new keyboard scanning code lines look:
if( keyboard.pressed("D") ){
 cube.position.x = cube.position.x + 60 * frameTime;
}
Let's take a look at each line above.  Remember our 'if( )' statement a while back.  This 'if' statement tests for whether something is true or not.  And what we want to test for is if the user is pressing the 'D' key on their keyboard.  Jerome has designed a function for the keyboard object called '.pressed(" ")' .  Inside the double quotes we place the key on the keyboard that we want to test for.  In this case it's the 'D' key, so we type 'keyboard.pressed("D")' .  This will return 'true' if the 'D' key is pressed, and 'false' if it is not pressed.

Also recall that if the test comes back as 'true', then the 'if' statement will execute whatever is inside its curly braces { }.  In this case I have put a line of code to move our green cube's position to the right (positive x).  I chose '60' because it is medium-fast and looked good for this demo (feel free to experiment with this number).  Notice I also put ' * frameTime; ' at the end.  Get used to doing this for every line of code that deals with motion or animation.  Using 'frameTime' like this on all the game's moving objects will make sure that it looks the same on everyone's computer, no matter what the specs are for their device.

All that's left to do now is add the tests for the other 3 keys : the W, A, and S keys.  Here's how the whole thing looks now:
if( keyboard.pressed("D") ){
   cube.position.x = cube.position.x + 60 * frameTime;
}
if( keyboard.pressed("A") ){
   cube.position.x = cube.position.x - 60 * frameTime;
}
if( keyboard.pressed("W") ){
   cube.position.y = cube.position.y + 60 * frameTime;
}
if( keyboard.pressed("S") ){
   cube.position.y = cube.position.y - 60 * frameTime;
}
The above statements test for the 4 keys that most games use as input: the WASD keys.  Looking at each case above, the 'D' key will move the cube right (positive x), the 'A' key will move the cube left (negative x), the 'W' key will move the cube up (positive y) and the 'S' key will move the cube down (negative y).  

What's really cool is that Jerome's keyboard helper also can handle multiple keypresses.  So, if you hold down 'D' and 'A' at the same time, the +60 and -60 will cancel each other out, and you will see the cube stop.  If you hold the 'D' and the 'W' key at the same time, the cube will combine movement right (+60) and up (+60) at the same time, which is diagonally upper-right motion.  We now have 8 possible movement directions!  Thinking in a clockwise manner, they are: up, upper-right, right, lower-right, down, lower-left, left, and upper-left.  

All we have to do now is add all the above lines back into our tempGame.js file.  Copy and Paste the following code and save it as 'tempGame.js', overwriting the old one:

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
var clock = new THREE.Clock();

var keyboard = new THREEx.KeyboardState();

var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener('resize', onWindowResize, false); 

var cubeGeometry = new THREE.CubeGeometry(20,20,20);
var cubeMaterial = new THREE.MeshLambertMaterial({ color: 'rgb(0,255,0)' });
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
scene.add(cube);

var sphereGeometry = new THREE.SphereGeometry(5);
var sphereMaterial = new THREE.MeshBasicMaterial({ color: 'rgb(255,255,0)' });
var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
scene.add(sphere);

var light = new THREE.PointLight( 'rgb(255,255,255)', 1, 0 );
scene.add(light);

var sunRiseFlag = true;
var sunHeight = 0;
var frameTime = 0;

camera.position.y = 40;
camera.position.z = 160;

cube.rotation.x = 0.4;
cube.rotation.y = 0.6;

animate();

function animate(){
   
   requestAnimationFrame(animate);

   frameTime = clock.getDelta();

   if(sunRiseFlag == true){
      sunHeight = sunHeight + 60 * frameTime;
   }

   if(sunRiseFlag == false){
      sunHeight = sunHeight - 60 * frameTime;
   }

   if(sunHeight > 150){
      sunHeight = 150;
      sunRiseFlag = false;
   }

   if(sunHeight < 0){
      sunHeight = 0;
      sunRiseFlag = true;
   }

   light.position.set(50,sunHeight,50);
   sphere.position = light.position;

   
   if( keyboard.pressed("D") ){
 cube.position.x = cube.position.x + 60 * frameTime;
   }
   if( keyboard.pressed("A") ){
 cube.position.x = cube.position.x - 60 * frameTime;
   }
   if( keyboard.pressed("W") ){
 cube.position.y = cube.position.y + 60 * frameTime;
   }
   if( keyboard.pressed("S") ){
 cube.position.y = cube.position.y - 60 * frameTime;
   }

   renderer.render( scene, camera );
   
}


function onWindowResize(){
   camera.aspect = window.innerWidth / window.innerHeight;
   camera.updateProjectionMatrix();
   renderer.setSize( window.innerWidth, window.innerHeight );   
}

Go back and open the example01.html file with your browser.  If you saved everything correctly, you should see our new help text at the top of the webpage that reads: "Press W, A, S, and D keys to move the cube".  And when you press them - the cube moves!  We now have a responsive, interactive graphics demo.  It is not a game yet, but we are getting there.

I should note that this demo only works on desktops and laptops that have a physical keyboard attached.  For smart phones and tablets, we will have to figure out how to handle touch input on those devices.  We want ALL users to be able to play our games right?  Luckily, there are some libraries and helper routines that we can quickly add to make the demos/games work on mobile devices as well.  We will take a look at those in the near future. 

It's pretty awesome though that we only added about 10 lines of code and now we have user input, making our demo truly interactive.  That is the magic of JavaScript and helper libraries!

In the next posts we will add debug/info text to the top left-hand corner of our webpage that will tell us the number values of any game object that we want to inspect more closely.  This is really helpful when monitoring game performance, game variables that are quickly changing, and also when something isn't quite working how we want - it can help us correct any problems that come up. 

See you soon!