Palagpat Coding

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

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

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]

<< Home