Thursday, November 21, 2013

Starting Our First Game, SpacePong3D!

If you have been following along with this blog, you might be saying "These graphics demos and code examples are nice, but when are we going to start making a real 3D game?"  Well, I'm happy to tell you that we are finally ready to start our first game!  

Sorry if the game setup blog posts before this were kind of dull.  But this is a necessary chore for anyone wanting to create a computer game.  Here's the good news:  now that we have gotten the boring setup code out of the way, we can now focus on the fun part: designing and building a real 3D game!  And because we have some basic template code in place to handle different devices, different speeds of hardware,  and different methods of user input, we can relax and feel confident that whatever games we make will work and run smoothly on most devices in the world today.

So, what kind of game should we start with?  There's a saying I like that goes, "Learn to walk before you run."  This phrase applies to a lot of things in life, and I believe it applies to game programming as well.  We must start somewhere to get some experience with coding games.  And what better place to start, than with one of the first and most popular computer games of all time - Pong.  

This may date me, but my first video game experience as a kid was playing Pong on my new Atari 2600 home system.  It is a simple game with simple elements, but it is a classic that stands the test of time.  Countless clones and emulations have been made from its original concept.  Although we will not be the first, nor the last, we will make a 3D version of this classic with some extra tricks.  I chose Pong because it is an excellent teaching example.  The lessons we learn by building a simple 3D game like this will carry over to our more ambitious future game projects.

Since the name Pong is already taken, I thought maybe we could spice up our game's name and call it SpacePong3D.  As the name implies, it will be a Pong-style game set in outer space, with true 3D graphics!  We will take an old flat 2D game and make it pop out into the 3rd dimension.  Sound fun?  I hope so - let's get started!           

Before we start coding, let's familiarize ourselves with the original Pong game.  Here's a screenshot:



The gameplay is sort of like Table Tennis, as viewed from high above the table (but nowhere near as fast as real Table Tennis matches!) .   Actually the game environment is more like Air Hockey because Air Hockey is a flat 2D game where as Table Tennis is 3D (you can go up-and-down as well as side-to-side and forwards and backwards).  Anyway, in Pong, 2 Human players can play against each other, or you can have 1 Human player vs. a Computer-controlled AI opponent.  Each player controls a paddle which hits a small ball back and forth.  A player tries to score against their opponent by having their opponent miss the ball.  When the ball slips past a player's paddle, their opponent receives 1 point.  When a pre-determined score number is reached, for example 10 points,  the player who reaches that score first wins the game.

OK so now we now the basics of Pong.  How do we add something new to the game?  One way is to make the paddles and ball have depth as well.  The rectangles pictured above can be turned into elongated cubes and the ball could be turned into a sphere with depth.  However many clones like this have already been made.  Plus, those re-makes are not being played in true 3D (they have no up and down motion, only side to side).  The gameplay, the motion of the ball, is still confined to a flat table.

Let's go one step further!  Instead of confining the play to a flat 2D table, let's give the table depth as well, and make it a hollow cube!  Now the paddles and ball will be able to move around freely inside the 3D cube space.  And since we have the awesome Three.js library on our side, this 3D updating of the classic game will be within our reach!     

We have been using the same old file names for our demo projects up until now.  I think it's time to create some new files with new names for our game.  The good news is that we don't have to start from scratch.  We can just use our existing code inside example01.html and tempGame.js, and change them to meet our new needs.  Always keep these old files around - that's why I called it tempGame.js - it stands for 'template Game'.   We might update tempGame.js somewhere in the future if we want to add some more functionality to every game, but for now we are just going to copy, paste, and rename.

  Here is our old 'example01.html' file.  Copy and Paste the following code, but save as 'SpacePong3D.html' instead.  You can place it right beside the old example01.html file in the same folder

<!DOCTYPE html>
<html>
   <head>
      <title> Hello Three.js </title>
   </head>
   <body>
      <div id="help" style="position:fixed; left:40%; top:4%; color:grey;">
         Desktop: Press W A S D keys, or Click/Drag Mouse to move the cube
      </div>

      <div id="help2" style="position:fixed; left:40%; top:8%; color:grey;">
         Mobile: Hold finger down and slow Swipe to move the cube
      </div>

      <div id="debug1" style="position:fixed; left:5%; top:4%; color:grey;">
         Debug Info 
      </div>

      <div id="debug2" style="position:fixed; left:5%; top:8%; color:grey;">
         Debug Info 
      </div>

      <script src="js/three.min.js"></script>
      <script src="js/threex.keyboardstate.js"></script>
      <script src="js/virtualjoystick.js"></script> 
      <script src="js/tempGame.js"></script>     
   </body>
</html>


Now Copy and Paste the following old tempGame.js code, but instead save it as 'pongGame.js' , and place it inside your 'js' folder, right beside the old tempGame.js:
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 joystick = new VirtualJoystick({
                      mouseSupport: true
                   });

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;

var debugText1 = document.getElementById("debug1");
var debugText2 = document.getElementById("debug2");

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;
   }

   if( joystick.right() ){
 cube.position.x = cube.position.x + 60 * frameTime;    
   }
   if( joystick.left() ){
 cube.position.x = cube.position.x - 60 * frameTime;     
   }
   if( joystick.up() ){
 cube.position.y = cube.position.y + 60 * frameTime;       
   }
   if( joystick.down() ){
 cube.position.y = cube.position.y - 60 * frameTime;
   }

   renderer.render( scene, camera );
   
   debugText1.innerHTML = "Cube position X: " + cube.position.x.toFixed(1);
   debugText2.innerHTML = "Cube position Y: " + cube.position.y.toFixed(1);
   
}


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


Looking at the entire project, by now you should have a file called SpacePong3D.html and a 'js' folder that sits right beside it.  Inside this 'js' folder, you should have a file called pongGame.js, as well as ones that we have been using:  three.min.js , virtualjoystick.js , and threex.keyboardstate.js . 

If you have all these files and folders in the right place, we are now ready to start coding!  We will begin with the SpacePong3D.html file in the next post.

See you soon!