Tuesday, November 12, 2013

Touch and SVG Spirals (Game Programming)

I've written a lot about bouncing balls as a way to make the comparison of CSS, Canvas, and SVG standardized. Graphics are about moving things, and that little ball bounces just fine on Firefox OS on my ZTE Open. In my next non-game post, I'll write up a comparison of the three graphics systems with pros and cons. But before I do that, I want to add one more bit about SVG ... and introduce the important issue of touch.

SVG isn't just about simple shapes like balls and boxes. Vector art (formerly known as line art before there were computers) can do incredible things and Adobe Illustrator has a strong place in the graphic design and illustration fields because you can rescale your art to fit a magazine, web page, or whatever. Scalable Vector Graphics is what SVG stands for. SVG does beautiful art by creating paths, which are just a series of lines connecting points.

A Little Bit about SVG

Here's an example of vector art, taken from the Open Clip Art site, a lively spiral created by Martin Bryce.


And here's a small part of the code that created it:

<path
       id="path2987"
       style="color:#000000"
       d="m-176.51-690.97c-59.728-0.23053-121.12 6.8953-145.25 18.438-2.2422 1.0724-4.4748 2.2103-6.6875 3.4375v18.344h-24.312v-1.4375c-13.611 11.42-25.872 25.146-35.688 39.406v22.031h-12.188c-4.0378 11.373-4.9902 23.682-5.625 35.719h16.438c4.2265-15.781 9.1152-32.046 20.281-46.031 19.959-24.998 48.083-49.881 76.781-65.312v-8.6562h19.219c24.836-8.4996 70.064-14.135 116.81-15.562-6.5418-0.21078-13.145-0.34939-19.781-0.375zm19.781 0.375c42.823 1.3798 82.471 6.8046 103.97 17.406v-1.8438h24.312v19.188c15.165 14.106 27.136 32.286 35.687
...
34.366 16.224 77.094 18.531 117.78 18.531 7.1247 0 13.769-0.0951 20.031-0.40625z"
       fill="#f33" />

The code goes on a few pages, and I'm 100% that Martin Bryce didn't sit down with graph paper and plot out those commands. What you're looking at is the definition of a series of points that make up one of the lines. Even though the final result in the image above has lots of wiggly lines, all those wiggles are just a set of points that define the line.

Martin used an art program such as Inkscape or Adobe Illustrator (or maybe something else, but those are the big two). The art program lets you draw with a mouse or use a drawing tablet to make your drawing. Why is this so cool?
  1. You can scale the drawing to be bigger or smaller, or stretch it.
     
  2. When you copy the code into your web page, it takes up less bytes than a bitmap.
  3. And you can get at all of this through JavaScript code and do great mangling!
So that's enough about SVG for a few minutes. It's cool. Check it out. Read about the SVG path command here. Complicated but Inkscape or Illustrator can do all the heavy lifting for you. For Inkscape, check out the book called The Book of Inkscape (very well written by one of the developers) and for Illustrator, track down any copy of any version of the Illustrator Wow! series.

So, go look at those and come back in a minute to ponder the mysterious world of Touch.

Touch

Bouncing a ball around is fine, but if you have a game, you want to interact with the elements on the page. There are a few ways to do that, but the most popular is touch. Touch the screen and make things happen.

For Firefox OS, you can fake touch by using mouse clicks. Mozilla can interpret the mouse code as a touch. No hover, however. Maybe some day, but you can't hover your finger over your phone screen and get any hover.

Code for Touch and Click

So here's the code for responding to a touch on the screen. The touch will move an SVG spiral (simpler than Martin's) to whatever point on the screen you touched, and the x and y coordinates of your touch will be displayed on the screen as well.

<!DOCTYPE HTML>
<html>
   
  <head>
  <title>svg spiral for touch</title>
  <meta charset="UTF-8">
 
  <style>
    #myDiv
    {   
      position:absolute;
      top: 0px;
      left: 0px;
    }
   
    #myPara
    {
      position:absolute;
      top: 50px;
      left: 50px;
    }
  </style>

    <script type="text/javascript">
       
      // Global variables
     
      var screenX = 320;        // Screen x value
      var screenY = 460;        // Screen y value
      var spiralX = 150;        // Spiral x value
      var spiralY = 150;        // Spiral y value

      // Add load event listener to run first function.
      window.addEventListener("load", runFirst, false);
                
      // This function runs when the document loads.
      function runFirst() {

        // Create SVG parent element.
        mySvg = document.createElementNS(
          "http://www.w3.org/2000/svg", "svg");
        mySvg.width.baseVal.valueAsString = "320px";
        mySvg.height.baseVal.valueAsString = "460px";

        // Create SVG screen.
        myScreen = document.createElementNS(
          "http://www.w3.org/2000/svg", "rect");
        myScreen.x.baseVal.value = 0;
        myScreen.y.baseVal.value = 0;
        myScreen.width.baseVal.value = screenX;
        myScreen.height.baseVal.value = screenY;
        myScreen.style.setProperty("stroke-width","1","");
        myScreen.style.setProperty("stroke","red","");
        myScreen.style.setProperty("fill","none","");
               
        // Append SVG parent to div of web page.
        document.getElementById(
        "myDiv").appendChild(mySvg);

        // Append screen to SVG parent element.
        mySvg.appendChild(myScreen);
      
        // Add mouse event listener.
        // Listener is attached to the SVG object.
        // Mousedown is better than click.
        mySvg.addEventListener(
          "mousedown", whereMouse, false);
               
        // Create spiral.
        mySpiral = document.createElementNS(
          "http://www.w3.org/2000/svg", "path");
               
        mySpiral.setAttribute("d",
          "M 8.3052015,10.567223 C "+
          "8.5290778,10.974677 "+
          "7.8664138,11.070027 "+
          "7.6279854,10.93932 6.9818595,10.585111 "+
          "7.1280823,9.668097 7.5610072,9.2127904 "+
          "8.3354083,8.3983538 9.6552076,8.6603337 "+
          "10.33685,9.4509311 11.337187,10.611165 "+
          "10.946898,12.380531 9.7935902,13.276087 "+
          "8.2564118,14.469722 6.0240157,13.946083 "+
          "4.9191211,12.427708 3.5287239,10.516989 "+
          "4.1879167,7.8159392 6.0726185,6.5039261 "+
          "8.3552449,4.9149023 11.527841,5.7107754 "+
          "13.045714,7.9625424 14.834475,10.616172 "+
          "13.901276,14.262005 11.281979,15.984951 "+
          "8.257897,17.974161 4.1377526,16.903234 "+
          "2.2102568,13.916097 0.02011351,10.521923 "+
          "1.2290373,5.9267386 4.5842299,3.7950617 "+
          "8.3482488,1.4036424 13.418989,2.7507484 "+
          "15.754578,6.4741538 c 2.592948,4.1336852 "+
          "1.107525,9.6803612 -2.98421,12.2196622") ;

        mySpiral.style.setProperty("stroke-width","1","");
        mySpiral.style.setProperty("stroke","black","");
        mySpiral.style.setProperty("fill","none","");
       
        mySpiral.setAttribute("transform",
          "translate(" + spiralX + "," + spiralY + ")");
    
        // Append spiral to SVG parent element.
        mySvg.appendChild(mySpiral);
       
        // Initial message.
        myPara.innerHTML = "Touch the screen.";
      }
   
      // Where did the mouse go down?
      function whereMouse(evt){
     
        // Get mouse coordinates.
        mouseX = evt.clientX;
        mouseY = evt.clientY;
       
        // Convert to string for display.
        myCoord = mouseX.toString() + " right, "
          + mouseY.toString() + " down";
        myPara.textContent = myCoord;
       
        // Calculate offset of click to spiral.
        spiralX = mouseX - 8;
        spiralY = mouseY - 8;
       
        // Change spiral position.
        mySpiral.setAttribute("transform",
          "translate(" + spiralX + "," + 

          spiralY + ")");              
      }
             
    </script>
  </head>
   
  <body id="myBody"> 
    <p id="myPara">Text goes here.</p>
    <div id="myDiv" />
  </body>

</html>


The structure follows the same as the last post on SVG. Create an SVG pane, stick an object on it, and then wait for events to happen. There's the usual load event handler, but there is a new one that waits for a mousedown event. Note that I'm using a mousedown event, not a click. For touch, you don't want to wait until the click ends. 

You need to register the mousedown event in the code after you create the SVG object, because the event handler is attached to the SVG pane. This is helpful so that the click goes right to the drawing board.

The mousedown event handler is pretty standard for a mouse event. You get the x and y properties and then you ... apply them to the spiral.

My Spiral

Not as pretty as Martin's, but simpler. Here's the code:

        // Create spiral.
        mySpiral = document.createElementNS(
          "http://www.w3.org/2000/svg", "path");
               
        mySpiral.setAttribute("d",
          "M 8.3052015,10.567223 C "+
          "8.5290778,10.974677 "+
          "7.8664138,11.070027 "+
          "7.6279854,10.93932 6.9818595,10.585111 "+
          "7.1280823,9.668097 7.5610072,9.2127904 "+
          "8.3354083,8.3983538 9.6552076,8.6603337 "+
          "10.33685,9.4509311 11.337187,10.611165 "+
          "10.946898,12.380531 9.7935902,13.276087 "+
          "8.2564118,14.469722 6.0240157,13.946083 "+
          "4.9191211,12.427708 3.5287239,10.516989 "+
          "4.1879167,7.8159392 6.0726185,6.5039261 "+
          "8.3552449,4.9149023 11.527841,5.7107754 "+
          "13.045714,7.9625424 14.834475,10.616172 "+
          "13.901276,14.262005 11.281979,15.984951 "+
          "8.257897,17.974161 4.1377526,16.903234 "+
          "2.2102568,13.916097 0.02011351,10.521923 "+
          "1.2290373,5.9267386 4.5842299,3.7950617 "+
          "8.3482488,1.4036424 13.418989,2.7507484 "+
          "15.754578,6.4741538 c 2.592948,4.1336852 "+
          "1.107525,9.6803612 -2.98421,12.2196622") ;

        mySpiral.style.setProperty("stroke-width","1","");
        mySpiral.style.setProperty("stroke","black","");
        mySpiral.style.setProperty("fill","none","");
       
        mySpiral.setAttribute("transform",
          "translate(" + spiralX + "," + spiralY + ")");
    
        // Append spiral to SVG parent element.
        mySvg.appendChild(mySpiral);
       

This creates an SVG object, and uses a path to define it. There's no fill because this is an open spiral. This spiral uses the SVG transform property to make the spiral translate to a new position. The transform property has a lot of cool options, and its main use is that you can move SVG objects, resize them, or otherwise make them do cool things on the fly with your code. MDN has a good page on transform here

And the last bit is always important. Attach your new SVG object to an SVG object. Don't leave it floating in space.

By the way, I didn't plot out this path either, even though it is simpler than Martin's. I used Inkscape! First I created a 32x32 workspace and then drew a spiral. Inkscape has some built-in spirals (not part of SVG but still cool) so I plopped one down and then saved it (save it as SVG, not as Inkscape SVG) and pasted the code into my web page. Very easy. There's a lot of Inkscape tutorials and other books, but that book by Dmitry Kirsanov is very cool.

So, we've got our spiral, and then when you click on it, the mousedown event handler gets the x and y coordinates and uses the SVG transform/translate attributes to move the spiral to a new position and display the new coordinates.

After the calculation, use this to move the spiral:

       mySpiral.setAttribute("transform",
          "translate(" + spiralX + "," + spiralY + ")");    
          
 

I had also set up a place to put messages with 

        myPara.innerHTML = "Touch the screen.";

And defined the absolute location in the CSS style section. Then to use it, I just put this in the mousedown event handler:

        // Convert to string for display.
        myCoord = mouseX.toString() + " right, "
          + mouseY.toString() + " down";
        myPara.textContent = myCoord;


So after all that, here is what the page looks like on my ZTE Open:


The spiral is near the middle and the message tells you to touch the screen. And when you do, this is what happens:



You can do this all day!

By the way, you want to be careful where you define your objects when working with SVG. In this example, the anchors were in the body. I found that if I put the paragraph after the div, the click wasn't handled if the click was over the paragraph. It's always important to remember that the flow of objects is set by the order in the web page and sometimes that can confuse you when you're throwing about objects.

Next

There's so much to talk about for Firefox OS game programming. Yes, it's HTML5 game programming, but things are different once you're talking about phones. Here's my schedule, subject to change, for the next few non-game-review posts:
  1. Comparison of CSS, Canvas, and SVG
  2. Screen Orientation
  3. Device Orientation (not the same as Screen Orientation)
  4. Collision Detection
  5. A simple first game
And that's just for starters. Stay tuned, but not iTuned.