Thursday, November 7, 2013

Bouncing SVG - Part 3 (Game Programming)

In the first two parts of this series I wrote about how to use SVG to animate a bouncing ball. The first one was about putting the SVG in the body with tags and the second was about creating the SVG from scratch with JavaScript. In this, the third part, I want to write about a third way of doing SVG. This new way is not hardly documented anywhere and will take some work, but the payoff might be worth it, especially if performance is important to your game.



I've already talked about the DOM (Document Object Model) and you probably have heard about it. The DOM is actually the API of web browsers and you can use JavaScript to make the DOM do cool tricks. For example, if you have a sequence of paragraphs, you can change their order by manipulating the DOM. Every bit of a web page is part of the DOM. You can "walk" the DOM to see how your web page is constructed and in general mess around a lot.

But because SVG is independent and XML based, when you add it to the HTML DOM, something magical happens. The regular HTML DOM can get at the SVG and that was how the manipulation of SVG was done in Part 2 of this series:
  1. The SVG was added to the page with createElementNS.
  2. JavaScript was able to manipulate it through the HTML DOM with setAttribute.
But there was something hidden going on. There was ... another DOM!

This DOM is called the SVG DOM.

NOTE: actually, the DOM in the first two examples isn't really the HTML DOM, it is the XML DOM which is a sibling to the HTML DOM. It's easier to call it the HTML DOM. Everything about DOM is shadowy and hidden in Mirkwood somewhere. But right now let's just talk about the SVG DOM.

When you add SVG to your HTML5 web page, there are two ways to get at the SVG DOM. One way (in Parts 1 and 2) is going through the HTML DOM. The HTML DOM does some of the work for you, especially in converting some of the messy parts of the original SVG spec. But you pay a price for this conversion. When your calls go to and from the SVG DOM by way of the HTML DOM, things can slow down. For bouncing a ball, you won't notice any difference. But if you are doing something complicated, the difference can make ... a difference.

But you can sneak around the HTML DOM and call the SVG DOM. The way of doing it is different, but the way you do it is more like programming and less like markup. So here's today's program, similar in many ways to earlier ball bouncing, but using JavaScript in a way that is unique to SVG.

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

      // --- Global variables ---

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

      // URL for W3C definition of SVG elements
      var svgURL = "http://www.w3.org/2000/svg";
          
      // Variables for ball initial position.
      var ballHor = boardWidth / 2;
      var ballVer = boardHeight /2;
      var ballHorPixels = ballHor + "px";
      var ballVerPixels = ballVer + "px";
      var changeHor = 10;
      var changeVer = 10;
     
      // Covers all bases for requestAnimationFrame.
      var requestAnimationFrame =
      window.requestAnimationFrame ||
      window.mozRequestAnimationFrame ||
      window.webkitRequestAnimationFrame ||
      window.msRequestAnimationFrame; 
     
      // --- Event listeners ---
     
      // Page load event listener (hear it loading?).
      window.addEventListener("load",
        runFirst, 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);
        
        // Start the game loop.
        gameLoop();
        }
       
      // Game loop.
      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, change direction.
        if (ballVer + changeVer < 0)
          changeVer = -changeVer;
       
        // If bottom is hit, reverse direction.
        if (ballVer + changeVer > boardHeight)
          changeVer = -changeVer;

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

        // If left edge hit, reverse direction.
        if (ballHor + changeHor < 0)
          changeHor = -changeHor;
       
        // If right edge is hit, do something.
        if (ballHor + changeHor > boardWidth)
          changeHor = -changeHor;
       
        // Draw the ball with new coordinates.
        paddleBall.cx.baseVal.valueAsString =
          ballHor + "px";
        paddleBall.cy.baseVal.valueAsString =
          ballVer + "px";   
        }    

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

</html>


This code starts out very much like Part 2. We're creating SVG objects in the browser with nothing in the body. But after createElementNS, the code looks weird. Here's how the SVG board size is set:

        gameBoard.width.baseVal.valueAsString =
          boardWidthPixels;
        gameBoard.height.baseVal.valueAsString =
          boardHeightPixels;


Actually, there were some new global variables. They were additions to the earlier ones, but instead of numbers, the height and width have to be set in pixels, not just a number. Behind the scenes, SVG uses a quirky numbering system which I'll get in to in another post later.

Anyway, you might think you could type:

                  gameBoard.width = 320;

But you can't. You have to add a few quirky things, but once you know those quirks, it's not that hard. Here's how to understand this code:

       gameBoard.width.baseVal.valueAsString =
          boardWidthPixels;


The first quirky part is that you have to throw in a new keyword, baseVal. This tells JavaScript to tell SVG that you want to use a normal SVG value for a length (length here means a measure of distance, and applied to height or width) the alternative is animVal, for animation, which you don't want. Why, you ask? Well, if you look up the SVG DOM page for rect (which is what we are defining) and can figure out that in normal SVG, you call it rect, but in SVG DOM, you call it SVGRectElement, you'll find it here: https://developer.mozilla.org/en-US/docs/Web/API/SVGRectElement.

Go look at that page. Mozilla has done a good job of putting the pieces there in the right order. But what you need to see at that page is that width and height are defined as a SVGAnimatedLength type. SVG animation was a cool idea back in the year 2000 (Conan O'Brien reference), but it never quite got itself together. You can animate SVG with JavaScript (don't ask about SMIL). Because of this, you have to make a slight twist and define the width and height with normal units, and use baseVal. And when you do that, you further need to feed the width and height values as strings, not as numbers. So we need to set our height and width as pixels.  You can get some clues here in the SVGLength topic on MDN https://developer.mozilla.org/en-US/docs/Web/API/SVGLength.

If you use this technique, you can do all kinds of programming tricks with SVG and you can bypass the DOM. Mozilla has a great page that lists all the various DOM categories and even what's in and what's out. It includes the three big DOM sets (DOM, HTML DOM, and SVG DOM). In the beginning was the DOM and then HTML DOM came forth and said let's make it easier, and finally SVG DOM came in and said let's party! https://developer.mozilla.org/en-US/docs/DOM/DOM_Reference. Talking about these is a pain, however, and I wish they'd give them easier names, like DOM Red, DOM Blue, DOM Green or something. Well, I guess they think they made it easy. The stock DOM is just DOM and it calls an element ... Element. The HTML DOM calls the same element HTMLElement (so you know it's the same if you whack off the HTML part), and the equivalent for SVG is called ... wait for it ... SVGElement. These prefixes actually have a reason, which is that they are used to keep the things with the same name apart. These prefixes are so you know what namespace you are in at any moment (if you're that poor overworked guy, the browser developer).

So all this info is in the Mozilla Developer Network, but you have to pick it apart. The SVG spec also has the same information, and the reason it is hard to follow is that specs are written for browser developers, not game programmers like you and me. But the really cool part is that all of these whacky things in the SVG DOM namespace are implemented in every browser that supports SVG. Mind you, not every blinking object and property actually are implemented because about 15% of SVG is just too obscure or not needed. Things involving Fonts, Text, and Animation are funky and/or just don't work and aren't a high priority for the browser developers.

But all these techniques work on every modern browser. Firefox! Chrome! Even IE! And even their friends on mobile devices and their cousins SeaMonkey! and Opera! And even that weird uncle in the background who made a pile of money in some way people don't want to talk about, Safari!

SVG is everywhere.

Oops, forgot one more cool quirk that SVG allows you to use in the SVG DOM. SVG and CSS are BFF! A lot of the values are right in line with CSS. So after you create the ball using this new SVG DOM technique, you can define the color by a CSS style, like this:

        paddleBall.style.setProperty("fill","fuchsia","");

Because this 3rd way of messing with SVG is working with the SVG DOM, everything is an object and objects have properties. Repeat this until it makes sense:

           HTML DOM = elements and attributes
           SVG DOM =  objects and properties

They are really the same concept, but the first is a markup thing and the second is a programming thing.

So anything that can have a property set can be set using "style". And one of the fun things about SVG is that it has a lot of style.

I'll be writing about this 3rd type of programming for SVG more in this blog but I want to get back to writing about game programming. I've written about three different game technologies you can use for drawing: CSS, Canvas, and SVG, and I'm hoping that you'll now be able to think about which way you want to go with programming. Here's my take on the three:

CSS - cool for a lot of games but needs more investigation on my part.
Canvas - almost everyone is using this now. It's good, but may not be the best in tight spaces.
SVG - lots of interesting possiblities, especially for tight spaces.

I'd like to do more with SVG for sure, but also see how CSS can fit in somewhere. So stay tunes. Next programming post, I want to get started on the next phase of game programming, which is touch! And a little bit more SVG for added flavor.

And, you don't need to pick just one. You can use any or all of these three technologies in the browser simultaneously. One of the best examples of this was a called Glow and showed who was downloading Firefox at any moment.


It showed a map of the world, drawn in SVG, and little points of light every place that someone was downloading Firefox, and the points of light were little dots created with Canvas. It was cool to watch and see how at night in the Americas, the downloads were in Europe, and the opposite was true for daytime in the Americas. Gone now, but you can read about it here.

No comments:

Post a Comment