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: , , ,

Tuesday, May 25, 2010

Tidbits

As of this morning, JSConf 2010 videos are starting to make their way online. I'm looking forward to virtually experiencing the Piratey goodness for myself.

Oh, and I'm starting to fix/update the Canvassa mapping tool, still needs some optimizations and bugfixes, but at least it's not completely broken anymore... yay?

Finally, I've recently found myself on an insanely-accelerated timetable to finish my Computational Linguistics MA thesis in the next 3 weeks or so, so you can probably expect my next post or two to reflect that preoccupation. Provided I find anything in the course of writing it that deserves the (slightly) larger platform.

Labels: , ,

Friday, March 05, 2010

Cloning Zelda: Meet Canvassa's Little Brother

Link, meet Link...

I recently received an email from Rune Andreas Grimstad, requesting my permission to use some of the data assets I created for Canvassa (specifically the map data and sprite tiles). You see, he's doing a Zelda clone of his own — for Silverlight (like his blog title suggests, "It should be fun").

Anyway, I'm happy to share... especially since the game design and sprites aren't mine in the first place! ;) But even my code is there to be shared — that's why I put it on GitHub, after all. Anyone else who wants to use that stuff can either fork the Github repo directly, or just grab it and use it. All I ask in return is a quick note like Rune's, to let me know about the project, so I can link back to it and throw a few clicks your direction.

Labels: , , ,

Saturday, February 13, 2010

Cloning Zelda: Fixing Chrome

Chrome displaying a 'broken' tab

This week, an epic snowfall in the mid-Atlantic states kept me home from work for four straight days, giving me lots of spare time to play with my kids, and work on some side projects. One of these was addressing a long-standing problem with Canvassa: namely, why it was broken in Google's Chrome browser.

The initial impetus for this task came from a comment left by Gaby de Wilde on a recent blog entry, in which he asked if I knew why the project wasn't working on Chrome. I was a bit embarrassed to admit that I wasn't sure, so it drove me to look into why.

Step 1: Get My Feelings Hurt

First stop was to fire up Chrome and open the JavaScript Console. Then I loaded up the main Canvassa URL and examined the error console. Sure enough, after loading several of my class files, it barfed on Game.js. Unfortunately, due to the nature of Dojo's on-demand script loader, the console didn't actually tell me WHY that class didn't load.

Given that the error console wasn't being very forthcoming, my next stop was Douglas Crockford's JSLint code-validation tool. Needless to say, it lived up to its billing and "hurt my feelings," at least a little bit. It turns out I was cutting quite a few corners that Crockford argues shouldn't be cut: omitting semicolons, defining my variables wrong, and not filtering for..in blocks seem to be my most common errors. Firefox and IE don't care about this sort of thing; they just blithely ignore it and carry on with what you intended to say, instead of making a scene in front of the entire classroom, going on about how "rules are for everyone, including you, Mister Potter."

Ahem.

Step 2: Learn Something About Chrome's Canvas Implementation

Once JSLint had issued me 50 demerits and was satisfied with the quality of my Game.js class file, I uploaded the fixed copy to my web server and went back to Chrome to reload the main page. Success... for a few minutes anyway. After playing the game for a few minutes, I began to notice that the game would, at seemingly random times, appear to "freeze up" and stop drawing its sprites. After a few seconds, though, they would all come back and the game would resume. Opening the JavaScript console again, I noticed that the freezeup occurred every time the app generated a flood of these errors:

Error drawing game sprites: DOMCoreException [in game_drawSprites(default)]

I added some additional logging to the game_drawSprites() function, and was able to determine that the exception was occurring in two cases: when Leevers or Zolas went under the ground/water, and when Link killed monsters. Looking for commonalities in those two cases, I found the answer: in both the "digger"-type monsters' animations, I defined the "under water/ground" animation — which should be invisible to the player — by specifying the animation frame's sprite coordinates as outside the boundaries of the source image. Looking in monster/_base.js, the base class for all monsters, I saw that I was doing this same thing in the default monster-death animation:

// /loc/monster/Zola.js: 'underwater' animation definition:
  this._stateDefs[4] = { name: 'underwater', ... anim: [
     [ {x:900,y:900,t:120} ]
  ]};

// note that the monsters.png image is 160x304 pixels, so (900,900) is off the edge

// /loc/monster/_base.js: 'die' animation definition:
  { name: 'die', ... anim: [
    [ {x:64,y:16,t:6},{x:80,y:16,t:3},{x:200,y:16,t:20} ]
  ]}

// again, (200,16) is beyond the 160x304 edge of monsters.png

When I originally set up the sprite animation system, I thought I was being terribly clever to define "invisible" animation frames in this way. My good buddy Firefox just ignored these calls, or at the very least failed quietly, so no sprite would be displayed for that animation tick. Chrome, however, doesn't see this as clever at all: if you try to pass invalid x,y coordinates to Canvas's 2d context object's drawImage() function, it throws a big, loud exception.

Of course, my approach had one other problem as well: if I ever expanded the monster sprite tile image to include more sprites, these supposedly "invisible" animations could suddenly be showing frames of some other sprite's animations! Yeah... bad idea.

Step 3: Go With What I Know

As with my last Canvassa update, the solution to this problem came to me by way of my MUGEN experience. With that game engine, the way you define an "invisible" animation frame is by using a sprite index of -1 (note that MUGEN's animation frame format is spriteX, spriteY, offsetX, offsetY, time):

; teleport: disappear and reappear behind my opponent
[Begin Action 1200]
1200,0, 0,0, 5
1200,1, 0,0, 5
1200,2, 0,0, 5
-1,-1, 0,0, 20  ; invisible for 20 ticks before re-appearing
1200,3, 0,0, 5
1200,4, 0,0, 5
1200,5, 0,0, 5

So, adopting this convention, I made the following, small changes to my code:

// /loc/Sprite.js: added this single line to draw():
   if (cut.x === -1 || cut.y === -1) { return; } // (-1 means "don't draw me")

// /loc/monster/Zola.js: changed the 'underwater' animation definition to:
  this._stateDefs[4] = { name: 'underwater', ... anim: [
     [ {x:-1,y:-1,t:120} ]
  ]};

// /loc/monster/_base.js: 'die' animation definition:
  { name: 'die', ... anim: [
    [ {x:64,y:16,t:6},{x:80,y:16,t:3},{x:-1,y:-1,t:20} ]
  ]}

Once I made those changes and uploaded them to my server, I reloaded the game url in Chrome, and it ran without a hitch. Go on, see for yourself.

So MUGEN saves the day (again). I'm always pleasantly surprised when experience in one realm helps me solve problems in another, but considering how often it happens, I really shouldn't be. Experience is experience... no matter what language or platform it comes from.

Labels: , , ,

Friday, January 01, 2010

Happy Birthday, Canvassa

I just realized it's been a year since my first post detailing my efforts to re-create The NES Legend of Zelda in Javascript and HTML5/Canvas. I'm currently on hiatus, recuperating from minor sinus surgery earlier in the week, but I have an update in mind for later this month.

Until then, enjoy the current version here, and have a happy 2010.

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: , , , ,

Wednesday, December 02, 2009

Cloning Zelda: And Lo, There Came... an Archive!

Short update tonight. Bullet points, because I love 'em so much:

  • I've added a few pretty keen new tidbits to Canvassa, and will be blogging about them soon.
  • Speaking of Canvassa, I've revamped the Canvassa Archive page with a more up-to-date (and relevant) development roadmap, including links to all related blog posts on each finished task.
  • Updated and cleaned up the navigation toolbar; it should now degrade better for IE and mobile users.
  • New favicon / site logo. Whaddayathink?

Labels: ,

Monday, October 26, 2009

Cloning Zelda: 0.5.x

Zelda menu screen

I'm not exactly sure what release number this is, or if such things really even apply to an experimental web game I operate on "live." But anyway, I wanted to give a brief "State of Canvassa" update on my recent changes.

A couple of my recent changes are completed tasks from my recently posted bug list for version 0.5:

  • Add text to the page describing the game controls
  • Fix projectile management (I wasn't tracking them correctly, especially the sword)

The other changes are things I added because I felt they were necessary and/or appropriate to be done at this time:

  • Added a "please wait" text blurb and animation to indicate game loading state
  • Re-encoded the music at lower bitrates: smaller file sizes, and more accurate to the original sounds.
  • Added a preliminary savegame screen; for now it just lists the available quest(s)
  • Added a few of the missing sound effects to the game (bombs boom, Link grunts when hit, etc)

As always, there's plenty to work on, and more coming soon.

Labels: ,

Wednesday, October 14, 2009

Cloning Zelda: Adding a Title Screen

Legend of Zelda title screen

First, a note on the server move: you may have noticed that my domain was offline for a few days last week; this was because I flubbed the domain transfer to my new server. Anyway, all was eventually straightened out, and I've successfully migrated all of my content from the old server to the new one. If you see anything here on the site that's broken, post a comment and let me know.

Now, on to today's subject: in trying to add several new states to the Game class for Canvassa, I've learned something interesting about Javascript's in operator: it doesn't really work for arrays! More specifically, it does in fact work, just not the way one would expect.

I do a lot of work in Python. In that language, you can do something like the following, and have a reasonable expectation of success:

>>> myList = [5,10,15,20]
>>> print (10 in myList)
True

So in a nutshell, you can define a list of values, and then check a reference value for membership in that list. Simple, and useful. I was doing this in Canvassa's Game class to test for the existence of a caller-specified game state before actually changing to the new state:

    this.constants.states = { overworld: 0, inventory: 1, 
                              dungeon: 2, map: 3, 
                              dying: 4, gameover: 5, cheat: 99, 
                              enum: [0,1,2,3,4,5,99] };
    ...
    changeState: function game_changeState(newState) {
        if (newState in this.constants.states.enum) {
            this.currentState = newState;
            this.drawBG();
        } else {
            console.log("Game.changeState() -- invalid state:", newState);
        }
    }

So basically I was using game.constants.states.enum as a list to keep track of the valid state values that changeState() should accept. Since the states I was using were all 0-based and sequential (except for the "cheat" state, which I haven't tested since Part 2 or so), this worked just fine. But in truth, it wasn't actually doing what I thought it was doing, as I was about to find out (dun-dun-DUN!)

I've been bothered for a while now about how I was loading the quest data (which handles map layouts, item and monster placement, and so on). Basically I was using the dojo.require() function to lazy-load the _quest1.js file, which doesn't actually contain a dojo class definition, but simply assigned the quest data to a namespaced variable, loc.gameData, which my classes could then refer to as a global. All kinds of poor, hackish behavior going on there. Plus, I'm building this game engine to support multiple (and, eventually, user-created) quests, so hard-coding one into the game bootstrapping logic is stupid and short-sighted. In order to correct this egregious flaw in the least destructive way possible, I reasoned, I should add several new states to the Game class, using negative integers to represent these meta-states that exist outside the regular game loop:

    this.constants.states = { title: -1, menu: -2, loading: -3,
                              overworld: 0, inventory: 1, 
                              dungeon: 2, map: 3, 
                              dying: 4, gameover: 5, cheat: 99, 
                              enum: [-3,-2,-1,0,1,2,3,4,5,99] };

In theory, the game engine could now start in the title state, and when the player presses start, change to the menu state, then allow quest selection from there. (for now, I'm simply XHR-loading _quest1.js when the user presses start, and will add the menu state later). But instead of working as I expected, this is when everything started to fall apart. I made the above change and a few others that would direct the game to load the necessary image resources, and then call game.changeState(game.constants.states.title) to display the title screen. BOOM! JavaScript error:

Game.changeState() -- invalid state: -1

Spending some time testing various expressions in the Firebug console, I eventually figured it out: the 'in' operator, when applied to JavaScript arrays, tests for the existence of a given index, not value! Observe this little exchange from my Firebug console:

>>> game.constants.states.enum
[-3, -2, -1, 0, 1, 2, 3, 4, 5, 99]
>>> -1 in game.constants.states.enum
false
>>> 8 in game.constants.states.enum
true

Like I said on Twitter when I discovered this: Freaky! I spent a few minutes looking at ways to get around this apparent misbehavior on JavaScript's part (I've since come to terms with why it works the way it does, but it's totally counter-intuitive to my way of thinking), and found this method, suggested by JavaScript rockstar Jonathan Snook.

In JavaScript, there's an in operator that tests whether a property is in an object. We can actually use this to mimic the PHP function in_array.
if(name in {'bobby':'', 'sue':'','smith':''}) { ... }
If the value of name matches any of the keys in the object literal, it returns true.

Jonathan then offers a refinement via a small function that takes a string array as input, and returns an object literal that can then be tested for membership via in. A reasonable approach, but not really one I wanted to pursue, if something simpler would work. This is what I came up with:

    this.constants.states = { overworld: 'overworld',
                              inventory: 'inventory',
                              dungeon: 'dungeon',
                              map: 'map',
                              dying: 'dying',
                              gameover: 'gameover',
                              title: 'title',
                              menu: 'menu',
                              loading: 'loading',
                              cheat: 'cheat' };
    ...
    changeState: function game_changeState(newState) {
        if (newState in this.constants.states) {
            this.currentState = newState;
            this.drawBG();
        } else {
            console.log("Game.changeState() -- invalid state:", newState);
        }
    }

Et, voila! Simple and elegant. I still have my pseudo-enumeration of valid game states, but I've eliminated the unnecessary "enum: [1,2,3...]" construction, and changed the state values from meaningless integers into strings that matched their own keys. Now my code can still refer to states via their game.constants.states.* enumeration values, and in works as expected, since those strings resolve as properties of the game.constants.states object literal.

There was a little more code cleanup necessary to support this change (for instance, I had to define a list of "unpausable" states so that the user wouldn't be able to put the game in pause mode from the title screen), and there are still a few lingering, unrelated problems in the 0.5 codebase, but we now have a title screen, and the ability to add new game states and check for their existence. All in all, a good night's work.

Labels: , , ,

Friday, October 02, 2009

Cloning Zelda: Progress and a Change of Methodology

So those of you who are hitting the work-in-progress Canvassa build may have noticed that I've added sound support back in. It's not very Dojo-y at the moment, but I got tired of not hearing anything as I tested, so it's in. I've also got the bug about predefined items (e.g. the sword on screen 1) reappearing after you pick them up.

The other major change in the Canvassa effort is how I'll be measuring progress. I started out treating it almost as a tutorial of how to build a game like this in JavaScript+Dojo, and to a certain extent I'll continue to do so, but the posts will stray from the "Part 1 / 2a / 2b..." format that I've been using, and go to a slightly more traditional "dot release" approach: right now I'm working on version 0.5, and when it's finished and the Github repository is updated, I'll move to v0.6. All development will be done in the work-in-progress Canvassa location, so no more Part 1 / Part 2 / etc. subfolders. Hopefully this will be easier for me to work with and stay on task, and will be more in line with what you, my faithful reader, would want to see anyway. (of course, if I'm wrong in my estimation, feel free to let me know in the comments).

Beyond that, not much else has progressed this week; I'll be moving this blog and my others to my new host soon, and I'm still trying to decide how exactly I want to set things up (one of the main points of uncertainty is whether to keep the three blogs separate, or merge them into a single stream with tags/categories to differentiate them. If I do merge the blogs, I'll keep my RSS feeds the same (I'll re-orient the FeedBurner links to point to the appropriate places), so those of you reading me through a feed reader probably won't notice much of a difference. Hopefully, I'll be able to get the buyog.com domain transferred over smoothly to my new host as well, but if not, the buyog.net domain will get you to the new content. When the move actually happens, I'll make a note of it here so you're all aware.

Labels: , , ,

Thursday, September 24, 2009

Canvassa 0.5 Bug List

Thought I'd peel back the curtain a little tonight. Here's what's left to do with my Canvassa Zelda clone before I can call version 0.5 (i.e. Part 5) done and ready to move on to 0.6:

  • Add text to the page describing the game controls
  • Fix projectile management (I'm not keeping track of them correctly, especially the sword)
  • Add the sword projectile "flash" effect when it hits something
  • Limit the blue candle to a single use per screen
  • Make Link & the monsters get knocked back when hit
  • Fix the number of hits required to kill monsters (esp. Zola)
  • Permanently remove predefined items from the map when picked up (e.g. the wooden sword on the start screen -- grab it, leave the screen, then come back: it's there again!)
  • Make killed monsters leave behind hearts and rupees (fairies and clocks come later)
  • Split Items.js file into multiple subfiles, parallel to what Peter Higgins did to Monsters.js for me last week

I'll be working on a few of these things tonight; maybe even have a more verbose blog post to go along with it (hope springs eternal, anyway)

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: , , , ,

Wednesday, August 19, 2009

NES Emulator in JavaScript? No Way!

Yes way, Ted: JSNES. What this means to me personally: well, I'm going to continue working on Canvassa, because ultimately it's the quest builder that I'm most excited about. But, I've still got to duplicate the existing functionality before I try to rewrite it all. This summer has ended up being much more hectic than I'd anticipated, but the work will progress, and I'll try to be better about my weekly updates here, even if it's just to post to cool stuff like this JavaScript-based NES emulator. Tell me that thing's not inspiring to all you JS hackers out there. At least, it is to me.

Labels: , ,

Tuesday, June 16, 2009

Cloning Zelda: Are we done yet?

Busy with graduate school projects tonight, so this week's Tuesday post (okay, it's Wednesday morning) will be short.

The Overworld map is now implemented in the Canvassa, Part 5 main page, and I've ported the Map Maker utility too, albeit in an optimized way (you have to actually click on a map screen in order to see its thumbnail, instead of having the page draw the entire map. I'm working on ways to draw all the thumbnails without bogging down the machine, but none of them are fully fleshed out yet).

Left to do before I close out the Part 5 code branch:

  • Port over the rest of the inventory/items code
  • Implement better map boundary checking (it's currently possible to slightly overlap walls and such)
  • Post over the hit collision code from the earlier iteration.

Once I've finished the above tasks, I'll consider the rewrite/re-engineering effort a success, write the final "Part 5" blog post, and move forward to Part 6. Depending on my school load the rest of this week, that may happen sometime before next Tuesday. If not... well, I'm sure I'll think of something.

Reminder: the most recent version of the code is also available on GitHub, if you're interested in checking out the full source code.

Labels: , ,

Wednesday, June 10, 2009

Cloning Zelda: Sandboxed

At Peter Higgins's suggestion, I've started a public sandbox on Github, and the latest version of Canvassa is up there for your perusal. Feel free to review / fork / comment the heck out of it, if you so choose.

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: , , ,

Wednesday, June 03, 2009

Cloning Zelda, part 5C: Building Better Monsters

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

Canvassa Bestiary with projectile attacks

Last time, I introduced you to the Canvassa Bestiary, a test page I wrote to put the new sprite class hierarchy through its paces. Since then, we've put an offer on a house and I've revised and submitted the prospectus for my MA project to my thesis committee. Plus my time commitments to my full-time job and my awesome family. So I've been a little stressed out distracted.

That said, I have added projectile attacks to the monsters that should have them... so Moblins shoot arrows, Lynels throw swords, Octorocs spit rocks, and so on. I've also stubbed in the dungeon monsters, as I mentioned before (although most of them don't have the right AI yet), so Wizzrobes are in there with their magic x-ray wavy attack... things, and Goriyas have their boomerangs... but then I hit the "Math Wall." (insert scary music here if you're so inclined). I'd intended to re-enable collision detection tonight and blog about all the cool new stuff I can do with the new sprites engine I've built, but instead I got stuck on the math needed to calculate the returning trajectory of a boomerang when its owner has moved. It's a lot more complicated than I expected it to be, mostly because I haven't had Geometry in like 20 years. (yikes!) Anyway, I'm close, but I'm also tired and ready for bed. (ugh, how'd it get to be 2:30am ?!?)

So, collisions will have to wait for the next update, which I'm (perhaps irrationally) hoping will also include the ported map engine and the game inventory screen. Then I'll be back to where I was TWO MONTHS AGO (sigh). Except I'll be cross-platform, projectiles will work, and dungeon monsters will be there. So that'll be a net gain after all.

To check out my handiwork, and fill the screen with monsters, click here.

Labels: , ,

Sunday, May 24, 2009

Cloning Zelda, part 5B

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

bestiary

I haven't yet finished writing up all that I've done for the next installment of my Cloning Zelda series, but in the meanwhile, I wanted to share the test page I've created to evaluate monster behavior, AI, and state management. It's here, at the Canvassa Bestiary.

A few notes on what the Bestiary does: arrow keys allow you to move Link around the canvas. Pushing the first set of buttons and selecting different radio options will change Link's animation and appearance; right now if you push the "Die" button, Link will in fact die, and the game simulation will end; you'll have to refresh the page to start it up again.

The next two sets of buttons will let you spawn enemies into the canvas... the first group is for overworld enemies, and the second group is for dungeon enemies, which I hadn't yet tried to do. Each will spawn in the correct way for that monster type (e.g. Armos enemies start out in their statue state), and all have basic AI that will determine how they move (even Peahats, Zolas and Leevers now work more or less correctly, although Tectites still look strange when jumping). I haven't yet added projectile attacks or sprite collisions; that's why I haven't blogged a full entry about the Bestiary yet (I hope to have that for this week's regular update on Tuesday).

Final technical note: I've tested the Bestiary in Firefox 3.0.10, Chrome, and MSIE 7 (IE is sloooow, but functional). Notably, I've also successfully tested it on my Windows Mobile smartphone, in Opera Mobile 9.5 beta! Again, it's slow, but even seeing it WORK on my phone was pretty frigging cool. Hopefully the Javascript performance war that has been raging on desktop browsers for the past year, will next find its way to the mobile platforms. Ultimately I'd love to be able to develop games and apps in HTML5/canvas and deploy to both desktop and mobile browsers in one click. What a cool step forward that will be.

Labels: , ,