首頁

2013年6月19日 星期三

**SMASHING MAGAZINE's*^How to Design A Moblie Game With HTML5**~!

**SMASHING MAGAZINE's*^How to Design A Moblie Game With HTML5**~!

*A handful of game developers are pushing the envelope of mobile HTML5 games at the moment. Check out the likes of   Nutmeg and Lunch Bug for some shining examples. The great thing about these titles is that they work equally well on         both mobile and desktop using the same code.  
Could HTML5 finally fulfill the holy grail of “write once, run anywhere”?*

*Getting Started
Before you start sketching the next Temple 
 Run or Angry Birds, you should be aware of  
 a few things that could  
dampen your excitement:  *

*Performance 
Mobile browsers are not traditionally 
known for their blazing JavaScript  
engines. With iOS 6 and Chrome beta  
for Android, though,  
 things are improving fast.
  • Resolution
    A veritable cornucopia of Android 
  • devices sport a wide range of   
  • resolutions. Not to mention the  
  •  increased resolution and pixel density 
  • of the iPhone 4 and iPad 3         *
  • *Audio
    Hope you enjoy the sound of silence.
    Audio support in mobile browsers is poor,
     to say the least. Lag is a major problem,
    as is the fact that most devices offer only
     a single channel. iOS won’t even load a    
    sound until the user initiates the action.   
    My advice is to hold tight and wait for    
    browser vendors to sort this out.                *
    *
    Now, as a Web developer you’re used to
     dealing with the quirks of certain      
    browsers and degrading gracefully and   
    dealing with fragmented platforms. So,  
    a few technical challenges won’t put you 
    off, right? What’s more, all of these     
    performance and audio problems are    
    temporary. The mobile browser landscape 
    is changing so quickly that these concerns 
    will soon be a distant memory.
    In this tutorial, we’ll make a relatively  
    simple game that takes you through the  
    basics and steers you away from pitfalls.
    The result will look like this:                         *

    *
    Screenshot of finished demo*

    *
  • Play the demo. 5
  • Download the demo  (ZIP).   *

  • *It’s a fairly simple game, in which the 
    user bursts floating bubbles before they 
    reach the top of the screen. Imaginatively, 
     I’ve titled our little endeavour Pop.        *
    *We’ll develop this in a number  
     of distinct stages:                     *

    *
    1. Cater to the multitude of viewports
    2. and optimize for mobile;
    3. Look briefly at using the canvas API
    4. to draw to the screen;
    5. Capture touch events;
    6. Make a basic game loop;
    7. Introduce sprites, or game “entities”;
    8. Add collision detection and some
    9. simple maths to spice things up;
    10. Add a bit of polish and some basic
    11. particle effects.    *
    *1. Setting The Stage
    Enough of the background story. Fire up
     your favorite text editor, pour a strong
    brew of coffee, and let’s get our 
    hands dirty.
    As mentioned, there is a plethora of  
    resolution sizes and pixel densities    
    across devices. This means we’ll have to  
    scale our canvas to fit the viewport.  
    This could come at the price of a loss 
    in quality, but one clever trick is to make  
    the canvas small and then scale up, which  
     provides a performance boost.
    Let’s kick off with a basic HTML shim:         *
    *
    <!DOCTYPE HTML>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    
    <meta name="viewport" content=
    "width=device-width, 
    
        user-scalable=no, initial-scale=1, 
    maximum-scale=1, user-scalable=0" /> 
    
    <meta name="apple-mobile-web-app-
    capable" content="yes" /> 
    
    <meta name="apple-mobile-web-app-
    status-bar-style" content="black-translucent" /> 
    
    <style type="text/css">
    body { margin: 0; padding: 0; background: #000;} 
    canvas { display: block; margin: 0 auto; 
     background: #fff; } 
    
    </style>
    
    </head>
    
    <body>
    
    <canvas> </canvas>
    <script>
    // all the code goes here 
    </script>
    
    </body> 
    </html> 
    The meta viewport tag tells mobile  
     browsers to disable user scaling and to
     render at full size rather than shrink   
    the page down.
    The subsequent apple-    prefixed meta  
    tags allow the game to be bookmarked. 
    On the iPhone, bookmarked apps do not 
    display the toolbar at the bottom of the
     page, thus freeing up valuable real estate.
    Take a look at the following:                  *

    *
    // namespace our game 
    var POP = { 
    
        // set up some initial values 
        WIDTH: 320, 
        HEIGHT:  480, 
        // we'll set the rest of these 
        // in the init function 
        RATIO:  null,
        currentWidth:  null,
        currentHeight:  null,
        canvas: null,
        ctx:  null,
    
        init: function() { 
    
            // the proportion of width to height 
            POP.RATIO = POP.WIDTH / POP.HEIGHT;
            // these will change when the 
    screen is resized
            POP.currentWidth = POP.WIDTH;
            POP.currentHeight = POP.HEIGHT;
            // this is our canvas element
            POP.canvas = document.
    getElementsByTagName('canvas')[0];
            // setting this is important 
            // otherwise the browser will 
            // default to 320 x 200 
            POP.canvas.width = POP.WIDTH; 
            POP.canvas.height = POP.HEIGHT; 
            // the canvas context enables us to 
            // interact with the canvas api 
            POP.ctx = POP.canvas.getContext('2d'); 
    
            // we're ready to resize 
            POP.resize(); 
    
        }, 
    
        resize: function() { 
    
            POP.currentHeight = window.innerHeight;
            // resize the width in proportion
            // to the new height
            POP.currentWidth =
     POP.currentHeight * POP.RATIO;
    
            // this will create some extra 
    space on the
            // page, allowing us to scroll past
            // the address bar, thus hiding it.
            if (POP.android || POP.ios) {
                document.body.style.height =
     (window.innerHeight + 50) + 'px';
            }
    
            // set the new canvas style 
    width and height
            // note: our canvas is still 
    320 x 480, but
            // we're essentially scaling 
    it with CSS
            POP.canvas.style.width =
     POP.currentWidth + 'px';
            POP.canvas.style.height =
     POP.currentHeight + 'px';
    
            // we use a timeout here because 
    some mobile
            // browsers don't fire if there is not 
            // a short delay 
            window.setTimeout(function() { 
                    window.scrollTo(0,1); 
            }, 1);
        } 
    
    };
    
    window.addEventListener 
    ('load', POP.init, false); 
    window.addEventListener 
    ('resize', POP.resize, false); 
    First, we create the POP namespace for
     our game. Being good developers,   
    we don’t want to pollute the global 
    namespace. In keeping good practice, 
     we will declare all variables at the  
    start of the program.
    Most of them are obvious: canvas  
    refers to the canvas    element in the HTML,
     and ctx    enables us to access it via the   
    JavaScript canvas API. 
    In POP.init,    we grab a reference to
    our canvas element, get its context 
    and adjust the canvas element’s     
    dimensions to 480 × 320.
     The resize     function, which is fired on 
    resize and load events, adjusts the  
     canvas’ style    attribute for width and
    height accordingly while maintaining 
    the ratio. Effectively, the canvas is   
    still the same dimensions but has been 
    scaled up using CSS. Try resizing your  
    browser and you’ll see the canvas scale to fit.
    If you tried that on your phone, you’ll 
     notice that the address bar is still     
    visible. Ugh! We can fix this by adding 
    a few extra pixels to the document and 
    then scrolling down to hide  
    the address bar, like so: 
    // we need to sniff out Android and iOS
    // so that we can hide the address bar in
    // our resize function
    POP.ua = navigator.userAgent.toLowerCase();
    
    POP.android = POP.ua.indexOf('android') >
     -1 ? true : false;
    
    POP.ios = ( POP.ua.indexOf('iphone') >
     -1 || POP.ua.indexOf('ipad') > -1  ) ? 
        true : false;
    The code above sniffs out the user
    agent, flagging for Android and iOS
    if present. Add it at the end of POP.init
    before the call to POP.resize().             *

    *Then, in the resize    function,
     if android    or ios    is true,    we add  
    another 50 pixels to the document’s  
    height — i.e. enough extra space to be 
    able to scroll down past the address bar.
    // this will create some extra space on the 
    // page, enabling us to scroll past 
    // the address bar, thus hiding it. 
    if (POP.android || POP.ios) { 
        document.body.style.height =
     (window.innerHeight + 50) + 'px';
    } 
    Notice that we do this only for Android
    and iOS devices; otherwise,
     nasty scroll bars will appear. Also,
     we need to delay the firing of scrollTo   
    to make sure it doesn’t get ignored  
    on mobile Safari.  *

    *2. A Blank Canvas
    Now that we’ve scaled our canvas  
    snuggly to the viewport, let’s add   
    the ability to draw some shapes.
    *Note: In this tutorial, we’re going to 
     stick with basic geometric shapes.  
    iOS 5 and Chrome beta for Android can 
     handle a lot of image sprites at a high 
    frame rate. Try that on Android 3.2 or  
    lower and the frame rate will 
    drop exponentially.
     Luckily, there is not much overhead 
    when drawing circles,
     so we can have a lot of bubbles in our 
    game without hampering performance   
    on older devices.
    Below, we’ve added a basic Draw    object  
    that allows us to clear the screen, draw a 
    rectangle and circle, and add some text.
    Nothing mind-blowing yet.  
    Mozilla Developers Network has excellent  
     resources as always,   

    *
    // abstracts various canvas operations into 
    // standalone functions 
    POP.Draw = { 
    
        clear: function() { 
            POP.ctx.clearRect 
    (0, 0, POP.WIDTH, POP.HEIGHT); 
        },
    
        rect: function(x, y, w, h, col) { 
            POP.ctx.fillStyle = col;
            POP.ctx.fillRect(x, y, w, h);
        },
    
        circle: function(x, y, r, col) { 
            POP.ctx.fillStyle = col;
            POP.ctx.beginPath();
            POP.ctx.arc 
    (x + 5, y + 5, r, 0,  Math.PI * 2, true);
            POP.ctx.closePath(); 
            POP.ctx.fill(); 
        },
    
        text: function(string, x, y, size, col) { 
            POP.ctx.font =
     'bold '+size+'px Monospace'; 
            POP.ctx.fillStyle = col; 
            POP.ctx.fillText(string, x, y); 
        } 
    
    };
    Our Draw object has methods for clearing 
     the screen and drawing rectangles, 
     circles and text.  
    The benefit of abstracting these  
    operations is that we don’t have to  
    remember the exact canvas API calls,
     and we can now draw a circle with  
    one line of code, rather than five.
    Let’s put it to the test: 
    // include this at the end of POP.init function 
    POP.Draw.clear(); 
    POP.Draw.rect(120,120,150,150, 'green'); 
    POP.Draw.circle(100, 100, 50, 'rgba(255,0,0,0.5)');
    POP.Draw.text('Hello World', 100, 100, 10, '#000');
    Include the code above at the end  
    of the POP.init        
    function, and you should see a couple 
    of shapes drawn to the canvas.

    3. The Magic Touch 

    Just as we have the click event, 
    mobile browsers provide methods 
     for catching touch events.
    The interesting parts of the code below
     are the touchstart, touchmove     
    and touchend                   events.
    With the standard click      event, we can
     get the coordinates from e.pageX    
    and e.pageY.     Touch events are slightly 
     different. They contain a touches     
    array, each element of which contains  
     touch coordinates and other data.
    We only want the first touch,
     and we access it like so: e.touches[0].    
    Note: Android provides JavaScript   
    access to multi-touch actions only  
    since version 4.
    We also call e.preventDefault();     when
    each event is fired to disable scrolling,
     zooming and any other action that  
    would interrupt the flow of the game.
    Add the following code  
    to the POP.init      function.
    // listen for clicks 
    window.addEventListener('click', function(e) { 
        e.preventDefault(); 
        POP.Input.set(e); 
    }, false); 
    
    // listen for touches 
    window.addEventListener('touchstart', function(e) { 
        e.preventDefault(); 
        // the event object has an array 
        // named touches; we just want 
        // the first touch 
        POP.Input.set(e.touches[0]);
    }, false);
    window.addEventListener('touchmove', function(e) { 
        // we're not interested in this,
        // but prevent default behaviour 
        // so the screen doesn't scroll 
        // or zoom 
        e.preventDefault();
    }, false); 
    window.addEventListener('touchend', function(e) { 
        // as above 
        e.preventDefault(); 
    }, false); 
    You probably noticed that the code  
    above passes the event data to   
    an Input object, which we’ve yet 
     to define. Let’s do that now:
    // + add this at the bottom of your code,
    // before the window.addEventListeners 
    POP.Input = { 
    
        x: 0,
        y: 0,
        tapped :false,
    
        set: function(data) { 
            this.x = data.pageX; 
            this.y = data.pageY; 
            this.tapped = true; 
    
            POP.Draw.circle 
    (this.x, this.y, 10, 'red'); 
        } 
    
    };
    Now, try it out. Hmm, the circles are 
    not appearing. A quick scratch of the 
    head and a lightbulb moment! Because
     we’ve scaled the canvas, we need to  
    account for this when mapping the  
    touch to the screen’s position.
    First, we need to subtract the offset  
    from the coordinates.
    var offsetTop = POP.canvas.offsetTop, 
        offsetLeft = POP.canvas.offsetLeft; 
    
    this.x = data.pageX - offsetLeft; 
    this.y = data.pageY - offsetTop;                      *
     
    *Diagram showing canvas offset relative to the browser window*
    *
    Then, we need to take into account the
    factor by which the canvas has been
    scaled so that we can plot to the actual
    canvas (which is still 320 × 480).
    var offsetTop = POP.canvas.offsetTop,
        offsetLeft = POP.canvas.offsetLeft;
        scale = POP.currentWidth / POP.WIDTH; 
    
    this.x = ( data.pageX - offsetLeft ) / scale; 
    this.y = ( data.pageY - offsetTop ) / scale;             *
     
    *Difference between CSS scaled canvas and actual dimensions *

    *
    If your head is starting to hurt, a practical
    example should provide some relief. Imagine
    the player taps the 500 × 750 canvas above
    at 400,400. We need to translate that
    to 480 × 320 because, as far as the JavaScript
    is concerned, those are the dimensions of the
    canvas. So, the actual x coordinate is 400
    divided by the scale; in this case
    , 400 ÷ 1.56 = 320.5.
    Rather than calculating this on each touch
    event, we can calculate them after resizing.
    Add the following code to the start of the
    program, along with the other
    variable declarations:
    // let's keep track of scale 
        // along with all initial declarations 
        // at the start of the program 
        scale:  1,
        // the position of the canvas 
        // in relation to the screen 
        offset = {top: 0, left: 0}, 
    In our resize function, after adjusting the
    canvas’ width and height, we make note of
    the current scale and offset:
    // add this to the resize function.
    POP.scale = POP.currentWidth / POP.WIDTH; 
    POP.offset.top = POP.canvas.offsetTop; 
    POP.offset.left = POP.canvas.offsetLeft; 
    Now, we can use them in the set method of
    our POP.Input class:
    this.x = 
    (data.pageX - POP.offset.left) / POP.scale; 
    this.y = 
    (data.pageY - POP.offset.top) / POP.scale;              *
     
    **

    4. In The Loop

    A typical game loop goes something like this:
    1. Poll user input,
    2. Update characters and process collisions,
    3. Render characters on the screen,
    4. Repeat.
    We could, of course, use setInterval, but
    there’s a shiny new toy in town named
    requestAnimationFrame. It promises smoother
    animation and is more battery-efficient.
    The bad news is that it’s not supported
    consistently across browsers. But Paul Irish
    has come to the rescue with a handy shim.
    Let’s go ahead and add the shim to the start
    of our current code base.
    // http://paulirish.com/2011/ 
    requestanimationframe-for-smart-animating 
    // shim layer with setTimeout fallback 
    window.requestAnimFrame = (function(){
      return  window.requestAnimationFrame       || 
              window.webkitRequestAnimationFrame || 
              window.mozRequestAnimationFrame    || 
              window.oRequestAnimationFrame      || 
              window.msRequestAnimationFrame     || 
              function( callback ){ 
                window.setTimeout 
    (callback, 1000 / 60); 
              }; 
    })(); 
    And let’s create a rudimentary game loop:
    // Add this at the end of POP.init; 
    // it will then repeat continuously 
    POP.loop(); 
    
    // Add the following functions after POP.init: 
    
    // this is where all entities will be moved 
    // and checked for collisions, etc.
    update: function() { 
    
    },
    
    // this is where we draw all the entities 
    render: function() { 
    
       POP.Draw.clear(); 
    },
    
    // the actual loop 
    // requests animation frame, 
    // then proceeds to update 
    // and render 
    loop: function() { 
    
        requestAnimFrame( POP.loop ); 
    
        POP.update(); 
        POP.render(); 
    } 
    We call the loop at the end of POP.init.
    The POP.loop function in turn calls our POP.
    update and POP.render methods.
    requestAnimFrame ensures that the loop is
    called again, preferably at 60 frames per
    second. Note that we don’t have to worry
    about checking for input in our loop because
    we’re already listening for touch and click
    events, which is accessible through our POP.
    Input class.
    The problem now is that our touches from the
    last step are immediately wiped off the screen.
    We need a better approach to remember what was
    drawn to the screen and where. **

    *

    5. Spritely Will Do It

    First, we add an entity array to keep track
    of all entities. This array will hold a
    reference to all touches, bubbles,
    particles and any other dynamic thing we
    want to add to the game.
    // put this at start of program 
    entities: [], 
    Let’s create a Touch class that draws a
    circle at the point of contact, fades it out
    and then removes it.
    POP.Touch = function(x, y) { 
    
        this.type = 'touch';    // we'll 
    need this later 
        this.x = x;             // the x coordinate 
        this.y = y;             // the y coordinate 
        this.r = 5;             // the radius 
        this.opacity = 1;       // initial 
    opacity; the dot will fade out 
        this.fade = 0.05;       // amount by
     which to fade on each game tick 
        this.remove = false;    // flag for
     removing this entity. POP.update 
                                // will take
     care of this 
    
        this.update = function() { 
            // reduce the opacity accordingly  
            this.opacity -= this.fade; 
            // if opacity if 0 or less, 
     flag for removal 
            this.remove =
     (this.opacity < 0) ? true : false; 
        }; 
    
        this.render = function() { 
            POP.Draw.circle
    (this.x, this.y, this.r, 'rgba
    (255,0,0,'+this.opacity+')'); 
        }; 
    
    }; 
    The Touch class sets a number of properties
    when initiated. The x and y coordinates are
    passed as arguments, and we set the radius
    this.r to 5 pixels. We also set an initial
    opacity to 1 and the rate by which the
    touch fades to 0.05. There is also a remove
    flag that tells the main game loop whether
    to remove this from the entities array.
    Crucially, the class has two main methods:
    update and render. We will call these from
    the corresponding part of our game loop.
    We can then spawn a new instance of Touch in
    the game loop, and then move them via
    the update method:
    // POP.update function 
    update: function() { 
    
        var i; 
    
        // spawn a new instance of Touch 
        // if the user has tapped the screen 
        if (POP.Input.tapped) { 
            POP.entities.push(new POP.Touch 
    (POP.Input.x, POP.Input.y)); 
            // set tapped back to false 
            // to avoid spawning a new touch 
            // in the next cycle 
            POP.Input.tapped = false; 
        } 
    
        // cycle through all entities and  
    update as necessary 
        for (i = 0; i < POP.entities.
    length; i += 1) { 
            POP.entities[i].update(); 
    
            // delete from array if remove property 
            // flag is set to true 
            if (POP.entities[i].remove) { 
                POP.entities.splice(i, 1); 
            } 
        } 
    
    }, 
    Basically, if POP.Input.tapped is true,
    then we add a new instance of POP.Touch to
    our entities array. We then cycle through the
    entities array, calling the update method
    for each entity. Finally, if the entity is
    flagged for removal, we delete it from the array.
    Next, we render them in the POP.render function.
    // POP.render function 
    render: function() { 
    
        var i; 
    
       POP.Draw.rect(0, 0, POP.WIDTH, 
     POP.HEIGHT, '#036');
    
        // cycle through all entities and  
    render to canvas 
        for (i = 0; i < POP.
    entities.length; i += 1) { 
            POP.entities[i].render(); 
        } 
    
    }, 
    Similar to our update function, we cycle
    through the entities and call their render
    method to draw them to the screen.
    So far, so good. Now we’ll add a Bubble class
    that will create a bubble that floats up for
    the user to pop.
    POP.Bubble = function() { 
    
        this.type = 'bubble'; 
        this.x = 100; 
        this.r =
     5;                // the radius of the bubble 
        this.y = POP.HEIGHT +
     100; // make sure it starts off screen 
        this.remove = false; 
    
        this.update = function() { 
    
            // move up the screen by 1 pixel 
            this.y -= 1; 
    
            // if off screen, flag for removal 
            if (this.y < -10) { 
                this.remove = true; 
            } 
    
        }; 
    
        this.render = function() { 
    
            POP.Draw.circle 
    (this.x, this.y, this.r, 'rgba(255,255,255,1)'); 
        }; 
    
    }; 
    The POP.Bubble class is very similar to the
    Touch class, the main differences being that
    it doesn’t fade but moves upwards. The motion
    is achieved by updating the y position,
    this.y, in the update function. Here, we also
    check whether the bubble is off screen; if so,
    we flag it for removal.
    Note: We could have created a base Entity class
    that both Touch and Bubble inherit from. But,
    I’d rather not open another can of worms about
    JavaScript prototypical inheritance versus
    classic at this point.
    // Add at the start of the program 
    // the amount of game ticks until 
    // we spawn a bubble 
    nextBubble: 100, 
    
    // at the start of POP.update 
    // decrease our nextBubble counter 
    POP.nextBubble -= 1; 
    // if the counter is less than zero 
    if (POP.nextBubble < 0) { 
        // put a new instance of bubble into 
     our entities array 
        POP.entities.push(new POP.Bubble());
         // reset the counter with a random value 
        POP.nextBubble =
     ( Math.random() * 100 ) + 100; 
    } 
    Above, we have added a random timer to our
    game loop that will spawn an instance of
    Bubble at a random position. At the start
    of the game, we set nextBubble with a value
    of 100. This is subtracted on each game tick
    and, when it reaches 0, we spawn a bubble and
    reset the nextBubble counter.

    6. Putting It Together

    First of all, there is not yet any notion of
    collision detection. We can add that with a
    simple function. The math behind this is
    basic geometry, which you can
    // this function checks if two circles overlap 
    POP.collides = function(a, b) { 
    
            var distance_squared =
     ( ((a.x - b.x) * (a.x - b.x)) + 
                                    ((a.y - b.y)
     * (a.y - b.y))); 
    
            var radii_squared =
     (a.r + b.r) * (a.r + b.r); 
    
            if (distance_squared < radii_squared) { 
                return true; 
            } else { 
                return false; 
            } 
    }; 
    
    // at the start of POP.update, we set a  
    flag for checking collisions 
        var i, 
            checkCollision = false; // we only 
     need to check for a collision
                                    // if the user
     tapped on this game tick
    
    // and then incorporate into the main logic 
    
    if (POP.Input.tapped) { 
        POP.entities.push(new POP.Touch 
    (POP.Input.x, POP.Input.y)); 
        // set tapped back to false 
        // to avoid spawning a new touch 
        // in the next cycle 
        POP.Input.tapped = false; 
        checkCollision = true; 
    
    } 
    
    // cycle through all entities and  
    update as necessary 
    for (i = 0; i < POP.entities.length; i += 1) { 
        POP.entities[i].update();
    
        if (POP.entities[i].type ===
     'bubble' && checkCollision) {
            hit = POP.collides(POP.entities[i], 
                                {x: POP.Input. 
    x, y: POP.Input.y, r: 7});
            POP.entities[i].remove = hit; 
        } 
    
        // delete from array if remove property 
        // is set to true
        if (POP.entities[i].remove) { 
            POP.entities.splice(i, 1); 
        } 
    } 
    The bubbles are rather boring; they all
    travel at the same speed on a very predictable
    trajectory. Making the bubbles travel at
    random speeds is a simple task:
    POP.Bubble = function() { 
    
        this.type = 'bubble'; 
        this.r = (Math.random() * 20) + 10; 
        this.speed = (Math.random() * 3) + 1; 
    
        this.x = (Math.
    random() * (POP.WIDTH) - this.r); 
        this.y = POP.HEIGHT +
     (Math.random() * 100) + 100; 
    
        this.remove = false; 
    
        this.update = function() { 
    
            this.y -= this.speed; 
    
        // the rest of the class is unchanged 
    And let’s make them oscillate from side to
    side, so that they are harder to hit:
    // the amount by which the bubble 
        // will move from side to side 
        this.waveSize = 5 + this.r; 
        // we need to remember the original 
        // x position for our sine wave calculation 
        this.xConstant = this.x; 
    
        this.remove = false; 
    
        this.update = function() { 
    
            // a sine wave is commonly a 
     function of time 
            var time = new Date().getTime() * 0.002; 
    
            this.y -= this.speed;
            // the x coordinate to follow a sine wave 
            this.x = this.waveSize *
     Math.sin(time) + this.xConstant; 
    
        // the rest of the class is unchanged 
    Again, we’re using some basic geometry to
    achieve this effect; in this case, a sine wave.
    While you don’t need to be a math whiz to
    make games, basic understanding goes a long way.
    The article
    Animations With JavaScript"

    should get you started.
    Let’s also show some statistics on screen.
    To do this, we will need to track various
    actions throughout the game.
    Put the following code, along with all of
    the other variable declarations, at the beginning
    of the program.
    // this goes at the start of the program 
    // to track players's progress 
    POP.score = { 
        taps: 0, 
        hit: 0, 
        escaped: 0, 
        accuracy: 0 
    }, 
    Now, in the Bubble class we can keep track
    of POP.score.escaped when a bubble goes off screen.
    // in the bubble class, when a bubble makes it to 
    // the top of the screen
        if (this.y < -10) {
            POP.score.escaped += 1; // update score
            this.remove = true;
        } 
    In the main update loop, we increase POP.
    score.hit accordingly:
    // in the update loop 
    if (POP.entities[i].type ===
     'bubble' && checkCollision) { 
        hit = POP.collides(POP.entities[i], 
                            {x: POP.Input.x, y: POP.
    Input.y, r: 7});
        if (hit) { 
            POP.score.hit += 1; 
        } 
    
        POP.entities[i].remove = hit; 
    } 
    In order for the statistics to be accurate,
    we need to record all of the taps
    the user makes:
    // and record all taps 
    if (POP.Input.tapped) { 
        // keep track of taps; needed to 
        // calculate accuracy 
        POP.score.taps += 1; 
    Accuracy is obtained by dividing the number
    of hits by the number of taps,
    multiplied by 100, which gives us a nice
    percentage. Note that ~~(POP.score.accuracy)
    is a quick way (i.e. a hack) to round floats
    down to integers.
    // Add at the end of the update loop 
    // to calculate accuracy 
    POP.score.accuracy =
     (POP.score.hit / POP.score.taps) * 100; 
    POP.score.accuracy = isNaN(POP.score.accuracy) ? 
        0 :
        ~~(POP.score.accuracy); 
     // a handy way to round floats
    Lastly, we use our POP.Draw.text to display
    the scores in the main update function.
    // and finally in the draw function 
    POP.Draw.text
    ('Hit: ' + POP.score.hit, 20, 30, 14, '#fff'); 
    POP.Draw.text
    ('Escaped: ' + POP.score.escaped,
     20, 50, 14, '#fff'); 
    POP.Draw.text
    ('Accuracy: ' + POP.score.accuracy +
     '%', 20, 70, 14, '#fff'); 

    7. Spit And Polish

    There’s a common understanding that a
    playable demo can be made in a couple of hours,
    but a polished game takes days, week,
    months or even years!
    We can do a few things to improve the visual
    appeal of the game.

    Particle Effects

    Most games boast some form of particle
    effects, which are great for explosions.
    What if we made a bubble explode into many
    tiny bubbles when it is popped, rather than
    disappear instantly?
    Take a look at our Particle class:
    POP.Particle = function(x, y,r, col) { 
    
        this.x = x; 
        this.y = y; 
        this.r = r; 
        this.col = col; 
    
        // determines whether particle will 
        // travel to the right of left 
        // 50% chance of either happening 
        this.dir =
     (Math.random() * 2 > 1) ? 1 : -1; 
    
        // random values so particles do not 
        // travel at the same speeds 
        this.vx = ~~(Math.random() * 4) * this.dir; 
        this.vy = ~~(Math.random() * 7); 
    
        this.remove = false; 
    
        this.update = function() { 
    
            // update coordinates 
            this.x += this.vx; 
            this.y += this.vy; 
    
            // increase velocity so particle 
            // accelerates off screen 
            this.vx *= 0.99; 
            this.vy *= 0.99; 
    
            // adding this negative amount to the 
            // y velocity exerts an upward pull on 
            // the particle, as if drawn to the 
            // surface 
            this.vy -= 0.25;
    
            // off screen 
            if (this.y < 0) { 
                this.remove = true; 
            } 
    
        };
    
        this.render = function() { 
            POP.Draw.circle 
    (this.x, this.y, this.r, this.col); 
        }; 
    
    }; 
    It’s fairly obvious what is going on here.
    Using some basic acceleration so that the
    particles speed up as the reach the surface
    is a nice touch. Again, this math and physics
    are beyond the scope of this article, but for
    those interested,
    To create the particle effect, we push several
    particles into our entities array
    whenever a bubble is hit:
    // modify the main update function like so: 
    if (hit) { 
        // spawn an explosion 
        for (var n = 0; n < 5; n +=1 ) { 
            POP.entities.push(new POP.Particle( 
                POP.entities[i].x, 
                POP.entities[i].y, 
                2, 
                // random opacity to 
     spice it up a bit
                'rgba
    (255,255,255,'+Math.random()*1+')'
            )); 
        } 
        POP.score.hit += 1; 
    } 

    Waves

    Given the underwater theme of the game,
    adding a wave effect to the top of the screen would be a nice touch. We can do this by drawing a number of overlapping circles to give the illusion of waves:
    // set up our wave effect; 
    // basically, a series of overlapping circles 
    // across the top of screen 
    POP.wave = { 
        x: -25, // x coordinate of first circle 
        y: -40, // y coordinate of first circle 
        r: 50, // circle radius 
        time: 0, // we'll use this in 
    calculating the sine wave 
        offset: 0 // this will be the
     sine wave offset 
    }; 
    // calculate how many circles we need to 
    // cover the screen's width
    POP.wave.total =
     Math.ceil(POP.WIDTH / POP.wave.r) + 1; 
    Add the code above to the POP.init function.
    POP.wave has a number of values that we’ll
    need to draw the waves.
    Add the following to the main update function.
    It uses a sine wave to adjust the position of
    the waves and give the illusion of movement.
    // update wave offset 
    // feel free to play with these values for 
    // either slower or faster waves 
    POP.wave.time = new Date().getTime() * 0.002; 
    POP.wave.offset =
     Math.sin(POP.wave.time * 0.8) * 5; 
    All that’s left to be done is to draw the waves,
    which goes into the render function.
    // display snazzy wave effect 
    for (i = 0; i < POP.wave.total; i++) { 
    
        POP.Draw.circle(
                    POP.wave.x + POP.wave.
    offset +  (i * POP.wave.r), 
                    POP.wave.y,
                    POP.wave.r, 
                    '#fff'); 
    } 
    Here, we’ve reused our sine wave solution for
    the bubbles to make the waves move gently to
    and fro. Feeling seasick yet?

    Final Thoughts

    Phew! That was fun. Hope you enjoyed this short
    forage into tricks and techniques for making
    an HTML5 game. We’ve managed to create a very
    simple game that works on most smartphones as
    well as modern browsers. Here are some things
    you could consider doing:
    • Store high scores using local storage.
    • Add a splash screen and a “Game over” screen.
    • Enable power-ups.
    • Add audio. Contrary to what I said at the
    • beginning of this article, this isn’t
    • , just a bit of a headache. One technique is
    • to use audio sprites (kind of like CSS image
    • sprites); Remy Sharp breaks it down 6.
    • Let your imagination run wild!
    If you are interested in further exploring the
    possibilities of mobile HTML5 games,
    I recommend test-driving a couple of frameworks
    to see what works for you. Juho Vepsäläinen
    If you’re willing to invest a little cash,
    then Impact is a great starting point, with
    thorough documentation and lively helpful forums.
    And the impressive X-Type demonstrates what is
    possible. Not bad, eh?
    (al) (jc) *

    &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
    &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&

    沒有留言:

    張貼留言


    if you like make fds, wellcome you here~~anytime***

    my free place for everyones who want the good software,

    come & download them~ wellcome!!