Palagpat Coding

Fun with JavaScript, game theory, and the occasional outbreak of seriousness

Monday, August 23, 2010

Cloning Zelda: Candles, or the Magic of dojo.connect

The versatile blue candle

It's been a while since I've worked on Canvassa, my HTML5 love letter to the original 8-bit NES Legend of Zelda. If you remember last time, we worked out some kinks in the code that were causing it to crash in Google Chrome. Today, it's back to implementing missing functionality: namely, how to make the blue candle behave appropriately. Fortunately, Dojo provides us a way to do this, and it's actually a pretty cool feature of the framework. Read on to see how it works.

Read more ยป

Labels: , , ,

Thursday, December 24, 2009

Cloning Zelda: Harmful Changes (and a Christmas Miracle)

Yesterday's post, which I called "Harmless Animations", led to me making a stupid – and harmful – change that I should have known better. It's worth talking about what happened, and why.

When I first implemented the loc.Explod class and the four SwordFlash elements, this is what the code that added them to the game screen looked like:

terminate: function swordProj_terminate() {
    game.insertProjectile(new loc.SwordFlashNW({'pos':this.getPos(),'owner':this.owner}));
    game.insertProjectile(new loc.SwordFlashSW({'pos':this.getPos(),'owner':this.owner}));
    game.insertProjectile(new loc.SwordFlashNE({'pos':this.getPos(),'owner':this.owner}));
    game.insertProjectile(new loc.SwordFlashSE({'pos':this.getPos(),'owner':this.owner}));

    this.inherited(arguments);
}

When I was blogging about the new code yesterday, I "streamlined" that function a bit to look like this:

terminate: function swordProj_terminate() {
    var myPos = this.getPos();
    game.insertProjectile(new loc.SwordFlashNW({'pos':myPos,'owner':this.owner}));
    game.insertProjectile(new loc.SwordFlashSW({'pos':myPos,'owner':this.owner}));
    game.insertProjectile(new loc.SwordFlashNE({'pos':myPos,'owner':this.owner}));
    game.insertProjectile(new loc.SwordFlashSE({'pos':myPos,'owner':this.owner}));

    this.inherited(arguments);
}

Do you see what I broke? It's probably not at all obvious... it certainly wasn't to me. I thought that I was optimizing the code by only running getPos() once and passing its value to all four child explods. But last night, I went to change the live code to reflect the blog post, in case anyone wanted to examine the new bits in context. When I did so, my sword flashes stopped behaving right! If the sword hit a monster, all four explods sat still in the exact spot where they were inserted, and if the sword made it to the end of the screen, two of them would come straight back, rather than at their proper angles. What the heck did I do?!?

I really should have known better, since this has tripped me up before. To explain what the difference is in these two code blocks, you need to know what the getPos() method (which is inherited from loc.Sprite) actually does:

getPos: function sprite_pos() {
    return dojo.clone(this.pos);
}

dojo.clone() is a cool little function. In a nutshell, it creates deep copies of JavaScript objects, which is tremendously useful if you want something approximating a struct datatype. This is exactly how I'm doing (x,y) pairs in Canvassa, which I use for both sprite position and velocity:

dojo.declare("loc.Sprite", null, {
    pos: {x:0, y:0},
    vector: {x:0,y:0},
...
})

The problem comes when you try to reuse one of these position values, which is precisely what the second code block tried to do, and is precisely why I created the getPos() method in the first place! Since a sprite's position is an object and not a struct, I need to dojo.clone() it to get a copy of its contained values instead of a copy of its pointer reference (see Jonathan Snook's explanation of why JavaScript works this way). Now, I'm not a n00b programmer. I've been around the block a few times, and have coded in nearly a dozen different languages in my time, and in many of them I was well aware of this Pass-by-Reference/Pass-by-Value distinction. And yet, in an effort to clean up my code, I managed to instantiate four separate SwordFlash explods and give all four of them the same position object! So when the game told them to update their positions, each in turn would modify its position object relative to its personal velocity:

// let's assume the starting position is {x:32, y:64}

// in SwordFlashNW.updatePosition():
this.pos.x += -3;  // {x:29, y:64}
this.pos.y += -3;  // {x:29, y:61}

// SwordFlashSW.updatePosition():
this.pos.x += -3;  // {x:26, y:61}
this.pos.y += 3;  // {x:26, y:64}

// SwordFlashNE.updatePosition():
this.pos.x += 3;  // {x:29, y:64}
this.pos.y += -3;  // {x:29, y:61}

// SwordFlashSE.updatePosition():
this.pos.x += 3;  // {x:32, y:61}
this.pos.y += 3;  // {x:32, y:64}

// end position for all four: {x:32, y:64}

So when all four explods were operating on a single, shared position object, the net result was no movement at all! And when, in the edge case, two of the explods went off the screen and were removed, the other two combined forces to push the position in a single direction. D'OH!

So, hopefully this time I've learned the lesson well enough that it'll sink in: my sprites' pos and vector values are objects, and I need to call getPos() EVERY time I want a non-interfering copy. Sure, it's not as profound a December lesson as "I will honour Christmas in my heart, and try to keep it all the year," but it'll do.

Happy Christmas to all, and to all a good night!

Labels: , , ,

Wednesday, December 23, 2009

Cloning Zelda: Harmless Animations

flashing shards fly off an octoroc when Link kills it with his sword

So.

Long time no update, eh? (at least, it's been a while since a substantial update)

A few weeks ago, I added something new and kinda cool to Canvassa. We've had sword projectiles for a while now, but it felt like something was missing... in the original game, when a thrown sword hits an enemy or a wall, you get this cool effect that the above screenshot shows off: a handful of flashing shards that fly off in 4 different directions. I'd been missing these, and wanted to add them to Canvassa, but I was afraid I had a "painted into a corner" situation on my hands: the game engine I'd built up so far had a couple of different types of sprites it handles, and the sword shards don't really fit any of them:

  • Player
    • used for: player main sprites
    • movement: user-controlled
    • attributes: can be hurt if touched by a monster or projectile
  • Monsters
    • used for: octorocs, tectites, etc.
    • movement: autonomous (via AI)
    • attributes: can be hurt if hit by player's weapon or projectile
  • Items
    • used for: small hearts, rupees, etc.
    • movement: none (except for fairies)
    • attributes: can be picked up if touched
  • Projectiles
    • used for: throwing sword, octoroc rocks, etc.
    • movement: pre-determined
    • attributes: can do damage if it touches a player or monster

Sword shards don't neatly fit into any of these boxes: they aren't controlled by the player or an AI, and while they do move in a predetermined way and can't be picked up (like projectiles), they don't hit obstacles or do damage to anything the way projectiles do.

As I mulled over the problem of how to implement these shards, inspiration struck: the MUGEN Explod construct! For the past several years I've developed something of a name for myself as a programmer (and occasional graphic artist) of custom characters for the open MUGEN fighting game engine. In my time working with that programming model, I've become familiar with its 4 tiers of sprite object hierarchy:

  • Player
    • used for: the character's core sprites
    • movement: user-controlled
    • attributes: can do damage and be damaged
  • Helper
    • used for: extensions of the character such as weapons, clones, etc.
    • movement: user-controlled or autonomous
    • attributes: can do damage and be damaged
  • Projectile
    • used for: things thrown/shot/expelled by the player
    • movement: pre-determined
    • attributes: can do damage and be blocked (but not hurt, per se)
  • Explod (no, I didn't misspell it)
    • used for: used for visual effects such as dust or hit sparks
    • movement: pre-determined
    • attributes: cannot do damage or be damaged

The above 4-way distinction between MUGEN sprite types seems to map pretty well to the engine I'm building, and it's a metaphor I'm familiar with, so I determined to apply it. For my purposes, Explods act almost exactly like Projectiles, except for two things: they don't do damage to anyone (or anything), and they have a time limit, after which they get removed from the screen. So the simplest thing to do is to subclass the loc.Projectile class and override the default behavior for these two differences:

dojo.declare("loc.Explod", loc.Projectile, {
    timeout: -1,
    constructor: function(args){
        /* required args: 'pos' and 'owner' (inherited from Projectile)
           optional args: 'timeout' (defaults to -1, meaning it never gets removed) and 'vel' (movement velocity) */
        dojo.mixin(this, args);
    },
    hit: function explod_hit() {
        // do nothing when making contact with another sprite
    },
    updatePosition: function() {
        if (this.timeout-- == 0) {
            this.terminate();   // when timeout runs down, tell the game to remove me
        }
        this.inherited(arguments);
    },
    _animateCurrent: function explod_animateCurrent() {
        return true;  // override default behavior to animate even if motionless
    }
});
View the code

Simple, right? Now, when I need an Explod-type effect, I simply subclass loc.Explod and add the necessary animation details. For the shattering sword, I defined four SwordFlash subclasses, one for each of the four shards created when a SwordProj projectile is terminated (NW, SW, NE, and SE, for the direction they move). In each, I specified the initial velocity and timeout values to be used (though I could also specify these during object instantiation, if I wanted to override the defaults):

dojo.declare("loc.SwordFlashNW", loc.Explod, {
    constructor: function(args){
        dojo.mixin(this, args);
        this.width = 8; this.height = 10;
        this.vel = {x: -3, y: -3}; this.timeout = 10;
        this._stateDefs = [ { faceted:false, nextState: 0, canMove: true,
            anim: [ [{x:152,y:11,t:1},{x:160,y:11,t:1}] ] }];
    }
});
View the code

With these four classes now defined, all I had to do was add a custom terminate() function to the loc.SwordProj "throwing sword" class, using it to insert a new instance of each explod into game.projectiles:

terminate: function swordProj_terminate() {
    var myPos = this.getPos();
    game.insertProjectile(new loc.SwordFlashNW({'pos':myPos,'owner':this.owner}));
    game.insertProjectile(new loc.SwordFlashSW({'pos':myPos,'owner':this.owner}));
    game.insertProjectile(new loc.SwordFlashNE({'pos':myPos,'owner':this.owner}));
    game.insertProjectile(new loc.SwordFlashSE({'pos':myPos,'owner':this.owner}));

    this.inherited(arguments);
}
View the code

You can see the results here.

If you clicked the above link and tested out the new feature, one thing you likely noticed is that the sword only works if you have full health; get touched by a monster, and suddenly you can't attack anymore... lame! Next time, we'll look at how the MUGEN Helper construct can help us fix this problem.

Labels: , , , ,

Friday, November 06, 2009

Javascript Snippet: Flip Text Upside-down

This week I was playing with my Google Analytics account to see what was happening with my blogs; it turns out that my MUGEN blog is about a bajillion times more popular than this one. Guess I'm learning, as Arthur Conan Doyle did, that you don't get to pick what you're known for (sigh). That said, the most popular post here on the ol' Coding blog was this one, where I talked about my home-grown tag cloud generator. So, although Canvassa isn't going to be dropped, I'm going to pepper in more of these "snippets" types of articles from time to time... like today!

Back in April of this year, YouTube did an April Fool's joke where they turned their site upside-down by means of a sneaky little code snippet that swapped out all alphanumeric characters with characters that looked like upside-down versions of themselves (for example, 6 became 9, M became W, etc). Later, Paul Irish adapted the code into a jQuery plugin. Still later, I followed his lead and adapted the code as a Dojo plugin, and posted it on Twitter. But I never blogged about it here.

In fact, it's even been in one of my Github repositories for months now, completely undocumented save its internal code comments. Surprise!

Click here to flip all the posts on this page, re-arranging them from top to bottom, or click here to flip their text in place without moving it around.

I wanted to go back re-implement the code as a bookmarklet, but it's currently dependent on (and namespaced in) Dojo, so that didn't get done. Maybe for the next snippet.

Oh, also of note: I've re-arranged my site template a bit, moving navigation from the sidebar to the header, adding more social networking links to my profile, and pushing it further down the sidebar. This is part of my effort to harmonize all three of my blogs into a single site, and there will likely be more changes before that effort is complete.

Update, 11/30/2009: Some recent template changes to the blog have broken the dynamically-loaded code in this post; I'm working on a fix.

Labels: , ,

Wednesday, September 16, 2009

Cloning Zelda: With a Little Help from my Friends...

My Twitter friend Peter Higgins, who I've mentioned before, has been following my progress on Canvassa for the past several months, and last week he sent me a brief message, asking if I'd be okay with him forking the code on Github to re-arrange the overall structure of the app and make it more Dojo-y. I said sure, because anytime you've got someone with his programming chops offering to partner with you to help make your code better, you don't say no!

So anyway, a few days later, Peter sent me a Github pull request to take a look at his branch, and yes, it's quite different. So different, in fact, that I haven't yet figured out yet how to integrate his changes back into the trunk! But a couple of things jumped out at me:

  • He moved all of the namespaced code (basically all of my Dojo classes) into a subfolder off the main folder, so now you have /loc/Canvassa/index.html for the main page, but the game classes all live in /loc/Canvassa/loc.
  • He split up the Monster classes into individual .js files, and put them in a subfolder under the namespaced folder (/loc/Canvassa/loc/Monster)

In the short term, this has resulted in a LOT more http requests when the game is initially loaded. Long-term, though, it'll help since you only have to download the monster classes that have actually changed instead of re-grabbing the entire code glob when I tweak something about, say, the Octorok's behavior. And when the long-awaited day arrives that I put the "it's done" seal on this project, I can use Dojo ShrinkSafe to merge and minify all of the code into a single package, eliminating the multiple-http-requests problem.

For the time being, I've put my working copy of Peter's branch on my site here. It's slightly more functional than the version in my Part 5 working copy, including updates to medicine (it works), bait (it kinda-sorta works, but doesn't affect enemy AI yet), and the bombs and candle (they both more or less work now, but don't destroy scenery yet). But in the process of those updates, I somehow managed to break the boomerang, so I need to fix that before I attempt to merge it back into the trunk and blog about what I did to implement the new item behaviors. Hopefully that'll be a simple fix; I suspect I just broke something minor in the loc.Item base class.

Labels: , , ,

Wednesday, September 09, 2009

Cloning Zelda: Back-to-School update

After taking a sanity break from my JavaScript/HTML5/Canvas Zelda clone I've been making for the past several months, I've started banging on it again, but haven't been able to muster the energy to actually blog about it, perhaps because the things I've been doing aren't all that technically interesting, just important to do (case in point: I've had the bow & arrow working for a while, but until yesterday's update, it wasn't tied to your rupee count as it was supposed to be). As of this morning, the sword, boomerang, bow, and magic wand are all working more or less as they should be (you can't throw the boomerang diagonally yet, and the wand produces magic waves without you actually waving it, but those are both minor quibbles like the arrow-rupee thing), and I've implemented a cheat code to grant you all items so you can actually, y'know, test them (hint: it uses the Konami Code, or you can just mash the "ALL ITEMS" button down in the corner of the screen)

A couple of the remaining items, however, promise to be interesting: bombs and candles both behave differently than standard projectiles, and the flute, bait, and letter aren't projectiles at all (well, I suppose you could think of the bait as a projectile in the same way that the bomb is, but still... it doesn't actually kill enemies, just attract them). Each of these has the promise of interesting blog fodder:

  • bomb: drop a bomb, and after a moment it spawns multiple "explosion" artifacts
  • candle: spawns a flame projectile, but it only goes a limited distance and not clear across the screen like the others
  • flute: spawns a whirlwind object that picks up the player and warps them to the location of the dungeons
  • bait: like the bomb, since you just drop it instead of throwing it -- but it doesn't explode or kill the enemies, it just alters their AI behavior
  • letter: causes the old lady NPC to sell you medicine
  • medicine: refills your health and then self-destructs

So as I implement these remaining items, I may jot some notes about why each is interesting to implement, and then I'll have something worth talking about here.

Meanwhile, go see the stuff that's already done here

Labels: , , , ,

Monday, June 22, 2009

Simple tag cloud generator in JavaScript

After having to roll my own tag cloud generator for my blogs (I'm old-school, so I don't have access to a blog layout engine with prebuilt widgets), I thought I'd clean up the code and share it. So, it's in this new repository I just created on GitHub.

Usage is pretty simple; copy the JS (and optional CSS) to your server, and link to them from your blog code like so:

  <style type="text/css">
    @import "http://www.myblog.com/css/tagCloud.css";
  </style>
  <script type="text/javascript" src="http://www.myblog.com/js/tagCloud.js"></script>

Once you've got it loaded, there are only two steps to create a new tag cloud: first, you need to populate a JavaScript object with the tag information structured like an associative array, with the tag names as properties and the instance counts for each tag as its hash value. Ideally this will be retrieved via an AJAX/XHR request, but in a pinch you can hard-code it (but that's not terribly useful, since you'd have to constantly update it whenever you post something new):

    // hard-coded, pure JavaScript version:
    tags = {
      "JavaScript":17,
      "Conferences":2,
      ".NET":1,
      "GeoWeb":1,
      "Site news":1,
      "snippets":1,
      "Dojo":16
    };
    function init_tagCloud(parentId) {
      var parentDiv = document.getElementById(parentId);
      if (parentDiv) {
        var cloud = makeCloud(tags, "http://www.myblog.com/labels/", 0,1,4,' ');
        parentDiv.appendChild( cloud );
      }
    }
    window.onload = function() {
      init_tagCloud('target_div');
    }

    // or, Dojo-powered and XHR-populated (substitute JS lib of your choice):
    dojo.addOnLoad(function() {
      dojo.xhrGet({
        url: "getBlogCategories.php",
        handleAs: "json",
        load: function(data){
          tags = data;
          var cloud = makeCloud(tags, "http://www.myblog.com/labels/", 0,1,4,' ');
          dojo.byId('target_div').appendChild( cloud );
        }
      });
    });

The parameters on the makeCloud() method are:

  • tags: the object hash mentioned above
  • baseUrl: the URL that should serve as the base for all tag links
  • minCount: minimum number of times tag must be used to show up in the cloud (defaults to 0)
  • minSize: minimum font size for tags in the cloud (defaults to 1em)
  • max: maximum font size for tags in the cloud (defaults to 4.5em)
  • delim: delimiter(s) to insert between tags in the cloud (defaults to 1 space)
  • shuffle: boolean value that controls if the tags get shuffled in random order, or sorted (defaults to false)

That's about all there is to say about it. The basic math for determining the tag size is taken almost verbatim from the Wikipedia page on tag clouds, just slightly tweaked to allow for a minimum font size. Surely there are other customizations that could be made, and ideally this would be implemented as a Dojo/plugd-style plugin. I'll leave that for an exercise at a later date.

--Edit: I inadvertently forgot to strip out the <script> tags from the sample code blocks up above, and they were getting run! D'oh!

Labels: , ,

Tuesday, June 09, 2009

Cloning Zelda, part 5D: Scaling the Math Wall

Previous posts in this series: Part 1 | Part 2 | Part 3A | Part 3B | Part 4A | Part 4B | Part 4C | Part 5A | Part 5B | Part 5C

I blogged last week about the strides I've made with refactoring the various monster classes into the new sprite engine, and related how I got stuck, for the second time, trying to make the boomerang work right (I'd had it mostly implemented a while ago when I first decided to do the sprite engine refactoring, in large part because it didn't animate well in the old engine). Well, a few afternoons later, I knuckled down while on my lunch break and got it working. (fair warning for the math-averse: the rest of this post is going to be pretty trigonometry-heavy)

First, I owe a huge debt to a couple of sites that helped refresh my memory of vector geometry. I recalled the old "rise over run" rule for defining the slope of a vector or line, and I knew that the Pythagorean Theorem (a2 + b2 = c2) could be used to figure out the length of a vector c, given its X and Y components a and b. The rest took some memory-jogging: lbackstrom's tutorial at TopCoder.com was a good refresher, and the Slopes and Lines overview at WhySlopes.com filled in the rest (good thing, too, because my daughter is going to need me to help her with her geometry homework before I know it...)

Anyway, the basic math of the boomerang problem works like this: when thrown, a boomerang has an initial vector that determines its movement in the X and Y directions. When it reaches its apogee or hits the edge of the screen, it then reverses course, homing in on its thrower. In order to do this, the boomerang version of the Projectile class had to override the base class's updatePosition() function, which gets called from the game main loop:

main: function game_main() {
...
  // move all active projectiles
  for (var j in this.items) {
      if ("updatePosition" in this.items[j]) { this.items[j].updatePosition(); }
  }
...
}

(Note that we first have to check to see if each item in the game's item list actually has the updatePosition() function, because non-projectile items, like dropped hearts and rupees, won't.) The boomerang projectile's updatePosition() function looks like this:

updatePosition: function boomProj_updatePosition() {
    // move me to the next point on my current trajectory
    this.pos.x += this.vel.x;
    this.pos.y += this.vel.y;

    // check to see if I've gone off the edge of the screen
    var offscreen = true;
    if (this.vel.x < 0) {
        // moving left; check left edge
        offscreen &= (this.pos.x <= game.constants.screenBound.left);
    } else if (this.vel.x > 0) {
        // moving right; check right edge
        offscreen &= (this.pos.x >= game.constants.screenBound.right);
    }
    if (this.vel.y < 0) {
        // moving up; check top edge
        offscreen &= (this.pos.y <= game.constants.screenBound.top);
    } else if (this.vel.y > 0) {
        // moving down; check bottom edge
        offscreen &= (this.pos.y >= game.constants.screenBound.bottom);
    }

    // calculate distance from my owner (set in the constructor via dojo.mixin() args)
    var dx = this.owner.pos.x - this.pos.x;
    var dy = this.owner.pos.y - this.pos.y;
    var distance = Math.sqrt(dx*dx + dy*dy); // thanks, Pythagorus!
    if (this._returning) {
        // if we're already returning, check to see if we're close enough to our owner to be caught
        if (distance <= 8) {
            this.owner.catchItem();
            this.terminate();
        }
    } else if (offscreen || distance >= this.apogee) {
        this._returning = true;
    }

    // calculate return velocity, if we're on the return path
    if (this._returning) {
        // change my vector to match the direction from me to my owner; valid values are -1, 0, or 1
        this.vector = {x: (dx) ? dx/Math.abs(dx) : 0, y: (dy) ? dy/Math.abs(dy) : 0};

        if (dx && dy) {
            // vector is on a diagonal; I need to calculate both x and y components of velocity
            var slope = dy / dx;
            this.vel.x = Math.sqrt((this.speed*this.speed) / (slope*slope + 1)) * this.vector.x;
            this.vel.y = this.vel.x * slope;
        } else if (dx) {
            // dx only: horizontal vector
            this.vel = {x: this.speed * this.vector.x, y: 0};
        } else {
            // dy only: vertical vector
            this.vel = {x: 0, y: this.speed * this.vector.y};
        }
    }
}

A boomerang's return velocity is easy when it's strictly horizontal or vertical: the full measure of speed is devoted to either X or Y, respectively. The complicated part is when the boomerang is on a diagonal offset from its owner; in that case, the speed needs to be divided into X and Y components, in a ratio that matches that of the X and Y portions of the difference between the boomerang's current position, and that of the player (typically referred to as Δx and Δy, but which I called dx and dy in the code):

Link and the boomerang on a 2-d plane

Subtracting the player's position from the boomerang's gives us the values for length of sides Δx and Δy: in this case, 30 and 43, respectively (note: the image is enlarged for clarity). The slope of the boomerang's velocity vector can be found by dividing Δx into Δy (Rise over run, remember?). That gives us:

slope = Δy / Δx = 30 / 43 ≈ 0.698

Now comes the tricky part: we know the total velocity of the boomerang, Veltotal, as it needs to be equal to the predetermined speed for boomerangs: 3 pixels per frame. But, we don't know either the X or Y components of the velocity, which is what we need in order to do the right thing in updatePosition(). Dredging up an old trick I learned in high school algebra, I remembered that if you have two unknown variables, you can solve them both if you can find two interrelated equations. Fortunately, we have them: the Pythagorean theorem, and the slope ratio. We'll figure out the x-component of the velocity Velx first, because that will make the calculation of the y-component Vely easy. Solving the Pythagorean Theorem for Velx, we get:

Veltotal2 = Velx2 + Vely2
         = Velx2 + (Velx * slope)2 -- (substituting in the slope formula: Vely = Velx * slope)
         = Velx2 + (Velx2 * slope2)
         = Velx2 * (1 + slope2)
Velx2 = Veltotal2 / (1 + slope2)
Velx = √ Veltotal2 / (1 + slope2)
    = √ 32 / (1 + 0.6982)
    = √ 9 / (1 + 0.487)
    = √ 6.052 
    = 2.460

Then, solving for Vely:

Vely = Velx * slope
    = 2.460 * 0.698
    = 1.717

So in this instance, the X and Y components of the boomerang's velocity should be set to 2.46 and 1.71708. Now that we've worked it out, these calculations can be done every time the game calls updatePosition(), and in all but the most extreme cases (i.e. 20 Goriyas in a dungeon room, all throwing boomerangs at the same time) this won't cause any measurable slowdown.

You can see the results for yourself, by loading up the latest iteration of the game, here. The arrow keys control the player's movement, and the Z and X keys throw the boomerang and sword, but the map and inventory screens aren't converted to the new codebase yet, so that's the next order of business. I've also added some preliminary code that should take care of preloading all necessary resources, but it's still a bit buggy, so if nothing happens when you first hit the page, hit refresh and try again. Of course you can also go to the Bestiary if you prefer, and see for yourself how the Goriyas can handle their boomerangs. Collision detection still isn't turned on yet, but I fully expect to have that for the next update.

Labels: , , ,

Tuesday, May 05, 2009

Cloning Zelda, part 5: Redefining the Sprite classes

Previous posts in this series: Part 1 | Part 2 | Part 3A | Part 3B | Part 4A | Part 4B | Part 4C

Too long since my last post. Moving forward, I'm going to establish a schedule for blog updates, and will work to maintain it. More on that soon.

As I've mentioned previously, my sprite classes had become something of a house of cards, which only held together in Firefox and collapsed in every other browser. Clearly, not what I was shooting for.

I also blogged last time that I got the chance to speak with Peter Higgins, the project lead for the Dojo Toolkit, at the JSConf developers' conference a few weekends ago. With his help, I've finally managed to wrap my head around how the Dojo class loader works, and have used my newfound knowledge to rebuild the sprite engine for Canvassa. (whew! that's a lot of links!)

Bottom line, for the short-attention-span crowd: click here to view the new sprite test harness, which shows off a couple of the new engine's features:

  • True class inheritance and dependency resolution (Player and Monster classes are defined in separate files from the base Sprite class)
  • Animation state and timing "cooked in" to the base classes (press the number keys to see the animations each sprite class defines)
  • Selective property overriding in derived classes (note the different walking speeds of the sprites)
  • Should be much more cross-browser compatible (Safari/Chrome/Opera users: please let me know if it doesn't work for you!)

So, the key to Dojo's load system are three commands: dojo.require, dojo.provide, and dojo.declare. The way it works is pretty brilliant: when dojo.require is called, the loader code looks to see if the requested class name exists yet in the global namespace. If not, it goes out and looks on the server for a path that matches the requested class's namespace, and if found, a JavaScript file named the same as the class in question. For example, this code:

  dojo.require("foo.bar.ClassName");

...will start looking in the location of the Dojo module, trying to find a folder named foo, with a subfolder of bar. If found, it then looks in that folder for a file named ClassName.js. If found, it then uses the DOM to dynamically insert a new <script> block into the current page's header, referencing the path foo/bar/ClassName.js. The ClassName.js file, in turn, begins by calling dojo.provide("foo.bar.ClassName") to indicate its intent, and dojo.declare(...) to actually define the class. The critical part that Peter helped me understand is that, since this is essentially injecting new code into the header at runtime, the class won't be immediately available... explaining why my code kept blowing up when I tried something like this:

  dojo.require("loc.Game");
  var game = new loc.Game();

The way to do what I was trying to do, Peter told me, was to leverage the dojo.addOnLoad() method. This registers a callback function to be executed when all requested classes have been fully loaded. The cool thing about this, Peter told me, was that you could actually chain multiple require() / addOnLoad() calls, and Dojo will do the right thing!

  dojo.require("namespace.Class1");
  dojo.addOnLoad( function() { console.log("Class 1 loaded."); } );

  dojo.require("namespace.Class2");
  dojo.addOnLoad( function() { console.log("Class 2 loaded."); } );

  dojo.require("namespace.Class3");
  dojo.addOnLoad( function() { console.log("Class 3 loaded."); } );

I mean, really: how cool is THAT?!? (answer: very.)

So now, armed with the knowledge of how to make Dojo load my classes in the proper order, I'll be able to move forward again. I should be able to leverage most of the old code from parts 2 through 4 of this series, albeit with slight tweaks to fit the new Sprite class model. And therein lies my love for Dojo: it formalizes things I already intuitively know how to do, and makes them work across all potential platforms, without any additional effort on my part. If you're not using Dojo or any other JavaScript framework or toolkit (e.g. MooTools / jQuery / YUI / etc.), why the heck not? It makes coding for the web a much more rewarding, and productive, experience.

Labels: , , ,

Saturday, April 25, 2009

JSConf Wrap-up and Tweets

Had a fantastic weekend at JSConf here in DC. Huge thanks to my employer, White Oak Technologies, for sending my team & I.

Here's a summary of the messages I sent out from Twitter during the conference yesterday and today:

  • sitting in a packed conference room learning about Objective-J. No idea yet where my coworkers are at [Original Tweet]
  • I suppose I shouldn't be as surprised as I am that so many of the webmonkeys here are Mac people... [Original Tweet]
  • @jsconf track A has some cool topics, the seating is sub-optimal tho... seating in Track B is way nicer [Original Tweet]
  • @paul_irish just gave a great talk on web typography... awesome to see this guy in person after following his blog for months [Original Tweet]
  • Awesome demo of Appcelerator Titanium by @jhaynie, gotta find a way to use this.. [Original Tweet]
  • Despite a few glitches, the JavascriptMVC pres was pretty good. Not 100% convinced that MVC really "works" for web apps, tho [Original Tweet]
  • urg, stupid phone battery dying from too much wifi usage - and the kicker is that g3 is faster anyway! =P [Original Tweet]
  • @jeresig Awesome talk on web gaming challenges, thanks for sharing! [Original Tweet]
  • RT @jhaynie: @jeresig presentation was incredibly awesome. [Original Tweet]
  • @phiggins rocks. He's a great ambassador for Dojo, and he totally helped me iron out require/declare/provide in the hacker lounge. [Original Tweet]
  • @jsconf Thanks for an awesome conference. Already planning on JSConf2010. [Original Tweet]

I think the thing I enjoyed the most about this weekend, over and above the awesome food and great demos of cutting-edge advancements in my field, was the opportunity to connect with so many of my peers in the industry at large. It's really easy to get bogged down in the immediacy of the "now", so it's always nice to step back and examine why I do what I do, and why I enjoy it so much.

Oh, and Peter Higgins, the lead guy behind Dojo, gave me a little personal help in getting the Dojo load system working. So, that next article in my Cloning Zelda series is finally progressing. :)

Labels: ,

Wednesday, March 18, 2009

Cloning Zelda, intermission

I'm hard at work on Part 5, and I hope to get it done soon so I can put more effort into blogging about it.

Meanwhile, a couple of notes: sprite problems

  • In making some changes to the image format for Part 5, I accidentally broke the sword sprites in Part 4. (Oops!) It's now fixed.
  • I added a new index page of sorts to keep track of the Zelda/Canvassa progress; it's here.
  • That page also includes a revised tasklist for "Phase 1" of my plans for Canvassa; dungeons and bosses would be a huge undertaking over and above what I've already bitten off, and I'm not yet sure that committing to doing it is wise for my sanity's sake.
  • I just discovered yesterday that my dumb CSS-based layout still doesn't render right on all browsers/window sizes. I'm about THIS close to just rewriting the whole page template using HTML tables and being done with it.
  • Yes, I know Part 4 doesn't work in IE or Chrome. I wouldn't be surprised if I broke it in Safari and Opera, too. :( All I can say is, I'm working on it.*

* The culprit seems to be JS-itis... I've been putting each new class heirarchy in its own JavaScript file, but they're all messily interrelated and all live in the global namespace, so it goes BOOM in every other browser besides Firefox 3. (but it works fine in FF!)

I'm considering using Dojo.require() to keep track of loading order and dependencies for me, but on the other hand, maybe I ought to just slap the whole thing into a single .js file and be done with it. Still pondering pros and cons on that one.

Labels: , ,

Thursday, January 22, 2009

Cloning Zelda, part 3B: Monster-think

As I mentioned last time, part 3 ended up being too much work to describe all in a single post. Here's part B.

wandering armosWhen I worked up the map schema in part 2, I had to decide how to handle the non-static portions of the scenery: walls that can be blown up, trees that can be burned, and statues that can get up and walk around. We'll deal with the walls and trees in a subsequent entry, but the statues armos statue, called Armos, were better treated as monsters since they can be woken up (live armos), move around, attack, get killed, and respawn when you come back to the screen later (a burned bush or exploded wall, on the other hand, stays that way for the rest of the game). So I figured it was time to decide how to deal with the game's sprites.

As I mentioned in part 2, the original implementation of the game drew the map and Link on a single canvas, which had to be completely repainted for every game tick. One way around this is to use multiple Canvas elements, layered one on top of the other:

two overlapping canvas layers

The "background" canvas gets painted first, on the bottom, then the game's sprites are all painted transparently onto the sprite canvas above the background, so it truly only needs to be repainted when it changes. Accomplishing this in HTML code is as simple as defining a CSS selector that applies to all canvas tags, like so:

canvas {
  border: 6px double black;
  position: absolute;
  top: 20px;
  left: 20px;
}

The canvas tags themselves are declared one right after the other, bottom layer first then working upwards (this keeps them in the proper z-order):

<canvas id="map" width="512" height="480"></canvas>
<canvas id="sprites" width="512" height="480"></canvas>

With the canvas layers properly set up, I modified the game code to paint each layer only when appropriate: redrawing the background layer when it changes, and redrawing the sprite layer on every iteration through the main game loop. That loop, then, becomes responsible for painting Link, the monsters, and any items/loot lying around on the ground. I usually try to do things in an object-oriented way if at all possible, so the map, player, and enemy classes each take care of painting themselves. That makes the main game loop's "redraw" section quite succinct:

this.drawSprites = function(){
   ...
    // draw any enemies
    if (this.enemies) {
        for (var i=0; i<this.enemies.length; i++) {
            this.enemies[i].draw(spriteCanvas);
        }
    }

    // draw link
    player.draw(spriteCanvas);
    ...
}

(Incidentally, I am indebted to the sprite gallery at ZeldaDungeon.net for most of my enemy sprites, although I did rip a few from an old NES emulator I had lying in a musty old corner of my hard drive). Aside from a few other bits of code to take care of things like the game header, that's the heart of the game.drawSprites() method, which is one-half of the game's main program loop (we'll talk about the other half in a minute).

As I noted in the last paragraph, the game is now structured to include an array of "enemy" objects at all times. This array gets re-initialized whenever Link enters a new screen, based on the data encoded in the gameData structure for each map screen. Here, for example, is part of the entry for overworld map screen 4,3 (the one shown in the first image in this post):

enemies: [
    {type:'armos',color:0,position:{x:40,y:72}},
    {type:'armos',color:0,position:{x:72,y:72}},
    {type:'armos',color:0,position:{x:104,y:72}},
    {type:'armos',color:0,position:{x:40,y:104}},
    {type:'armos',color:0,position:{x:72,y:104}},
    {type:'armos',color:0,position:{x:104,y:104}},
    {type:'leever',color:1,position:{x:72,y:40}},
    {type:'leever',color:1,position:{x:104,y:40}},
    {type:'leever',color:1,position:{x:152,y:40}},
    {type:'leever',color:1,position:{x:152,y:72}},
]

The gameData definition for monsters consists of three parts: a type, a color, and a starting position. The type indicates the type of monster object to be spawned, and color and position are passed along to its constructor so that it knows how and where to draw itself when asked to do so (many enemy types have a a red/orange and a blue "subspecies", as it were; the color attribute specifies which to draw). The Game class contains a large switch() block that uses the type value to determine which Enemy subclass to instantiate.

Yes, enemy subclasses. When you consider all of the different types of monster in the Zelda overworld (and even in the dungeons), there are shared traits. For instance, some monsters (namely Octorocs, Moblins, and Lynels) can throw things at you (rocks, arrows, and swords, respectively): it makes sense to define an abstract base class, let's call it "Thrower", for enemies that throw things. We can then put the specific code that implements the throwing into the base class, and just override the function that draws the sprites for each respective type of enemy derived from that class. (note: I am using the excellent Javascript library Dojo to handle my classes now; it does all of the heavy lifting for me in terms of polymorphism, class inheritance, etc.)

dojo.declare("Enemy", null, {
    constructor: function(position, color){},
    draw: function(ctx){},
    canMove: function(direction){},
    move: function(direction){},
    think: function(){}
});

dojo.declare("Thrower", Enemy, {
    // (moblins, octorocs, lynels)
    attack: function(){ // todo: launch projectile object }
});

dojo.declare("Octoroc", Thrower, {
    constructor: function(position, color){ // override base class to store correct sprite drawing info, etc }
});

I omitted a lot of the implementation details in the above example; indeed, not everything is even implemented yet! Nevertheless, the basic framework is now in place so that enemies appear in their proper places on each map screen, and all know how to move, more or less: I haven't yet fixed the Diggers subclass for Leevers and Zolas so that they go below the ground/water and pop up in a new position, nor have I implemented the pseudo-"jumping" movement that Tectites use. Both of these enemy subtypes just use the base type's movement algorithm at the moment. Likewise, I also haven't implemented the attack() method on the Thrower subclass; that's part of what I'll be doing in Part 4 of this series. But at the very least, all enemies can take advantage of the think() method, which we call from the main game loop (see? Told you I'd talk about the other half), which I can now show in its entirety:

this.main = function() {
    // update monster positions
    if (this.enemies) {
        for (var i=0; i<this.enemies.length; i++) {
            this.enemies[i].think();
        }
    }

    // draw everything
    this.drawSprites();
}

If you'd like to look at the implementation of the Enemy class and its many subclasses, they're all contained in the enemies.js script file (The updated game.js is here)

So, now we have enemies, and they know (kinda-sorta) how to move around. The game is finally (almost) starting to get interesting to play, now: when my son saw it, it engrossed him for 15 minutes or more as he walked around exploring the whole overworld and all of its ecosystems. Feel free to do the same, here.

Next time, we'll finally get around to giving Link a sword and the ability to swing it. He'll need it, because we're also going to implement hit points and the ability to kill (and be killed)!

Labels: , , ,