Cubicle Ninja

December 2, 2011

JavaScript and HTML5 – Simple Game Creation (part 6)

Filed under: HTML5,JavaScript — Tags: , , , , , — Cubicle Ninja @ 8:24 pm

Finally we get to part 6 of the game creation tutorial / learning endeavor. Reality conspired against me to cause this post to be delayed, but I got here all the same. Today we’re going to dig into creating goodness, gracious great balls of fire for our hero to throw around in a devil-may-care fashion. The image we’ll be using for our fireball sprite is one I found somewhere ages ago (honestly, no clue when or where I got it) and slightly modified to meet the needs of this game. It follows the same pattern as our other animate sprite (for the hero / villains) and is just a sequence of “keyframes” for the sprite with a transparent background.

fireballs!

Several of the pieces for the fireball object will be directly duplicated from our hero object (because, really, fireballs and people share, like, 95% of the same DNA sequences). We will have one main new concept that will be introduced: destroying / recreating an object. We want to ensure that when our fireball hits a wall, enemy or the edge of the world it is destroyed to free up the memory allocated for it (and to make sure invisible fireballs don’t keep roaming around all higgledy-piggledy).

As I mentioned, thee fireball object is going to be almost identical to the hero object. We’ll adjust the width and height to be 16 instead of 32, change the speed a little and (the biggest change) give it a destroyed property that will be set to true when it goes off the screen:

function fireball()
{

    // The width and height of the sprites for our fireball
    this.width = 16;
    this.height = 16;
    // When our fireball is first created is in null land
    // we assign a location based upon where the hero is
    this.x = null;
    this.y = null;
    // An array to hold the information about which keyboard keys are pressed
    this.keys = new Array();
    // When was the last time we drew the fireball to the screen
    this.lastRender = Date.now();
    // What delay do we want to use between switching sprites (in milliseconds)
    this.animSpeed = 100;
    // What image are we using for the hero
    this.image = new Image();
    // Which sprite in the image are we currently rendering
    this.whichSprite = 0;
    // How many pixels do we want to move the fireball each loop
    this.moveSpeed = 7;
    // Do we have a collision event?
    this.collision = false;
    // When was the last time we had a direction change?
    this.lastKeyChange = Date.now();
    // Do we need to destroy the fireball?
    this.destroyed = false;

    this.render = function()
    {
        // drawImage takes for parameters:
        //      the image to draw
        //      the x and y coordinates to use from the source image
        //      the width and height to use from the source image
        //      the x and y coordinates to draw it to on the canvas
        //      the width and height to draw it into on the canvas
        context.drawImage(this.image, this.whichSprite, 0, this.width, this.height, this.x, this.y, this.width, this.height);
    };

    this.checkCollision = function(obj)
    {
        // check to see if our x coordinate is inside the object and
        // our y coordinate is also inside the object
        // Adjust these to give 1 full pixel lenience due to canvas allowing
        // partial pixel rendering
        if ((this.x < (obj.x + obj.width - 1) && Math.floor(this.x + this.width - 1) > obj.x)
            && (this.y < (obj.y + obj.height - 1) && Math.floor(this.y + this.height - 1) > obj.y))
        {
            return true;
        }
    };

    this.update = function(elapsed)
    {
        // store out the current x and y coordinates
        var prevX = this.x;
        var prevY = this.y;
        // reset the collision property
        this.collision = false;

        var now = Date.now();
        // How long has it been since we last updated the sprite
        var delta = now - this.lastRender;

        // perform a switch statement on the last key pushed into the array
        // this allows us to always move the direction of the most recently pressed
        // key
        switch (this.keys[this.keys.length - 1])
        {
            case 37:
                // move the fireball left on the screen
                this.x -= this.moveSpeed * elapsed;
                // Check if the animation timer has elapsed
                if (delta > this.animSpeed)
                {
                    this.whichSprite = this.whichSprite == 0 ? 16 : this.whichSprite == 16 ? 32 : this.whichSprite == 32 ? 48 : 0;
                    this.lastRender = now;
                }
                break;
            case 38:
                // move the fireball up on the screen
                this.y -= this.moveSpeed * elapsed;
                // Check if the animation timer has elapsed
                if (delta > this.animSpeed)
                {
                    this.whichSprite = this.whichSprite == 0 ? 16 : this.whichSprite == 16 ? 32 : this.whichSprite == 32 ? 48 : 0;
                    this.lastRender = now;
                }
                break;
            case 39:
                // move the fireball right on the screen
                this.x += this.moveSpeed * elapsed;
                // Check if the animation timer has elapsed
                if (delta > this.animSpeed)
                {
                    this.whichSprite = this.whichSprite == 0 ? 16 : this.whichSprite == 16 ? 32 : this.whichSprite == 32 ? 48 : 0;
                    this.lastRender = now;
                }
                break;
            case 40:
                // move the fireball down on the screen
                this.y += this.moveSpeed * elapsed;
                // Check if the animation timer has elapsed
                if (delta > this.animSpeed)
                {
                    this.whichSprite = this.whichSprite == 0 ? 16 : this.whichSprite == 16 ? 32 : this.whichSprite == 32 ? 48 : 0;
                    this.lastRender = now;
                }
                break;
        }
       
        // Logic to see if we're going off the screen
        // If we do we are destroyed
        if (this.x < -this.width)
        {
            this.destroyed = true;
        }
        if (this.x >= renderW)
        {
            this.destroyed = true;
        }
        if (this.y < -this.height)
        {
            this.destroyed = true;
        }
        if (this.y >= renderH)
        {
            this.destroyed = true;
        }
    };
}

Much like we did for the enemies and the rocks, we’re going to need a base fireball object that we will load the image into in order to speed things along as we are playing. At the top, underneath var baseEnemy = null; we’ll add in:

// variable to hold our base fireball image
var baseFireball;

And we need a new function to initialize the base fireball object (it’s actually only two lines of code, but we’re doing it for consistency since we did it for all the other objects):

function initFireball()
{
    baseFireball = new Image();
    baseFireball.src = "images/fireballs.png";
}

Now just add a call to initFireball(); inside the $(window).load(function() area right after initEnemies();.

Once we know what a fireball is and have the base all set, we’re going to make it up so that our hero is only allowed to have one active fireball in play at a given time. We’ll start off by just adding a new property to our hero object called “activeFireball” and a boolean “shooting” variable. Inside our function heroObject() add:

    // Do we have an active fireball in play?
    this.activeFireball = null;
    // Rather than using the keys array for fireballs, we'll just have a boolean
    this.shooting = false;

Now we’ll make a slight adjustment to our key capture logic to trap a press of the spacebar (we’ll add logic into our hero later to check for this in order to Hadouken!). At the top of the keydown and keyup handlers we’ll add a check to see if the spacebar was pressed. This is the code for the keydown event, the keyup event is the same code except we set the value to false:

    // check if the spacebar is being pressed
    if (event.keyCode == 32)
        hero.shooting = true;

Now we’re all set capturing the spacebar (why the spacebar, you may ask? Because that’s what it always seems to be in games, just accept it). The next piece is to adjust our hero update method to check for the spacebar being pressed and, if it is, start up a fireball object. This code will go inside the update method, just above the switch statement for the hero movement:

        // check to see if the spacebar is being pressed        
        if (this.shooting)
        {
            // check to make sure we don't currently have a fireball in play
            if (this.activeFireball == null)
            {
                // create a new fireball inside our hero object
                this.activeFireball = new fireball();
                // set the image to use the base fireball we loaded
                this.activeFireball.image = baseFireball;

                // check which way our hero is facing, we use this to determine
                // where we position the fireball, which direction the fireball
                // has to move and which set of 4 sprites we use
                if (this.whichSprite < this.width * 2)
                {
                    this.activeFireball.keys[0] = 40;
                    this.activeFireball.x = this.x + (this.width / 4);
                    this.activeFireball.y = this.y + this.height;
                }
                else if (this.whichSprite < this.width * 4)
                {
                    this.activeFireball.keys[0] = 37;
                    this.activeFireball.x = this.x;
                    this.activeFireball.y = this.y + (this.height / 4);
                }
                else if (this.whichSprite < this.width * 6)
                {
                    this.activeFireball.keys[0] = 39;
                    this.activeFireball.x = this.x + this.width;
                    this.activeFireball.y = this.y + (this.height / 4);
                }
                else
                {
                    this.activeFireball.keys[0] = 38;
                    this.activeFireball.x = this.x + (this.width / 4);
                    this.activeFireball.y = this.y;
                }
               
                this.activeFireball.render();
            }
        }

The steps we are having the code perform are: check if the spacebar was just pressed — if so, remove the spacebar from the keys array (we do this because we want the movement switch to go based upon the last arrow key that was pressed) and check if the hero currently has an active fireball — if not, create a new fireball object and position it based upon which way the hero is facing then draw it to the screen.

The last change we have to make to get our fireball moving on the screen (but not finished since it will just go on forever at this point) is to add the logic into our function gameLoop() right after the call to hero.render();:

    // if the hero has a fireball, render it
    if (hero.activeFireball != null)
    {
        // Update the fireball based upon how long it took for the game loop
        hero.activeFireball.update(elapsed / timerRatio);

        // if our fireball was destroyed in the last update, remove it
        // otherwise draw it to the screen
        if (hero.activeFireball.destroyed)
            hero.activeFireball = null;
        else
            hero.activeFireball.render();
    }

When you run the code at this point you should be able to press the spacebar and have it shoot off a fireball. The fireball will be destroyed when it goes off the screen allowing you to shoot another. At the moment, the fireballs will pass right through the rocks and enemies and that just will NOT do! We’ll fix that glaring oversight by adding calls to the collisionCheck inside the fireball update method. For the rocks we’ll take the code we already have in place within the hero object that loops through and checks for collisions. We need to make a small tweak to it so that it tells the fireball to bugger off when it has a collision

        // loop through all of the rocks in the array
        // we use an for-in loop to go through the rocks in case
        // we later add some logic that can destroy static objects
        // a regular for loop could break with null values if that happens
        for (iter in rocks)
        {
            // check to see if we have a collision event with the
            // current rock
            if (this.checkCollision(rocks[iter]))
            {
                // we hit a rock, we need to destroy the fireball
                this.destroyed = true;
                break;
            }
        }

Great, now we can toss fireballs around the map and have them be destroyed when they go off screen or hit a rock. Of course, the REAL fun comes from being able to shoot the enemies. To accommodate the need for wanton destruction and the killing of cute, onscreen pixels, we need to add a new property into our heroObject (remember the heroObject is used for both the hero and the enemies). Right below the this.shooting = false; line we added earlier we’ll add in

    // Did we get hit by a fireball and need to be destroyed?
    this.destroyed = false;

Next step is adjusting the fireball update method to also iterate through all of the enemies to check for a collision. We’ll add this code after the loop through all of the rocks:

        // loop through all of the enemies in the array
        // we use an for-in loop to go through the enemies in case
        // we later add some logic that can destroy an enemy objects
        // a regular for loop could break with null values if that happens
        for (iter in enemies)
        {
            // check to see if we have a collision event with the
            // current enemy
            if (this.checkCollision(enemies[iter]))
            {
                // we hit an enemy, we need to destroy the fireball
                // and the enemy
                this.destroyed = true;
                enemies[iter].destroyed = true;
                break;
            }
        }

That tells it to destroy both the enemy AND the fireball when we have a collision between the two. We have one final piece of the puzzle and that is to update the gameLoop to have it remove the enemy from the game if it is destroyed. We’ll replace the foreach loop through the enemies within the gameLoop with the following:

    // do a foreach type loop through the enemies
    for (curEnemy in enemies)
    {
        if (enemies[curEnemy].destroyed)
        {
            enemies.splice(curEnemy, 1);
        }
        else
        {
            // Update the enemy based upon how long it took for the game loop
            enemies[curEnemy].update(elapsed / timerRatio);

            // check if the enemy collided with a rock, if it did turn it a random direction
            if (enemies[curEnemy].collision)
            {
                enemies[curEnemy].keys[0] = Math.floor(Math.random() * 4) + 37;
                enemies[curEnemy].lastKeyChange = Date.now();
            }

            // if the enemy has gone a while without changing directions, turn it a random direction
            if (now - enemies[curEnemy].lastKeyChange > ((Math.random() * 3000) + 5000))
            {
                enemies[curEnemy].keys[0] = Math.floor(Math.random() * 4) + 37;
                enemies[curEnemy].lastKeyChange = Date.now();
            }

            // draw the enemy to the screen again
            enemies[curEnemy].render();
        }
    }

And that does it. You can know wield flaming balls of death (well flaming ball of death since you can only have one at a time) that will destroy enemies and disappear off the screen. This post covered a fairly large amount of changes, so if anything doesn’t make sense feel free to let me know. Here’s a screenshot of a fireball in action.

Simple Game 6 - Sample Image

And, as always, the link to the demo for this code

October 29, 2011

JavaScript and HTML5 – Simple Game Creation (part 5)

Filed under: HTML5,JavaScript — Tags: , , , , , — Cubicle Ninja @ 11:20 am

I know I promised goodness gracious great balls of fire in part 5…but I changed my mind. I talked with a couple friends and they pointed out that it was a little annoying that the game was a fixed size and just sat there at 800×600 no matter how big they made the screen size / browser window. To remedy that, this post is going to be step through the process of implementing scaling into our context rendering. Fair warning, I have never done this with the HTML5 / JavaScript objects and haven’t even done it with DirectX for years now. This may not be the best way to do it! That having been said, this method seems to work for me in all the major desktop browsers I tried (Safari, Opera, IE 9, Firefox, Chrome) but if you know of a better way to do it, please let me know.

I’m going to assume that if you’re reading part 5 of the series you’ve already at least looked through part 1, part 2, part 3 and part 4. If you haven’t, I highly recommend you at least familiarize yourself with them somewhat or you may get lost. Then again, you may already know how to do this and just came to read my blog and mock me for the stupid decisions I make as I’m learning this stuff…I just made myself sad.

Back on topic now. The concept of scaling isn’t all that complicated in reality. What we want to do is tell the game that we want the resolution to be 800×600 but that we want that to stretch (or shrink) to fill all of the screen real estate the browser has available. Previously we had created two variables (gameW and gameH) that held the dimensions of our game and everything was driven off of those. In order to get our scaling working properly we are going to expand from those two variables up to eight different variables:

// These are now going to hold the dimensions of the full game window (browser)
var gameW = 0;
var gameH = 0;
// Add in these six variables to be able to track scaling for "fullscreen"
// These are what we want the actual dimensions (resolution) of the game to be
var baseW = 800;
var baseH = 600;
// These two will hold the rendered dimensions to be
var renderW = 0;
var renderH = 0;
// These will hold the scale difference between our rendered dimension and the actual browser dimensions
var scaleX = 1.0;
var scaleY = 1.0;

As you can see by the comments (go go comments) the gameW and gameH variables are now going to be holding the “true” dimensions of the game window (browser). The baseW and baseH variables are our static variables that tell the game what we want the resolution to be (feel free to change those around and see how things change later). Variables renderW and renderH are driven off of a comparison between the gameW/gameH and the baseW/baseH and will be used in our actual render methods for the objects being drawn to the screen. The scaleX and scaleY variables do just what you’d expect, they hold the scale ratio between what we want to show and what screen size we actual have.

There are a couple different ways I tried for doing this:

  1. Keeping it always at the base resolution and centering it in the browser — I didn’t go this route because it wasn’t really any improvement over doing nothing at all
  2. Maintaining a static aspect ratio and scaling to fill the window — this looks nicer, in my opinion, because the rendered objects are never stretched or squashed, but has the draw back of things not fitting properly if the window is resized to a different aspect ratio than at load time
  3. Not maintaining the aspect ratio and scaling to fill the window — this can cause objects to be squashed or stretched if the browser window is not of a size that keeps the 800×600 ratio, but things always fit properly on the screen

In the demo we’ll be implementing both 2 and 3 and providing a checkbox so you can toggle between the two methods. I’m not 100% sure which I like best of all, so I’ll wait and see if I get any feedback about which one other people like most and go forward with that one when we add the fireballs (I’m going to do my best to get that in part 6!).

As I mentioned at the start, we’ll be using the renderW and renderH variables for rendering out our objects. Because of this there are a couple changes we need to make. In each of our object methods we need to update the this.x and this.y assignments to use renderW/renderH instead of baseW/baseH. For the staticObject() we only need to change the assignment lines since it has no update method:

    // Change the positioning to reference the render height and width instead of the actual size
    this.x = this.width * Math.floor(Math.random() * ((renderW - this.width * 2) / this.width)) + this.width;
    this.y = this.height * Math.floor(Math.random() * ((renderH - this.height * 2) / this.height)) + this.height;

In the heroObject() we’ll change the x/y assignments:

    // Change this to use the render height and width
    this.x = this.width * Math.floor(Math.random() * (renderW / this.width));
    this.y = this.height * Math.floor(Math.random() * (renderH / this.height));

We also need to change the code in the update() method for the heroObject that handled wrapping around the screen:

        // This code handles wrapping the hero from one edge of the canvas to the other
        // Change these to use the render height and width
        if (this.x < -this.width)
        {
            this.x = renderW - this.width;
        }
        if (this.x >= renderW)
        {
            this.x = 0;
        }
        if (this.y < -this.height)
        {
            this.y = renderH - this.height;
        }
        if (this.y >= renderH)
        {
            this.y = 0;
        }

Another change is required to our initRocks() function because we had the logic in place that calculated a new x/y position for each rock when there was a collision:

            // check to see if we have a collision between this rock and
            // the hero object, if so we generate new coordinates for the rock
            // change this to use the render dimensions
            while (hero.checkCollision(rocks[i]))
            {
                rocks[i].x = this.width * Math.floor(Math.random() * ((renderW - this.width * 2) / this.width)) + this.width;
                rocks[i].y = this.height * Math.floor(Math.random() * ((renderH - this.height * 2) / this.height)) + this.height;
            }

And the final change is inside the gameLoop to our clearRect call

    // changed to reference the render dimensions
    context.clearRect(0, 0, renderW, renderH);

The next bit of code we’ll put in will be a new function that will be used to calculate the values for the new variables we added at the top. I set this up to accept a parameter that determines whether or not we want to maintain the aspect ratio or not. If we want to maintain the aspect ratio then we take the following steps: determine what our current width-to-height ratio is, set our gameW and gameH, set our renderW and renderH using a ratio calculation to keep it constant, set our scaleX and scaleY to the same value since the ratio is being maintained. If we don’t care about maintaining the aspect ratio then our steps are simplified (and have no “real” logic): set our gameW and gameH, set our renderW and renderH to the baseW and baseH values, calculate our scaleX and scaleY values. The scaling function will look like this:

function calculateScaling(maintainAR)
{
    // Check if we want to maintain the aspect ratio
    // this "looks better" in that items won't be stretched or squashed
    // no matter how the screensize is adjusted
    // However, if the ratio changes during the resize, then we have
    // "empty space" or offscreen items
    if (maintainAR)
    {
        // check what the screens width to height ratio is
        var wtoh = $(document).width() / $(document).height();

        // Make the canvas "fullscreen"
        gameW = $(document).width();
        gameH = $(document).height();

        // if we're greater than 1 then we are wider than we are tall
        // so we adjust our height based upon the width for proper scaling
        // otherwise we adjust our width based upon the height for proper scaling
        if (wtoh > 1)
        {
            renderW = baseW;
            renderH = Math.round(baseW / wtoh);
        }
        else
        {
            renderH = baseH;
            renderW = Math.round(baseH * wtoh);
        }      

        // Calculate what our scaling ratios need to be
        // these ratios are the same for x and y because of
        // the above logic to adjust the base compared to the fullscreen
        scaleY = scaleX = gameW / renderW;
    }
    else
    {
        // Make the canvas "fullscreen"
        gameW = $(document).width();
        gameH = $(document).height();

        // If we aren't maintaining the aspect ratio, our render dimensions
        // are always the same as our base dimensions
        renderW = baseW;
        renderH = baseH;
       
        // Calculate what our scaling ratios need to be
        scaleX = gameW / renderW;
        scaleY = gameH / renderH;
    }
}

We’re going to need to adjust $(window).load(function() to make a call to the new scaling function, but first let’s go ahead and add a checkbox to the html file so we know if we are maintaining the aspect ratio or not. These lines can go anywhere inside the <body></body> since we are absolutely positioning it:

        <div id="debugDiv" style="position:absolute;top:0;left:0;z-index:200;font-weight:bold;color:White;">
            <input type="checkbox" id="maintainAR" />Maintain Aspect Ratio
        </div>

With that out of the way we’ll dive into the $(window).load(function() changes. Prior to the call to initCanvas() we need to calculate our scaling information and immediately following the initCanvas() we’ll save out the context objects and apply our scaling to them before rendering anything to the screen. The new $(window).load(function() will look like this:

$(window).load(function()
{
    // calculate the scaling values, based upon whether the check box on the screen
    //is checked or not
    calculateScaling($("#maintainAR").is(':checked'));

    initCanvas();

    // Save out the context information
    // we do this so on resize events we can restore to undo
    // this scaling before applying the new scaling
    baseContext.save();
    context.save();    
   
    // Apply the scaling values to the context objects
    baseContext.scale(scaleX, scaleY);
    context.scale(scaleX, scaleY);
   
    initHero();
    initRocks();
    initEnemies();

    lastUpdate = Date.now();
    // call the gameLoop as fast as possible
    setInterval(gameLoop, 1);
});

At this point you should be able to run the game and see it fill the entire screen, stretching / squashing the objects as needed to do so. You’ll most likely notice that resizing the window has no effect. That’s easy enough to fix by hooking to the window resize event, but there are a couple things that could end up biting us in the patootie if we don’t watch out. We don’t want the resize event to reinitialize everything, because then our hero, enemies and rocks will all be regenerated and positioned differently on the screen. We’ll create a new function to handle resizing that will do the following: recalculate the scaling, reset our canvas width and height, clear our the context objects, restore to the saved context information, re-apply our scaling and re-render all of the objects. We will not reinitialize anything entirely because we want to keep the same objects we had prior to resizing:

function handleResize()
{
    // During our resize event we're going to change the width and height of the canvas
    // we hide them before we scale in order to get the true document size
    // We could use the window dimensions but I've been getting some weird results with that
    $("#mainCanvas").hide();
    $("#baseCanvas").hide();
    calculateScaling($("#maintainAR").is(':checked'));
    $("#mainCanvas").show();
    $("#baseCanvas").show();

    // set the width and height of the canvas
    canvas.width = gameW;
    canvas.height = gameH;

    // set the width and height of the baseCanvas
    baseCanvas.width = gameW;
    baseCanvas.height = gameH;

    // tell the baseContext we are going to use a dark green fill color
    baseContext.fillStyle = "#004400";
    // fill the entire baseContext with the color
    baseContext.fillRect(0, 0, gameW, gameH);

    context.clearRect(0, 0, gameW, gameH);
   
    // Restore the original context information
    baseContext.restore();
    context.restore();
   
    // Apply the new scaling values to the context objects
    baseContext.scale(scaleX, scaleY);
    context.scale(scaleX, scaleY);
   
    // We have to re-render all our objects now that the scaling has changed
    for (var curRock in rocks)
    {
        rocks[curRock].render();
    }
    for (var curEnemy in enemies)
    {
        enemies[curEnemy].render();
    }

    hero.render();
}

Initially, I just wired the call to this function directly into the window resize event…that ended up not being the best idea. Firefox and Chrome (maybe others) trigger the resize event constantly while the window is resizing and cause a lot of extra calculations to be done if we call the resizeHandler directly from the event. Instead I created a simple customTimeout function that we can use to simulate a “resizeComplete” event. The customTimeout will make use of a timer object and the base setTimeout function to accomplish our goals:

// Created this because chrome and firefox call the window resize
// event constantly while it is being resized, this allows us to simulate
// an "resizeComplete" type event
var customTimeout = (function()
{
    var timer = 42;

    return function(callback, wait)
    {
        clearTimeout(timer);
        timer = setTimeout(callback, wait);
    };
})();

Now to wire that up to the resize event we use a single line of jQuery

$(window).resize(function() { customTimeout(handleResize, 500); });

Voila, we have a “fullscreen” game that will adjust and resize the objects as the browser window is resized. Pressing F11 and going into fullscreen mode with your browser should cause the game to truly be fullscreen, but any resize should work as well (even making it really tiny).

Hopefully I managed to have this all make sense and didn’t do anything incredibly stupid. If I did, please don’t hesitate to let me know and I’ll update the post accordingly. Here’s what we should have at this point (I resized the window down to a fairly small size):

Simple Game - Sample Image 5

Here’s the link to the demo for this code.

October 21, 2011

JavaScript and HTML5 – Simple Game Creation (part 4)

Filed under: HTML5,JavaScript — Tags: , , , , , — Cubicle Ninja @ 8:06 pm

In part 1 I went through the basics of setting up the canvas and adding a simple sprite to it. Then part 2 got through handling keyboard controls and animating the hero character. Most recently, part 3 added some scenery objects and the basics of collision detection. We now are to a point where we can leverage the framework we’ve created to add in some bad guys to roam around the canvas. We’ll make some modifications to our heroObject in order to allow it to accommodate NPC characters as well as the player controlled hero. Because I’m all for clichés I’ll be using the same sprite we used for the hero character…only making it evil.

Evil Mage Sprite

We’ll start off with similar functionality that we used for our rock objects in part 3 and add a variable at the top for the number of bad guys we want to have on the screen at one time, an array to hold them all and a base image object to hold the sprite image.

// variable to determine how many enemies to draw into the scene
var numEnemies = 10;
// array to hold all of the enemy objects we have
var enemies = new Array();
// variable to hold our base enemy image
var baseEnemy = null;

The next part is to create an initEnemies function that will combine elements of the initRocks and the initHero functions. For each enemy we want to create we have to make sure it doesn’t collide with the hero and also that it doesn’t collide with any of the rocks that are on the scene.

function initEnemies()
{
    // Set up the base enemy Image object
    // and load in the correct image we want to use
    baseEnemy = new Image();
    baseEnemy.src = "images/Evil_Mage_Sprite.png";
    // once it has loaded into memory we loop through
    // and create a new heroObject and set the image to our base
    baseEnemy.onload = function()
    {
        for (var i = 0; i < numEnemies; i++)
        {
            // we have to make sure the enemy is not drawn on top of the hero
            // or on top of a rock
            do
            {
                // this creates the enemy which we set up to have a random
                // x and y coordinate
                enemies[i] = new heroObject();

                // check if we have a hero collision, if so we recreate the enemy
                if (enemies[i].checkCollision(hero))
                    enemies[i].collision = true;
                else
                {
                    // if it didn't collide with the hero, check to make sure it doesn't collide
                    // with any of the rocks
                    for (curRock in rocks)
                    {
                        if (enemies[i].checkCollision(rocks[curRock]))
                        {
                            // if it collides with a rock, break out of the for loop and recreate the enemy
                            enemies[i].collision = true;
                            break;
                        }
                    }
                }
            }
            while (enemies[i].collision);

            // use the baseEnemy object as our image
            enemies[i].image = baseEnemy;
            // render it to the baseContext
            enemies[i].render();
        }
    }
}

We need to make a call to the initEnemies() function inside of the $(window).load(function() after we initialize the rock objects. This takes care of the initial creation and rendering of the enemies. Since we reused the heroObject (which we should *really* rename at this point to mobileObject or something, but we’ll get to that later) the enemies are drawn on the same canvas as the hero. Keep in mind that we are erasing that entire canvas each time through the game loop so we need to make sure we redraw the enemies each time. We do that by adding a small loop after our call to hero.render(); in the game loop:

    // do a foreach type loop through the enemies
    for (curEnemy in enemies)
    {
        enemies[curEnemy].render();
    }

At this point you should be able to run the game and have a screen with pseudo-randomly placed rocks and bad guys with none of them stacking on top of the other. The next piece will be to adjust the initEnemies() to get them started moving in a random direction and also to update the game loop to properly update them on the screen each iteration through the loop. We’ll tackle setting them up moving in a random direction first, and we do that by adding a single line above the call to render inside initEnemies(). We have 4 directions we are able to move and the heroObject is controlled by the values in the keys array (37 = left, 38 = up, 39 = right, 40 = down), so we will set the keys[0] value randomly to one of the 4 valid directions.

            // use the baseEnemy object as our image
            enemies[i].image = baseEnemy;

            // set the enemy to be moving a random direction at the start
            enemies[i].keys[0] = Math.floor(Math.random() * 4) + 37;
           
            // render it to the baseContext
            enemies[i].render();

Before that change will make any difference we need to modify the game loop so that before it renders out each enemy it calls the update method for it

    // do a foreach type loop through the enemies
    for (curEnemy in enemies)
    {
        // Update the enemy based upon how long it took for the game loop
        enemies[curEnemy].update(elapsed / timerRatio);
        // draw the enemy to the screen again
        enemies[curEnemy].render();
    }

Running the game at this point you’ll notice that the enemies are basically very poorly developed roombas and will just walk in a single direction until they hit a rock and then they just repeatedly slam their heads into it(luckily, reusing the heroObject means they already have our collision detection logic without having to do anything additional). We’ll slightly upgrade their AI and allow them to turn away from a rock any time there is a collision. This is also done in the game loop after the call to the update method, and before the call to the render method.

    // do a foreach type loop through the enemies
    for (curEnemy in enemies)
    {
        // Update the enemy based upon how long it took for the game loop
        enemies[curEnemy].update(elapsed / timerRatio);

        // check if the enemy collided with a rock, if it did turn it a random direction
        if (enemies[curEnemy].collision)
            enemies[curEnemy].keys[0] = Math.floor(Math.random() * 4) + 37;
           
        // draw the enemy to the screen again
        enemies[curEnemy].render();
    }

This works well enough for the most part, but we have an issue if one of the enemies starts walking down a path that doesn’t contain any rocks…they just keep going ad nauseum. To correct that we’ll need to add a new property to the heroObject and then another test inside the game loop for the enemies (again, before the call to render). The property we’re going to add will be called “lastKeyChange” and it will be used to check how long it’s been since the enemy last changed movement directions. We can set whatever type of limit on that we want before we force a direction change. Inside the heroObject add the property underneath the this.collision = false; property

    // When was the last time we had a direction change?
    this.lastKeyChange = Date.now();

Then update the loop to make use of that property

    // do a foreach type loop through the enemies
    for (curEnemy in enemies)
    {
        // Update the enemy based upon how long it took for the game loop
        enemies[curEnemy].update(elapsed / timerRatio);

        // check if the enemy collided with a rock, if it did turn it a random direction
        if (enemies[curEnemy].collision)
        {
            enemies[curEnemy].keys[0] = Math.floor(Math.random() * 4) + 37;
            enemies[curEnemy].lastKeyChange = Date.now();
        }

        // if the enemy has gone a while without changing directions, turn it a random direction
        if (now - enemies[curEnemy].lastKeyChange > ((Math.random() * 3000) + 5000))
        {
            enemies[curEnemy].keys[0] = Math.floor(Math.random() * 4) + 37;
            enemies[curEnemy].lastKeyChange = Date.now();
        }
           
        // draw the enemy to the screen again
        enemies[curEnemy].render();
    }

At this point you should be able to run the game and have all the randomly placed rocks from before in addition to the new enemy mages wandering around on the screen bouncing off the rocks and pseudo-randomly changing directions. The next part of the series will get into allowing the hero to throw fireballs to kill the enemy mages and adding in some point tracking for the game.

Simple Game 4 - Sample Image

Here’s the link to the demo for this code.

October 17, 2011

JavaScript and HTML5 – Simple Game Creation (part 3)

Filed under: HTML5,JavaScript — Tags: , , , , , — Cubicle Ninja @ 6:31 pm

Having completed part 1 and part 2 of the tutorial you should now have a fancy green screen with a little red wizard that can wander all over the place. Now we’ll start getting into a bit more of the nitty gritty and work on adding in some scenery (for now we’ll just add some admittedly pretty pathetic looking bricks My pathetic brick) and providing some collision detection logic to make it so our hero cannot walk through the bricks that we add. Also, since the code is going to start getting a bit more complex, we’ll follow general practices and separate things out so that each function performs one main task instead of cramming it all together.

When you’re working with games in JavaScript you have to carefully weigh the cost of every action you perform in your game loop. Since we opted to clear out the entire canvas in each iteration, adding our static objects (scenery) to the mainCanvas object would force us to redraw all of the bricks each loop. Obviously, we don’t want to do that so we’re going to adjust our main HTML page to have two canvases that are stacked on top of each other (using CSS positioning and z-index). Here’s what our new HTML file looks like:

<!DOCTYPE HTML>
<html>
    <head>
        <title>JavaScript and HTML5 - Simple Game Creation:  Part 1</title>  
        <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
        <script type="text/javascript" src="scripts/jquery-1.6.4.min.js"></script>
        <script type="text/javascript" src="scripts/SimpleGame_3.js?ver=10_16_2011"></script>
        <style type="text/css">
            #canvasWrapper
            {
                position:relative;
            }
            #canvasWrapper canvas
            {
                position:absolute;
                top:0;
                left:0;
            }
            #baseCanvas
            {
                z-index:1;
            }
            #mainCanvas
            {
                z-index:2;
            }
        </style>
    </head>
    <body style="margin:0;">
        <div id="canvasWrapper">
            <canvas id="baseCanvas">Your browser does not support HTML5</canvas>
            <canvas id="mainCanvas">Your browser does not support HTML5</canvas>
        </div>
    </body>
</html>

Nothing really fancy here, we added a new canvas with an id of “baseCanvas” and then wrapped both canvas elements in a div wrapper. The style tag handles adjusting the CSS on the canvas elements so they are stacked on top of each other with the baseCanvas (the one where we will draw our static scenery) on the bottom of the stack. The clever among you probably already realized that we’re going to have to make changes to our gameLoop even though we’ve stacked our canvases this way. We no longer are going to want to fill the mainCanvas with the green color because that would hide all of the scenery on the base canvas. Instead we are going to adjust our gameLoop code so that it just completely empties the mainCanvas each iteration through the gameLoop.

We’ll change this

    // tell the context we are going to use a dark green fill color
    context.fillStyle = "#004400";
    // fill the entire canvas with the color
    context.fillRect(0, 0, gameW, gameH);

to this

    // clear the entire canvas
    context.clearRect(0, 0, gameW, gameH);

We’ll also need to adjust how we initially set up our canvas object in the $(window).load(function() so we’ll use this opportunity to pull things out into a separate function. Above the $(window).load(function() add a new function definition named “initCanvas” and then delete all of the canvas and context references from the $(window).load(function(). Before we fill out the initCanvas function we need to add in two new variables at the top of our JavaScript file so we can access our baseCanvas and the context related to it

var baseCanvas;
var baseContext;

Once those are in place, here’s the code for the new initCanvas function:

function initCanvas()
{
    // retrieve a reference to the canvas object
    canvas = document.getElementById("mainCanvas");
    // create a context object from our canvas
    context = canvas.getContext("2d");

    // retrieve a reference to the base canvas object
    baseCanvas = document.getElementById("baseCanvas");
    // create a context object from our baseCanvas
    baseContext = baseCanvas.getContext("2d");

    // set the width and height of the canvas
    canvas.width = gameW;
    canvas.height = gameH;

    // set the width and height of the baseCanvas
    baseCanvas.width = gameW;
    baseCanvas.height = gameH;

    // we no longer fill the main canvas with anything, we just let the
    // base canvas show through
    // tell the baseContext we are going to use a dark green fill color
    baseContext.fillStyle = "#004400";
    // fill the entire baseContext with the color
    baseContext.fillRect(0, 0, gameW, gameH);
}

You can see we are getting our references set up for the baseCanvas and the context related to it and filling it with our dark green color. Since the baseCanvas is now going to contain the background and the scenery we no longer have to worry about filling our the mainCanvas other than setting its width and height. Running the code at this point should give you a result that looks exactly the same as what we had at the end of part 2.

While we’re on the cleaning up code kick, let’s go ahead and move the logic that instantiated our hero out of the $(window).load(function() and put it into an initHero function. The new initHero function and the resulting $(window).load(function() should look like the following:

function initHero()
{
    // instantiate a heroObject
    hero = new heroObject();
    // set it's image to the proper src URL
    hero.image.src = "images/Mage_Sprite.png";
    // once the image has completed loading, render it to the screen
    hero.image.onload = function()
    {
        hero.render();
    };
}

$(window).load(function()
{
    initCanvas();
    initHero();

    lastUpdate = Date.now();
    // call the gameLoop as fast as possible
    setInterval(gameLoop, 1);
});

There we go, the annoying organization bits are out of the way we are free to get started on the actual new functionality. We need to add a few variables to the top of the JavaScript file to accommodate the changes we are going to make: we want a variable to tell the game how many rocks to load into the scene, an array to hold all of the rock information and, since we’re going to be reusing the same image object multiple times, a base image object for the rock.

// variable to determine how many rocks to draw into the scene
var numRocks = 50;
// array to hold all of the rock objects we have
var rocks = new Array();
// variable to hold our base rock image
var baseRock = null;

Before we start working with those variables let’s define what our rock object actually will be. We’ll do this in a very similar fashion to how we did our heroObject except much more simply since the rocks don’t need to move/update. Because this same object could be used for any static object you add to your game we’ll be calling it “staticObject” (I know, I’m very clever). We need to know the width and height of the object, its x and y coordinates, the image to use for the object and then have the ability to render it to the baseContext.

function staticObject()
{
    // the width and height of the sprites for our static objects
    // I'm using 32x32 as the default grid size
    this.width = 32;
    this.height = 32;
    // Place it at a random spot on the screen to start
    this.x = this.width * Math.floor(Math.random() * ((gameW - this.width * 2) / this.width)) + this.width;
    this.y = this.height * Math.floor(Math.random() * ((gameH - this.height * 2) / this.height)) + this.height;
    // What image are we using for the object
    this.image;

    this.render = function()
    {
        // drawImage takes for parameters:
        //      the image to draw
        //      the x and y coordinates to use from the source image
        //      the width and height to use from the source image
        //      the x and y coordinates to draw it to on the canvas
        //      the width and height to draw it into on the canvas
        baseContext.drawImage(this.image, 0, 0, this.width, this.height, this.x, this.y, this.width, this.height);
    };
};

You can see that it is pretty much a copy and paste of the code we used for our heroObject except we modified the render to draw on the baseContext instead of the mainContext. We don’t need to worry about an update method for this object since they won’t be moving around on the screen or getting updated in anyway after they are initially rendered. As we did for our hero and our canvas we’re going to create a new initRocks function that will be a close cousin to the initHero function. We’ll load the image for our brick into the base and then use that to render each of the rocks we want in our scene:

function initRocks()
{
    // Set up the base rock Image object
    // and load in the correct image we want to use
    baseRock = new Image();
    baseRock.src = "images/SimpleBrick.png";
    // once it has loaded into memory we loop through
    // and create a new staticObject and set the image to our base
    baseRock.onload = function()
    {
        for (var i = 0; i < numRocks; i++)
        {
            // this creates the rock which we set up to have a random
            // x and y coordinate
            rocks[i] = new staticObject();
            // use the baseRock object as our image
            rocks[i].image = baseRock;
            // render it to the baseContext
            rocks[i].render();
        }
    };
}

The only thing left to do after that is to add a call to the initRocks function inside of our $(window).load(function() which leaves us with:

$(window).load(function()
{
    initCanvas();
    initHero();
    initRocks();

    lastUpdate = Date.now();
    // call the gameLoop as fast as possible
    setInterval(gameLoop, 1);
});

Running the game at this point will give you a pseudo-random layout of rocks on the game grid with the hero object on top of them able to walk around. Each time you refresh the screen you’ll receive a different pattern for the rocks (sometimes with the hero appearing on top of one). Assuming you’ve made it this far, the next step (and biggest leap forward for our game) is to add in some basic collision detection between the heroObject and the staticObjects. To that end we’ll first add a new property to both the heroObject and the staticObject that is simply a boolean letting us know if we have a collision or not

    // Do we have a collision event?
    this.collision = false;

The next step is to create the method in the heroObject that will check for a collision with any of the static objects that we have loaded into the scene. Once we have the method in place we’ll call it at the bottom of our update method and, if we have a collision, we’ll undo the movement that we would have made. The reason we do this after we calculate the movement for the update is to make sure we catch the collision event prior to drawing the hero inside of the object. If we did it at the top of the update method then the hero would be allowed to take one step inside of a rock and would then be stuck there and unable to move any direction. The collision method is fairly straightforward and just takes a parameter of another object and tests to see if the hero’s x and y coordinates are inside the object:

    this.checkCollision = function(obj)
    {
        // check to see if our x coordinate is inside the object and
        // our y coordinate is also inside the object
        if ((this.x < (obj.x + obj.width) && Math.floor(this.x + this.width) > obj.x)
            && (this.y < (obj.y + obj.height) && Math.floor(this.y + this.height) > obj.y))
        {
            return true;
        }
    };

In order for our update method to be able to “roll back” a movement we’ll need to store out the current x and y coordinates for the hero (and also reset its collision property. At the top of the update method add the following lines:

        // store out the current x and y coordinates
        var prevX = this.x;
        var prevY = this.y;
        // reset the collision property
        this.collision = false;

And now, the pièce de résistance, we add a loop at the bottom of our update method (this check is the very last thing, it goes after your check for looping around and / or blocking movement at the edge of the screen) to iterate through all of the rocks in the array and check to see if we have a collision. If we find a collision event then we reset the x and y coordinates of the hero:

        // loop through all of the rocks in the array
        // we use an for-in loop to go through the rocks in case
        // we later add some logic that can destroy static objects
        // a regular for loop could break with null values if that happens
        for (iter in rocks)
        {
            // if we already have a collision there's no need to continue
            // checking the other rocks
            if (this.collision)
            {
                break;
            }
            else
            {
                // check to see if we have a collision event with the
                // current rock
                if (this.checkCollision(rocks[iter]))
                {
                    // reset our x and y coordinates and set our collision property to true
                    this.x = prevX;
                    this.y = prevY;
                    this.collision = true;
                }
            }
        }

Running the code at this point should provide you with something that is actually starting to resemble an old school sprite-based RPG. You have your little keyboard controlled hero, a “maze” of rocks and some collision detection. Before we wrap up part 3, we’re going to make one more adjustment to the code. As I mentioned above there’s nothing in place to prevent the hero from loading “inside” one of the rocks which would make that game start from an unplayable state since the collision would instantly be true and the hero could never move. To prevent that from happening we’ll modify our initRocks function so that it checks to see if the rock is “under” the hero object and, if so, will re-generate the x and y coordinates for it. The new initRocks function looks like this:

function initRocks()
{
    // Set up the base rock Image object
    // and load in the correct image we want to use
    baseRock = new Image();
    baseRock.src = "images/SimpleBrick.png";
    // once it has loaded into memory we loop through
    // and create a new staticObject and set the image to our base
    baseRock.onload = function()
    {
        for (var i = 0; i < numRocks; i++)
        {
            // this creates the rock which we set up to have a random
            // x and y coordinate
            rocks[i] = new staticObject();

            // check to see if we have a collision between this rock and
            // the hero object, if so we generate new coordinates for the rock
            while (hero.checkCollision(rocks[i]))
            {
                rocks[i].x = this.width * Math.floor(Math.random() * ((gameW - this.width * 2) / this.width)) + this.width;
                rocks[i].y = this.height * Math.floor(Math.random() * ((gameH - this.height * 2) / this.height)) + this.height;
            }

            // use the baseRock object as our image
            rocks[i].image = baseRock;
            // render it to the baseContext
            rocks[i].render();
        }
    };
}

Coming up in part 4 of the series will be adding in some “Bad Guys” that will wander around the maze causing all sorts of havoc (actually they won’t do anything other than randomly wander around the maze and use our collision detection).

Simple Game 3 - Sample Image

Here’s the link to the demo for this code.

Older Posts »

Powered by WordPress