Showing posts with label complete game. Show all posts
Showing posts with label complete game. Show all posts

Monday, February 24, 2014

Flying Fox Part 3 (Game Programming)

Today is the deadline for the Flappy Jam (read all about it here: http://firefoxosgaming.blogspot.com/2014/02/game-jams-game-programming.html. I definitely didn't write this series of posts on Flying Fox just to enter the jam, but I admit that I was interested in how I could make a simple version of Flappy Bird. The main interest was that I wanted to try a one-finger one-meaning game. The earlier two posts in this series were:

Making the Fox Fly
http://firefoxosgaming.blogspot.com/2014/02/flying-fox-part-1-game-programming.html

Making the Clouds Move
http://firefoxosgaming.blogspot.com/2014/02/svg-and-z-order-game-programming.html

The first post showed how to import a complex SVG drawing made in Inkscape and make it jump up if it is touched and to fall down if it is not. The second post talked about z-order in SVG (there isn't any) and showed how to make clouds fly by, creating the illusion that the Fox is flying.

Today's post wraps up things. Three final parts are added:

1. Pipes are moving and the Fox must avoid them.

2. A way to detect if the Fox collides with the pipe.

3. The result of any Fox/Pipe collision.

Here's what it will look like when the Fox is flying:


Here's the Fox about to hit a pipe:


And here is the exploded Fox after he hits the pipe a second later:


And so, here is the code, all 408 lines. (Not too much, really, and I hope it is well-enough organized to follow along. A lot of this code is reused from other posts.)

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <title>
    Floppy Fox Part 3
  </title>
 
  <style>
        
    #mySVG
    {
      width: 360px;
      height: 620px;
      position: absolute;
      top: 0px;
      left: 0px;
    }
      
  </style>

  <script>

  // --- Global variables ---

  // Width and height of board in pixels
  var boardWidth = 360;
  var boardHeight = 620;
  var boardWidthPixels = boardWidth + "px";
  var boardHeightPixels = boardHeight + "px";

  // URL for W3C definition of SVG elements
  var svgURL =
    "http://www.w3.org/2000/svg";
  var svgLink =
    "http://www.w3.org/1999/xlink";

  // Variables for fox initial position.
  var foxX = boardWidth / 2 - 100;
  var foxY = boardHeight / 2;
  var foxH = 50;
  var foxW = 50;
 
  // Cloud initial x coordinate value.
  var cloud1X = boardWidth;
  var cloud2X = boardWidth / 2;
 
  // Pipe globals
  var uPipeLength = 260;
  var lPipeLength = 260;
  var pipeWidth = 50;
  var uPipeX = boardWidth - pipeWidth;


  // Use requestAnimationFrame.
  var requestAnimationFrame =
  window.requestAnimationFrame ||
  window.mozRequestAnimationFrame ||
  window.webkitRequestAnimationFrame ||
  window.msRequestAnimationFrame;

  // --- Event listeners ---

  // Page load event listener.
  window.addEventListener("load",
    runFirst, false);

  // Runs when page loads.
  function runFirst() {

    // Define the game board as SVG element.
    gameBoard =
      document.
        createElementNS(svgURL, "svg");

    // Width and height of SVG board element.
    gameBoard.width.baseVal.valueAsString =
      boardWidthPixels;
    gameBoard.height.baseVal.valueAsString =
      boardHeightPixels;
    gameBoard.id = "mySVG";
   
    // You must append the board to the body.
    document.getElementById("pageBody").
      appendChild(gameBoard);

    // Add a background element for color.
    gameBack =
      document.
        createElementNS(svgURL, "rect");
    gameBack.x.baseVal.valueAsString =
      "0px";
    gameBack.y.baseVal.valueAsString =
      "0px";
    gameBack.width.baseVal.valueAsString =
      boardWidthPixels;
    gameBack.height.baseVal.valueAsString =
      boardHeightPixels;       
    gameBack.style.
          setProperty("fill","Thistle","");
         
    // Attach the background to  game board.
    gameBoard.appendChild(gameBack);
   
    // Create cloud1.
    cloud1 =
      document.
        createElementNS(svgURL, "circle");
    cloud1.cx.baseVal.valueAsString =
      cloud1X + "px";
    cloud1.cy.baseVal.valueAsString =
      "400px";
    cloud1.r.baseVal.valueAsString =
      "50px";       
    cloud1.style.
          setProperty("fill","blue","");
         
    // Attach cloud1 to the game board.
    gameBoard.appendChild(cloud1);
   
    // Create cloud2.
    cloud2 =
      document.
        createElementNS(svgURL, "circle");
    cloud2.cx.baseVal.valueAsString =
      cloud2X + "px";
    cloud2.cy.baseVal.valueAsString =
      "200px";
    cloud2.r.baseVal.valueAsString =
      "50px";       
    cloud2.style.
          setProperty("fill","green","");
         
    // Attach cloud1 to the game board.
    gameBoard.appendChild(cloud2);
  
    // Mouse down event listener
    gameBoard.addEventListener(
      "mousedown", foxJump, false);
       
    // Import SVG fox image.
    // Set height, width, x, and y.
    myFox = document.
      createElementNS(svgURL,"image");
    myFox.setAttribute("height",foxH);
    myFox.setAttribute("width",foxW);
    myFox.setAttribute("x",foxX);
    myFox.setAttribute("y",foxY);
   
    // Use different namespace for xlink.
    myFox.setAttributeNS(svgLink,
      "href","fox.svg");
   
    // Add the fox to the game board.
    gameBoard.appendChild(myFox);
   
    // Pipes
    // Upper Pipe
    // Define the pipe.
    uPipe = document.
      createElementNS(svgURL,"rect");
    // Set the values.
    uPipe.x.baseVal.valueAsString =
      uPipeX + "px";
    uPipe.y.baseVal.valueAsString = "0px";
    uPipe.width.baseVal.valueAsString =
      pipeWidth + "px";
    uPipe.height.baseVal.valueAsString =
      uPipeLength + "px";
    uPipe.style.setProperty("fill","red","");
    // Attach it to the board.
    gameBoard.appendChild(uPipe);

    // Lower Pipe
    // Define the pipe.
    lPipe = document.
      createElementNS(svgURL,"rect");
    // Set the values.
    lPipe.x.baseVal.valueAsString =
      uPipeX + "px";
    lPipe.y.baseVal.valueAsString =
      uPipeLength + 100 + "px";
    lPipe.width.baseVal.valueAsString =
      pipeWidth + "px";
    lPipe.height.baseVal.valueAsString =
      uPipeLength + "px";
    lPipe.style.setProperty("fill","red","");
    // Attach it to the board.
    gameBoard.appendChild(lPipe);
  
    // Start the game loop.
    gameLoop();
  }

  // Game loop.
  function gameLoop(){

    // Endless loop requestAnimationFrame.
    requestAnimationFrame(gameLoop);

    // The fox waits to drop.
    // Only you can save him.
    foxWait();
   
    // Move the clouds.
    moveClouds();
   
    // Move the pipes.
    movePipes();
  }
 
  // Game over!
  function gameOver() {

    // Make the fox big!
    myFox.setAttribute("height",200);
    myFox.setAttribute("width",200);
       
    //Wait wait a short time!
    foxBlowUpTimer =
      setTimeout(foxBlowUp, 700);
  }
 
  // Fox blow up!
  function foxBlowUp() { 
   
    // Reset fox values.
    foxX = boardWidth / 2 - 100;
    foxY = boardHeight / 2;
    foxW = 50;
    foxH = 50;
   
    // Reset fox position.
    myFox.setAttribute("height",foxH);
    myFox.setAttribute("width",foxW);
    myFox.setAttribute("x",foxX);
    myFox.setAttribute("y",foxY); 
  }
 
  // Move the pipes.
  function movePipes() {

    // Move the pipe X value. 
    uPipeX = uPipeX - 2;
   
    // Check if pipes off left edge.
    if (uPipeX < -50) {
   
      // If too far, send to right side.
      uPipeX = boardWidth;
     
      // Resize pipes.
      // Generate random change.
      change =
        Math.
          floor((Math.random()*60)+1) - 30;
       
     
      // Calculate new pipe lengths.
      uPipeLength = uPipeLength + change;
      lPipeLength = lPipeLength - change;
   
      // If upper pipe is too short, back up.
      if (uPipeLength < 100) {
        uPipeLength = uPipeLength - change;
        lPipeLength = lPipeLength + change;
        console.log("UPPER PIPE TOO SHORT");
      }   
     
      // If lower pipe is too short, back up.   
      if ((uPipeLength + 100) >
          (boardHeight)) {
        uPipeLength = uPipeLength - change;
        lPipeLength = lPipeLength + change;
        console.log("LOWER PIPE TOO SHORT");
      }
           
      // Draw upper pipe if new size.
      uPipe.height.baseVal.valueAsString =
        uPipeLength  + "px";
     
      // Draw lower pipe if new size.     
      lPipe.y.baseVal.valueAsString =
        uPipeLength + 150 + "px";
      lPipe.height.baseVal.valueAsString =
        lPipeLength + "px";
      
      // Display current values.      
      console.log(uPipeLength,
        lPipeLength, change);
    }
   
    // Test for upper pipe collision.
    if ((foxX > uPipeX) &&
        (foxX < (uPipeX + 50)) &&
        (foxY < uPipeLength)
        ){
     
      console.log("Hit upper pipe. Ouch!");
     
      // Game is over.
      gameOver();
    }

    // Test for lower pipe collision.
    if ((foxX > uPipeX) &&
        (foxX < (uPipeX + 50)) &&
        (foxY > (boardHeight - lPipeLength))
        ){
     
      console.log("Hit lower pipe. Ouch!");

      // Game is over.
      gameOver();
    }
   
    // Draw pipes at new x location.
    uPipe.x.baseVal.valueAsString = uPipeX;
    lPipe.x.baseVal.valueAsString = uPipeX;
  }
 
  // Move the clouds.
  function moveClouds() {
   
    // Move cloud 1.
    cloud1X = cloud1X - 0.5;
   
    // Does it go off the edge?
    if (cloud1X < -100) {
      cloud1X = boardWidth;
    }
   
    // Move cloud 2.
    cloud2X = cloud2X - 1;
   
    // Does it go off the edge?
    if (cloud2X < -100) {
      cloud2X = boardWidth;
    }

    cloud1.cx.baseVal.valueAsString =
      cloud1X + "px";
    cloud2.cx.baseVal.valueAsString =
      cloud2X + "px";     
  }

  // Fox is waiting to fall.
  function foxWait() {
 
    // Make the fox fall after one second.
    foxFallTimer = setTimeout(foxFall, 1000);
  }
 
  // Fox is falling.
  function foxFall() {
 
    // Make the fox fall one pixel.
    foxY = foxY + 1;
   
    // Does he hit the floor?
    if (foxY > (boardHeight - 50)){
     
      // Calculate new position.
      foxY = (boardHeight / 2) - 50; 

      // Game is over!
      gameOver();
      console.log("Fox hit the floor!");
    }

    // Draw the fox.
    myFox.y.baseVal.valueAsString =
      foxY + "px";
     
    // Report progress.
    console.log("Fox is falling.");
  }
 
  // Fox is jumping.
  function foxJump() {
 
    // Fox jumps up!
    foxY = foxY - 20;
   
    // Does he hit the roof?
    if (foxY < 20) {
     
      // Calculate new position.
      foxY = (boardHeight /2) - 50; 
     
      // Game is over!
      gameOver();
      console.log("Fox hit the roof!");
    }
   
    // Otherwise, draw the fox.
    myFox.y.baseVal.valueAsString =
      foxY + "px";
   
    // Report progress.
    console.log("Fox is jumping.");  
  }

  </script>
</head>
<body id="pageBody">
</body>
</html>


Most of this code is explained in the previous two posts, but here is the new stuff:

Globals

 I added a few globals to keep track of the pipe data. There are two pipes. They move from right to left (but at a different speed from the clouds).

  // Pipe globals
  var uPipeLength = 260;
  var lPipeLength = 260;
  var pipeWidth = 50;
  var uPipeX = boardWidth - pipeWidth;


The upper and lower pipes are given a length. This length will change to simulate new pipes that are of different lengths. This change happens off-screen, every time the pipes are hidden. When they come back on the right, their lengths will be changed, but the gap will be the same so the Fox has a fighting chance to get through.

The width of the pipe is a constant: 50 pixels.

I needed a value that would reflect the upper pipe's x value at the bottom point of the pipe. This will be used to calculate whether the bird hits either pipe. Essentially I needed a base point to measure from, and this seemed handy.

Adding the Pipes

I added two pipes, using SVG rectangles. Notice that I had to add them after the clouds and the Fox. The pipes are in the foreground. As I wrote about the last time, the order you define SVG object becomes the z-order. The last one defined is the object in front of the rest. We want the clouds behind the pipes and the Fox might as well be there too.

Here's the code for the two pipes:

    // Pipes
    // Upper Pipe
    // Define the pipe.
    uPipe = document.
      createElementNS(svgURL,"rect");
    // Set the values.
    uPipe.x.baseVal.valueAsString =
      uPipeX + "px";
    uPipe.y.baseVal.valueAsString = "0px";
    uPipe.width.baseVal.valueAsString =
      pipeWidth + "px";
    uPipe.height.baseVal.valueAsString =
      uPipeLength + "px";
    uPipe.style.setProperty("fill","red","");
    // Attach it to the board.
    gameBoard.appendChild(uPipe);

    // Lower Pipe
    // Define the pipe.
    lPipe = document.
      createElementNS(svgURL,"rect");
    // Set the values.
    lPipe.x.baseVal.valueAsString =
      uPipeX + "px";
    lPipe.y.baseVal.valueAsString =
      uPipeLength + 100 + "px";
    lPipe.width.baseVal.valueAsString =
      pipeWidth + "px";
    lPipe.height.baseVal.valueAsString =
      uPipeLength + "px";
    lPipe.style.setProperty("fill","red","");
    // Attach it to the board.
    gameBoard.appendChild(lPipe);

Moving the Pipes

This is in two parts. The first part is a call to a pipe moving function from the game loop. This call takes place every time through the loop. This line does it:

    movePipes();

The actual function both moves the pipes and checks for collisions. First we move the pipes and check to see if the pipes move off the left edge. If they do, we start them over again but change the lengths by a random amount so that the pipes give a different challenge every time the Fox encounters them. In addition, a check is made to make sure that the pipes aren't too short or too tall. When making a game, you have to think of every possibility and deal with it.

Here's the code for moving the pipes, checking for off-screen motion, resizing the pipes, and then checking to make sure that the resizing went okay.

  // Move the pipes.
  function movePipes() {

    // Move the pipe X value. 
    uPipeX = uPipeX - 2;
   
    // Check if pipes off left edge.
    if (uPipeX < -50) {
   
      // If too far, send to right side.
      uPipeX = boardWidth;
     
      // Resize pipes.
      // Generate random change.
      change =
        Math.
          floor((Math.random()*60)+1) - 30;
       
     
      // Calculate new pipe lengths.
      uPipeLength = uPipeLength + change;
      lPipeLength = lPipeLength - change;
   
      // If upper pipe is too short, back up.
      if (uPipeLength < 100) {
        uPipeLength = uPipeLength - change;
        lPipeLength = lPipeLength + change;
        console.log("UPPER PIPE TOO SHORT");
      }   
     
      // If lower pipe is too short, back up.   
      if ((uPipeLength + 100) >
          (boardHeight)) {
        uPipeLength = uPipeLength - change;
        lPipeLength = lPipeLength + change;
        console.log("LOWER PIPE TOO SHORT");
      }
           
      // Draw upper pipe if new size.
      uPipe.height.baseVal.valueAsString =
        uPipeLength  + "px";
     
      // Draw lower pipe if new size.     
      lPipe.y.baseVal.valueAsString =
        uPipeLength + 150 + "px";
      lPipe.height.baseVal.valueAsString =
        lPipeLength + "px";
      
      // Display current values.      
      console.log(uPipeLength,
        lPipeLength, change);
    }


Essentially here is how it goes:

  1. The pipe moves 2 pixels to the left.
  2. If the pipes go off the left edge, the pipes are resized.
  3. Is the upper pipe too short? If so, ignore the change.
  4. Is the lower pipe too short? If so, ignore the change.
  5. If all is well, draw the upper pipe.
  6. If all is well, draw the lower pipe, basing it on the upper pipe.

Note: the pipes are drawn in this part only if they are being resized. Otherwise the pipes are redrawn every time the game loop runs and that code will be at the end of the function.

Testing for Collision

Still in the same function, we need to now test to see if the pipe hit the Fox (which is the same as the Fox hitting the pipe, but because the Fox is flying, it is easier to test from the pipe's point of view). Here are the tests, base on simple calculation.

    // Test for upper pipe collision.
    if ((foxX > uPipeX) &&
        (foxX < (uPipeX + 50)) &&
        (foxY < uPipeLength)
        ){
     
      console.log("Hit upper pipe. Ouch!");
     
      // Game is over.
      gameOver();
    }

    // Test for lower pipe collision.
    if ((foxX > uPipeX) &&
        (foxX < (uPipeX + 50)) &&
        (foxY > (boardHeight - lPipeLength))
        ){
     
      console.log("Hit lower pipe. Ouch!");

      // Game is over.
      gameOver();
    }


This simply check for the x and y values of the Fox and each pipe. It is simpler to test each pipe separately. If they collide, the game is over and a separate function is called. Note that I've used the console.log here a lot. It was invaluable in determining whether things collide and where they are. Don't leave home without it!

Draw the Pipes Every Time

The pipes need to be drawn so they move every time through the game loop. But this is separate from the drawing that takes place if the pipes move off the left edge. Here's the simple drawing:

    uPipe.x.baseVal.valueAsString = uPipeX;
    lPipe.x.baseVal.valueAsString = uPipeX;
  }


Ending the Game
I had fun deciding how to do this. When there is a collision with the pipes or the Fox hits the top or bottom of the screen, the gameOver function is called. This just does two things:

  1. Make the Fox big. Really big. He blows up!
  2. Then a quick timer call takes place so the Fox won't stay blown up too long.

Here's the code for that:

// Game over!
  function gameOver() {

    // Make the fox big!
    myFox.setAttribute("height",200);
    myFox.setAttribute("width",200);
       
    //Wait wait a short time!
    foxBlowUpTimer =
      setTimeout(foxBlowUp, 700);
  }


And the final code sets the Fox back to normal size and puts him back to the starting position. The game continues. Here's that code:

  // Fox blow up!
  function foxBlowUp() { 
   
    // Reset fox values.
    foxX = boardWidth / 2 - 100;
    foxY = boardHeight / 2;
    foxW = 50;
    foxH = 50;
   
    // Reset fox position.
    myFox.setAttribute("height",foxH);
    myFox.setAttribute("width",foxW);
    myFox.setAttribute("x",foxX);
    myFox.setAttribute("y",foxY); 
  }


So that's it for the Flying Fox! I hope you've enjoyed this walk through a simple game and if you get a chance, check out all the other games that were inspired by Flappy Bird at http://itch.io/jam/flappyjam.

Wednesday, January 15, 2014

PaddleFox (Game Programming)

One of the simplest games to create is a variation of Pong that is based on the real-life sport of Racquetball. You can play by yourself and don't need a partner. Just slam the ball against a court made of three walls and bat it back and forth. I wanted to be more unique to choose a name for this game, so after some thought and research, I picked PaddleFox.

Why pick a name? Part of this series of posts is to create real games, and right now seems like a good time to pull together a lot of the technologies I've been exploring to make a "real" game and see how to put it in the store. This isn't going to be the greatest game in the marketplace and I certainly wouldn't charge money for it. I might expand on it later. There's an absolutely fabulous game I do love that is on the Android called Juggle by those wizards of Dundee, Scotland, known as Denki. I recommend it and I'd like to try something like it as I build up more skill. (Essentially it is similar to the game I'm building today, but introduces more balls and different sized ones.)

I'll see about submitting this game to the Firefox OS Marketplace and keep you posted. As I improve it over time, I'll let you know about that too. But I also have lots more game techniques I want to explore that aren't about bouncing balls, and can't wait to get going on a space shooter, a platform game, and a lite RPG.

But on to today's game. There's a ball, there's a paddle, and that's it. Hit the ball and it bounces off. Miss the ball and the game is over (but you don't need a quarter to play over again). This game uses SVG, but you can do the same game by using the corresponding bouncing ball and collision techniques I wrote about for CSS and Canvas.

Here is ... PaddleFox!


Tested and run on a Geeksphone Peak, my new pride and joy. I feel guilty not playing with my ZTE Open. I know it is sitting in the corner missing me, but I'm hoping to get back to it when I can safely upgrade to Firefox OS 1.3 so I can start using the App Manager.

Basically this game combines the bouncing ball technology of creating SVG with JavaScript and the SVG DOM with the Collision Detection built into SVG with getBBox with a touch of Touch! SVG is still a largely undiscovered country for game programming (but see a good one in esviji).

So, here's the code:

<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="utf-8">
    <title>
      PaddleFox
    </title>
   
    <style>
   
    #myHead
    {
      position:absolute;
      top: 10px;
      left: 10px;
    }
   
    #myPara
   
    {
      position:absolute;
      top: 60px;
      left: 10px;
    }
   
    </style>
    
    <script>

      // --- Global variables ---

      // Width and height of board in pixels
      var boardWidth = 320;
      var boardHeight = 460;
      var boardWidthPixels = boardWidth + "px";
      var boardHeightPixels = boardHeight + "px";
     
      var padMove = 20;  // Amount the paddle moves.
      var score = 0;     // Initial score.

      // URL for W3C definition of SVG elements
      var svgURL = "http://www.w3.org/2000/svg";
          
      // Variables for ball initial position.
      var ballHor = boardWidth / 2 - 20;
      var ballVer = 50;
      var ballHorPixels = ballHor + "px";
      var ballVerPixels = ballVer + "px";
     
      // Amount of vertical and horizontal change.
      // Think of this is a vector. Speed + direction.
      var changeHor = 5;
      var changeVer = 5;
     
        // Covers all versions of requestAnimationFrame.
        window.requestAnimationFrame =
        window.requestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.msRequestAnimationFrame; 
     
      // Page load event listener.
      window.addEventListener("load",
        runFirst, false);
       
      // Mousedown event listener.
      window.addEventListener(
        "mousedown", whereMouse, false);
                       
      // Runs when page loads.    
      function runFirst() { 

        // Define the game board as an SVG element.
        gameBoard =
          document.createElementNS(svgURL, "svg");
         
        // Width and height of the SVG board element.
        gameBoard.width.baseVal.valueAsString =
          boardWidthPixels;
        gameBoard.height.baseVal.valueAsString =
          boardHeightPixels;
         
        // You must append the board to the body.
        document.getElementById("pageBody").
          appendChild(gameBoard);
       
        // Define the ball as an SVG element.
        paddleBall =
          document.createElementNS(svgURL, "circle");
         
        // Width,  height, radius, and color of ball.
        paddleBall.cx.baseVal.valueAsString =
          ballHorPixels;
        paddleBall.cy.baseVal.valueAsString =
          ballVerPixels;
        paddleBall.r.baseVal.valueAsString =
          "10px";
        paddleBall.style.
          setProperty("fill","fuchsia","");
         
        // Attach the ball to the game board.
        gameBoard.appendChild(paddleBall);
       
        // Define the paddle.
        myPaddle =
          document.createElementNS(svgURL, "rect");
         
        // X, Y, width, height, and color of paddle.
        myPaddle.x.baseVal.valueAsString = "90px";
        myPaddle.y.baseVal.valueAsString = "400px";
        myPaddle.width.baseVal.valueAsString = "130px";
        myPaddle.height.baseVal.valueAsString = "20px";
        myPaddle.style.setProperty("fill", "darkcyan","");
       
        // Attach the paddle to the board.
        gameBoard.appendChild(myPaddle);
       
        // Color the page elements.
        pageBody.style.backgroundColor = "black";
        myHead.style.color = "red";
        myPara.style.color = "white";      
        
            // Start the game loop.
            gameLoop();
      }
       
      // Game loop. Runs as fast as it can.
    function gameLoop(){
     
            // Endless loop with requestAnimationFrame.
            requestAnimationFrame(gameLoop);
      
        // Move the ball.
        ballMove();        
    }
     
    // Move the ball.
    function ballMove() {
     
      // Changes are calculated but do not
      // take effect until next time through loop.
         
      // Calculate new vertical component.
      ballVer = ballVer + changeVer;

      // If top is hit, bounce off.
      if (ballVer + changeVer < 10)
        changeVer = -changeVer;
       
      // If bottom is hit, the game is over.
      if (ballVer + changeVer + 10 > boardHeight) {
        changeVer = -changeVer;
        // You missed!
        console.log("Hit bottom");
       
        // Game over - actions
        // Set the score to zero.
        score = 0;
        myPara.textContent = "Score = " + score;
       
        // Alert the player.
        alert("You missed the ball!  Start again");
       
        // Start over again.
        // Set ball position.
        ballHor = boardWidth / 2 - 20;
        ballVer = 50;
       
        // Set ball vector.
        changeHor = 5;
        changeVer = 5;
       
        // Set paddle position.
        myPaddle.setAttribute("x", "90px");
       
        // Exit here and don't do any more now.
        return;
      }

      // Calculate new horizontal component.
      ballHor = ballHor + changeHor;

      // If left edge hit, bounce off.
      if (ballHor + changeHor < 10)
        changeHor = -changeHor;
       
      // If right edge is hit, bounce off.
      if (ballHor + changeHor + 10 > boardWidth)
        changeHor = -changeHor;
       
      // Does a collision take place with paddle?    
      // Get bounding box of ball.
      bbBall = paddleBall.getBBox();      
      // Get bounding box of paddle.
      bbPaddle = myPaddle.getBBox();        
     
      // Compare bounding boxes.   
      // Is the ball intersecting the paddle?
      // Four conditions must be met.
      if  ((bbBall.x > bbPaddle.x) &&
        (bbBall.x < (bbPaddle.x + 140)) &&
        (bbBall.y > bbPaddle.y) &&
        (bbBall.y < bbPaddle.y + 10)){
          console.log("HIT"); 
         
          // If hitting the paddle, reverse vertical.
          changeVer = -changeVer;
         
          // Make a small spin to create variation.
          spin = Math.floor((Math.random()*5)) - 2;
          // Add spin to horizontal vector.
          changeHor = changeHor + spin; 
         
          // Bounce ball up to untangle from paddle.
          ballVer = ballVer - 20;
         
          // Increase score.
          score = score + 1;
          myPara.textContent = "Score = " + score;
      }         

      // Draw the ball with new coordinates.
      paddleBall.cx.baseVal.valueAsString =
        ballHor + "px";
      paddleBall.cy.baseVal.valueAsString =
        ballVer + "px"; 
    }

    // Handle mousedown event.
    // Tap once each time to move paddle.
    function whereMouse(evt) {
   
      // Get mouse coordinates.
      mouseX = evt.clientX;
      mouseY = evt.clientY;
     
      // Current paddle position.
      oldX = myPaddle.x.baseVal.value;   
     
      // Calculate which side click is on.
      // If tap is on right of center.
      if (mouseX > 160) {
        newX = oldX + padMove;
        // If new x would go off screen.
        if (newX > 210)
          newX = oldX;
      }
      // If tap is on left side of center.
      else {
        newX = oldX - padMove;
        // If new x would go off screen.
        if (newX < 10)
          newX = oldX;
      }
     
      // Move paddle.
      myPaddle.setAttribute("x", newX);     
    }   

  </script>
</head>
   
<body id="pageBody">
<h1 id="myHead">PaddleFox</h1>
  <p id="myPara">Score = 0</p>
</body>

</html>


268 lines of code. That's not too many! Here's how it works.

Shell

I use a standard HTML5 shell. The minimum you need to tell the browser that you're talkin' HTML5 and UTF-8. Here's the layout I use, but all this is up to you. The HTML5 part is not. Even though it is not directly about SVG, my post on CSS Shell is a good starter.

In general, here's how my shell is laid out. You can also call this a template, but I like shells, and Susie sells them down by the seashore!
  1. HTML5 minimum code
  2. CSS
  3. Globals
  4. Event listeners
  5. Page Load event handler
  6. Game Loop
  7. Function blocks
  8. Other event handlers
  9. HTML UI code (if any)
The HTML5 and Globals should be easy enough to figure out. I use globals when I want something to be accessible in any event handler.

CSS

Sometime I use CSS and sometimes I don't. When you are using SVG (which has cousins in CSS on its mother's side), you can avoid CSS, but it's always there in the background, cooking up delicious meals. Here's today's CSS:

    <style>
   
    #myHead
    {
      position:absolute;
      top: 10px;
      left: 10px;
    }
   
    #myPara
   
    {
      position:absolute;
      top: 60px;
      left: 10px;
    }
   
    </style>


I use the CSS here to position the heading and paragraph. If you don't absolutely position these text elements, the SVG pane will be drawn after these, but this will make it go off the bottom of the screen! When doing games, you need to make sure that everything is absolutely positioned.  Just to be different, I colored these text elements in code a little later:

        pageBody.style.backgroundColor = "black";
        myHead.style.color = "red";
        myPara.style.color = "white";      


I did this in the page load. I'm actually more comforable with JavaScript than CSS, but I'm learning CSS fast.

Event Listeners

Games are usually all about events, and you need to listen for them. Here are the listeners:

       window.addEventListener("load",
        runFirst, false);
      
      // Mousedown event listener.
      window.addEventListener(
        "mousedown", whereMouse, false);

The first is listening for the page load event and the second is looking for a mouse down event. On Firefox OS, the mouse down event is what I use for simple touch. Your finger is a mouse! You want just the mouse down, not the click (which is mouse down plus mouse up). Fast and Firefoxy!

Page Load Event Handler

I throw in everything here that should happen before you start running the game. This includes:
  1. SVG definitions for the SVG pane, the ball (a circle), and the paddle (a rect). This is explained in the SVG post about SVG with JavaScript and the SVG DOM with the Collision Detection built into SVG with getBBox
     
  2. Whatever else I want to set up (coloring the text, in this case).
     
  3. A simple call to the game loop. Game loops govern the action cycle in an arcade game and they've been around forever. 
Game Loop

This is a simple game loop. It just loops around and around, governed by requestAnimationFrame. All it does is draw the ball (ballMove).

Function Block

The only function I have is (that isn't an event handler or the game loop) is the one that moves the ball. This function, ballMove, does most of the heavy lifting. It does the following things:
  1. Calculate the new ball position based on the changes you made the last time through the loop. This is a little goofy, but works. The loop is always processing what happened last time through. For example, if you hit a ball, you bounce, but the actual bounce doesn't happen until you come back again. Fortunately the game loop is bringing you back several times a second, depending on the negotiation between requestAnimationFrame and your browser. First the vertical component of your ball's vector (direction of travel and speed) is calculated, later the horizontal component follows.

    ballVer = ballVer + changeVer;
  2. A check is made to see if you hit the top of the screen. If you did, you bounce!

          if (ballVer + changeVer < 10)
            changeVer = -changeVer;

     
  3. Next a similar check is made to see if you hit the bottom of the screen. Oh, oh! You don't want to do that. If you did, the following actions take place: your score is reset to zero, the player is alerted, the ball is reset to its original position, so is the paddle, and then you return to the game loop without drawing the ball in this function block.

          if (ballVer + changeVer + 10 > boardHeight) {
            changeVer = -changeVer;

            console.log("Hit bottom");
           
            score = 0;
            myPara.textContent = "Score = " + score;
           
            alert("You missed the ball!  Start again");
           
            ballHor = boardWidth / 2 - 20;
            ballVer = 50;
           
            changeHor = 5;
            changeVer = 5;
           
            myPaddle.setAttribute("x", "90px");
           
            return;
  4. Next you do the same for horizontal positions. Bounce off the left and right walls.

          ballHor = ballHor + changeHor;

          if (ballHor + changeHor < 10)
            changeHor = -changeHor;
           
          if (ballHor + changeHor + 10 > boardWidth)
            changeHor = -changeHor;
  5. Now its time to see if the ball hits the paddle or not. This is a smart ball and does all the thinking. The paddle just moves (but elsewhere). This is a simple process. Get the bounding boxes of the ball and paddle. Even though the ball is round, a box can be used to determine collisions. Then once you get the bounding boxes, use their data to check a rectangle that sits on top of the box. If the ball enters that rectangle, a collision takes place. That collision will then make the ball reverse its vertical vector component, add a small random change to the horizontal vector component (called spin), pop the ball up a bit so it doesn't stay tangled in the paddle, and adds +1 to your score.

          bbBall = paddleBall.getBBox();      
          // Get bounding box of paddle.
          bbPaddle = myPaddle.getBBox();        
         
          // Compare bounding boxes.   
          // Is the ball intersecting the paddle?
          // Four conditions must be met.
          if  ((bbBall.x > bbPaddle.x) &&
            (bbBall.x < (bbPaddle.x + 140)) &&
            (bbBall.y > bbPaddle.y) &&
            (bbBall.y < bbPaddle.y + 10)){
              console.log("HIT"); 
             
              // If hitting the paddle, reverse vertical.
              changeVer = -changeVer;
             
              // Make a small spin to create variation.
              spin = Math.floor((Math.random()*5)) - 2;
              // Add spin to horizontal vector.
              changeHor = changeHor + spin; 
             
              // Bounce ball up to untangle from paddle.
              ballVer = ballVer - 20;
             
              // Increase score.
              score = score + 1;
              myPara.textContent = "Score = " + score;


    The only tricky part is the actual collision detection.

                if ((bbBall.x > bbPaddle.x) &&
            (bbBall.x < (bbPaddle.x + 140)) &&
            (bbBall.y > bbPaddle.y) &&
            (bbBall.y < bbPaddle.y + 10)){


    This checks four conditions to make sure that the ball's bounding box is inside the paddle's bounding box and uses the && to say that all four must happen to trigger the collision.
  6. Finally if you didn't hit the bottom of the screen, the ball is drawn.

          paddleBall.cx.baseVal.valueAsString =
            ballHor + "px";
          paddleBall.cy.baseVal.valueAsString =
            ballVer + "px"; 
This is the only trick part, but it seems to work well. And unlike Canvas, you don't have to erase anything, you just move stuff. Well, CSS game programming can just throw stuff around, but only SVG can do really cool shape modification and filters (more about that later, lots more).

Mousedown Event Handler

 This is triggered any time you touch the screen. In order to make this a game, you have to do something. My thought is to make it simple, so you touch on the left half of the screen to move the paddle left, and touch on the right half to make it go right. Each time you tap the screen, the paddle will jump a specific number of pixels and you'll have to tap it again to move it again. This provides a small amount of challenge.

Interestingly enough, when testing this on desktop Firefox, the clicking to move is clumsy and slow. But on a phone, it's fast and very intuitive. No keyboard, no mouse, just your finger. Tap anywhere on the left to move left, etc.

The mousedown handler does the following:
  1. Get the coordinates of where the tap took place.
  2. Get the current paddle X position (we're only moving left and right).
  3. Calculate where the click took place. Center is at 160 pixels right of the left edge. If the tap is on the right, add padMove to the paddle X value. But check to make sure the paddle doesn't move off the right edge.
  4. Do the same for the left of center tap. Again, make sure you don't move the paddle off the left edge.
  5. Move the paddle!
Here's the code for the mousedown handler:

      mouseX = evt.clientX;
      mouseY = evt.clientY;
     
      oldX = myPaddle.x.baseVal.value;   

      if (mouseX > 160) {
        newX = oldX + padMove;

        if (newX > 210)
          newX = oldX;
      }

      else {
        newX = oldX - padMove;

        if (newX < 10)
          newX = oldX;
      }
     
      myPaddle.setAttribute("x", newX);     


Wrapping It Up

You just start the game and move the paddle, stopping the ball from its mad passion to slam into the bottom of the screen. Rack up your score and keep playing. Maybe even have fun. But the real point is to see how to pull together the elements of creation, motion, and collision to make a simple arcade game.

Next I'm going to try to put this in the Marketplace and relate my adventures. After that, more game programming, more game reviews, and so on until I run out of ideas (not soon). Any ideas you have or any simple game types you'd like me to create would be appreciated. Well, maybe not Call of Duty.

Written while listening to the soothing strains of the Panzer Dragoon Saga Memorial Edition soundtrack. One of my all-time favorite games, totally only on the Sega Saturn. I don't have it any longer but I still remember finishing it and really liking the world it took place in, one of archaeology and dragons.