Wednesday, November 6, 2013

Bouncing SVG - Part 2 (Game Programming)

In my first post on SVG (http://firefoxosgaming.blogspot.com/2013/11/bouncing-svg-part-1-game-programming.html), I covered the simplest way to use SVG in a game. Create your images using SVG tags in the body of your HTML5 document and then manipulate them using setAttribute to make things move.

That's not a bad way to work, but I have found that creating objects through JavaScript seems to make for faster action on the page. But there's a logical reason. The SVG tags have to be parsed and then translated into the DOM. For something simple like bouncing a ball, you won't see any difference. In fact, for the different ways I've written about for bouncing a single ball, you aren't likely to notice any difference because the work done by the browser is easy. In fact, for a lot of action games, you'll have to slow down the programming to be fair to the user. Ball bouncing needs to be fast, but not too fast. Although when I watch Olympic ping pong, it seems too fast to me.



So if you create your own SVG objects on the fly in JavaScript, the creation is faster and it is just cooler. I personally find it exciting to think that I'm starting with a blank web page and I'm adding thing to it in JavaScript. Maybe its a matter of taste. But it's nice to have options.

So, here's option 2 for SVG. Make it all in JavaScript and the DOM. Actually, in option 1, you were using the DOM to get at the attributes of the ball you created with tags. The main reason I started with tags is that there are tons of books, websites, and blogs explaining how to create SVG images using tags. For example, want to make an ellipse? Check out Mozilla's docs on the SVG ellipse.

But there aren't very many docs or examples on how to do scripting with SVG, because most people began by thinking that SVG is a way to represent static vector images and stopped there. Well, don't stop, keep going!

Here is the code for a bouncing ball using the second technique of SVG, creating art on the fly and using the DOM to get at it.

<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="utf-8">
    <title>
      SVG in the XML DOM
    </title>
   
    <script>
   
      // --- Global variables ---

      var boardWidth = 320;
      var boardHeight = 460;
      var ballHor = boardWidth / 2;
      var ballVer = boardHeight /2;
      var changeHor = 10;
      var changeVer = 10;
     
      // Covers all bases for requestAnimationFrame.
      var requestAnimationFrame =
      window.requestAnimationFrame ||
      window.mozRequestAnimationFrame ||
      window.webkitRequestAnimationFrame ||
      window.msRequestAnimationFrame; 

     
      // URL for W3C definition of SVG elements
      var svgURL = "http://www.w3.org/2000/svg";
        
      // Page load event listener (hear it loading?)
      window.addEventListener("load",
        runFirst, false);
               
      // Run this first.
      function runFirst() { 
     
        // Define the game board as an SVG element.
        gameBoard =
          document.createElementNS(svgURL, "svg");
         
        // Width and height of the SVG board element
        gameBoard.setAttribute("width", boardWidth);
        gameBoard.setAttribute("height", boardHeight);        

        // 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, and radius of the ball
        paddleBall.setAttribute("cx", ballHor);
        paddleBall.setAttribute("cy", ballVer);
        paddleBall.setAttribute("r", 10);
        // Fuchsia = #FF00FF
        paddleBall.setAttribute( "fill", "#FF00FF");
         
        // Attach the ball to the game board.
        gameBoard.appendChild(paddleBall);
       
        // Start the game loop.
        gameLoop();
        }
       
      // Game loop - governed by requestAnimationFrame.
      function gameLoop(){
     
        // Restart the 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, change direction.
        if (ballVer + changeVer < -10)
          changeVer = -changeVer;
       
        // If bottom is hit, reverse direction.
        if (ballVer + changeVer > boardHeight - 10)
          changeVer = -changeVer;

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

        // If left edge hit, reverse direction.
        if (ballHor + changeHor < -10)
          changeHor = -changeHor;
       
        // If right edge is hit, do something.
        if (ballHor + changeHor > boardWidth - 10)
          changeHor = -changeHor;
       
        // Draw the ball with new coordinates.
        paddleBall.setAttribute("cx", ballHor);
        paddleBall.setAttribute("cy", ballVer);
        }
       
      </script>
    </head>
   
    <body id="pageBody">
    </body>

</html>


Most of this is the same as earlier ball bouncing examples. HTML5, load the code, start the loop, check for changes, repeat! But if you read this from top to bottom, you'll notice that the body of the page is now empty. There's nothing there! Well, almost nothing. The body now has an id of "pageBody" and you'll need this to anchor your SVG objects to.

Here's the basic process for using SVG in a web page using JavaScript:
  • Create the object using createElementNS.
  • Modify it by setting the attributes.
  • Add the object to the page or another object.
createElementNS

This is similar to createElement but it creates an element in a specified namespace. This is needed so you don't get your SVG objects mixed up with your other objects. SVG is definitely its own animal, but it plays well with others if you feed it properly.

createElementNS starts out by creating the SVG pane. I call it a pane because I don't want to call it a canvas, but the idea is similar to the canvas in Canvas. The SVG pane is where you place all your SVG objects. All the objects have to fit inside the boundaries of the object or they won't display (you might want to do this to have objects hide off-screen until they are ready for their big scene).

The first use of createElementNS is this:

   gameBoard = document.createElementNS(svgURL, "svg");

I had previously defined an URL (path to a web site) for svgURL.

   var svgURL = "http://www.w3.org/2000/svg";

SVG uses this URL to clue the browser in that we are talking about SVG as defined in the year 2000. Maybe someday they will change this, but right now this works for everyone for SVG. It says "I'm SVG and I'm special."

The second parameter of createElementNS is "svg". You can call it anything you want, but it is the name of the newly created element. However, you want to assign the value of createElementNS to a variable. I picked gameBoard and that's what you use to grab on to the SVG pane.

setAttribute

Once you've created your SVG object, it's just floating in space somewhere but it has no color, size, or odor. That's no fun. You need to assign it values. For the SVG pane, you want to give it a size and nothing else because for this example, the SVG pane is just a container that the ball will bounce around in. So this code will define the size:

        gameBoard.setAttribute("width", boardWidth);
        gameBoard.setAttribute("height", boardHeight);


 The board width and height were previously defined as 320x460, the size of my lovely little ZTE Open phone.

appendChild

The third step is to attach your SVG object to the web page (or another SVG object). You do this by using appendChild. Here's the code to attach the object:

        document.getElementById("pageBody").appendChild(gameBoard);

This can be tricky to read as it has three parts:
  1. The word "document" which is the page document.
  2. getElementbyID("pageBody") which is the way that you grab the page body.
  3. appendChild(gameBoard) which adds the SVG pane to the body.
The three pieces are joined by dots. Essentially you're adding a new element to the existing page element which in turn is part of the whole document.

At this moment, when you  add the SVG pane as a child of the page, the SVG pane is now part of the page. Because we didn't give it any color, it doesn't look different. But its there and it says, "I am SVG and I am 320x460 wide and high. Hear me roar!"

Not much fun, yet. Now to create the familiar Fuchsia ball, you just repeat the three step process above.
  1. Create the ball.
  2. Give it attributes.
  3. Attach it to the SVG container.
Here's the code that does this:

        // Define the ball as an SVG element.
        paddleBall =
          document.createElementNS(svgURL, "circle");
         
        // Width,  height, and radius of the ball
        paddleBall.setAttribute("cx", ballHor);
        paddleBall.setAttribute("cy", ballVer);
        paddleBall.setAttribute("r", 10);
        // Fuchsia = #FF00FF
        paddleBall.setAttribute( "fill", "#FF00FF");
         
        // Attach the ball to the game board.
        gameBoard.appendChild(paddleBall);


At the moment this code runs, the ball will appear on the screen. The rest of the code is similar to the other examples. And in the ballMove section, you move the ball with this code:

        paddleBall.setAttribute("cx", ballHor);
        paddleBall.setAttribute("cy", ballVer);


which was used in the first SVG example.

There you have it. Create your objects, give 'em attributes, attach them to something, and bang 'em around! What could be easier? This technique isn't well documented, but it works and you can get the attribute names and values from the Mozilla docs at https://developer.mozilla.org/en-US/docs/Web/SVG/Element. There are lots of elements, but luckily Moz has sorted them into 13 categories further down the page. Working with paths can be tricky, but you can draw a path with Inkscape, save it as SVG, and see what the path actually is in numbers and letters.

But there's one more way to do SVG that is even cooler. In a complicated game, this third way will make everything fly. Stay tuned!