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.

Step 1: Defining the missing behavior

In the original game, the blue candle was one of the first items available to you: for the bargain price of 60 rupees, you could buy it from the merchant just around the corner. As such, it had some cool functionality (damaging enemies, burning down trees), but one steep limitation: you could only use it once on any given screen. When I first implemented the candle item, it included no such limitation — mainly because I didn't want to pollute the main game loop by writing some specialized code for that purpose in the loc.Game class. Rather, I wanted to keep the game loop as clean as possible, and let all of Link's inventory items take care of themselves as much as possible.

Step 2: Framing the Problem

I determined that what I needed was a way for the candle to know when Link had moved to a new screen, and reset itself whenever that happened. This suggested the need for either the candle object to poll the player's position on a regular basis, or the player object to notify the candle object when its position changed. Neither solution appealed to me: having the candle object query the player object for anything at all seemed hackish at best (especially since proper encapsulation dictates that an owned object shouldn't be privy to all of its owner's properties), and forcing the player object to notify the candle object whenever it entered a new screen would tightly bind the two objects, requiring one to know and manage the internal state of the other — something I want to avoid if at all possible.

The solution lies in an often-overlooked corner of Dojo Base called dojo.connect, which allows us to create event listeners for not only DOM events, but any arbitrary JavaScript functions that we want. Here's how DojoCampus describes it:

// When ob.onCustomEvent executes, customEventHandler is invoked:
dojo.connect(ob, "onCustomEvent", null, "customEventHandler");
dojo.connect(ob, "onCustomEvent", "customEventHandler"); // same

In our case, we want the player inventory to be listening for an event that tells us that the game's map screen has changed. Here's how it works:

// in Player.js:

updateInventory: function{} {
// when Link first picks up the candle:
dojo.connect(game, 'setupMapScreen', this.resetItems);

resetItems: function() {
    // reset the candle

// in Item.js:
dojo.declare("loc.Candle", [loc.Item, loc.InventoryItem], {
    available: true,
    getProjectile: function(args) {
        // if we're color=0 (blue), check to see if we've been used already on this screen
        if (this.available || this.color) {
            this.available = false;
            return new loc.FlameProj(args);
    reset: function() { this.available = true; }

Once you pick up the blue candle, the Player class starts listening for the Game class to run its setupMapScreen function, which is not an event. When that function is invoked, Dojo lets Player know, and it quietly resets inventory item #3 (i.e. the loc.Candle object). When the Player has the blue candle equipped, the getProjectile call, which is used to get the loc.FlameProj projectile-flame object, first checks to see if available is true, or failing that, if its color is nonzero (the red candle, which comes much later in the game, doesn't have the once-per-screen limitation, and is assigned color=1). Not too tricky, and it allows each object to take care of itself as much as possible.

The full change set for this feature is in my Github repository, which also includes several other tweaks I've made in the past several months but never had the time or inclination to blog about. If you see something you'd like to fix, feel free to fork it and give it a shot.

I'm starting to see areas where the code needs a refactoring, particularly in regards to the need for multiple game maps (i.e. for dungeons). Next time I'll begin to tackle the necessary changes to make this work.

Labels: , , ,


Post a Comment

Subscribe to Post Comments [Atom]

<< Home