Thursday, July 22, 2010

Game in Progress: Sprites and Animation

This is the first post of hopefully many about my attempts at building games in HTML5. HTML5 is a breakaway from past revisions, in that it lends itself heavily to JavaScript programming, local information storage, and other paradigms that allow for many applications that were either impossible or prohibitively complex in the past. A perfect example of this is the canvas element, which is a panel that can be drawn on with JavaScript code. My goal throughout this process will be to create an interesting (or at least functional) game using the new technologies provided by HTML5.

I started out wondering what type of game I should focus on, as usually the best introduction to a new programming language is a simple puzzle game such as Tetris or the like. However my ambition always gets the best of me, so the hell with it - we're making a classic platformer.

First things first, all platform games consist of the following elements:
- Levels, containing:
-- Objects, that are:
--- Inanimate (blocks, obstacles)
--- Animate (player character, NPC helpers, enemies)
--- Power-up items
-- Goal (usually get to the end alive)

I began by tackling what was going to be the most tedious issue: displaying and animating the game objects.

Sprites

The Sprite class will represent anything on the page that has to move, with variables for size and location.

Sprite class
new Sprite(x, y, w, h, startAnimation);

Variables
- x, y: Coordinates relative to top-left of canvas
- w, h: Width and height
- startAnimation: Will get into this later, but I figured all animate objects will have a default animation to start with.

Methods
- addAnimation(animationName, newAnimation): Adds newAnimation (an Animation object) to the Sprite's animations array, with the name given (i.e. megaMan.animations["RunRight"]).
- setAnimation(animationName): Sets the Sprite's currentAnimation variable to the given animation name. If the sprite is visible on-screne, the draw loop will pick up this change and immediately change to the given animation.
- draw(): Calls draw() function of the current Animation, which keeps track of frames elapsed and renders the sequence.

Using Mega Man as an example (because he is awesome and someone had already ripped his animation frames at Game Sprites Archive, his sprite is initialized as follows before the setInterval call in the init() function:

megaMan = new Sprite(250, 400, 25, 29, "RunLeft");

This places the megaMan sprite at (250,400) at a height/width of (25,29). "RunLeft" is a string that specifies the name of the default animation that the sprite should begin with. Once the Animation class is in place, we will create an animation of running left frames, and assign it to him with the name "RunLeft".

Animations

The sprite and animation classes were tricky to work out, because it's hard to determine what behaviors/properties should stay with the Sprite class.


Animation class
new Animation(image, startX, startY, frameW, frameH, lastFrame, frameRate);

Variables
- image: Image() object whose 'src' property points to the graphic containing the animation frames. More on this file format later.
- startX, startY: Coordinates of the given image where the desired animation frames begin.
- frameW, frameH: Width and height of the animation frames (must all be same size for now).
- lastFrame: # of last frame in the animation sequence, zero-indexed.
- frameRate: # of global frames that should render before the next animation step.

Methods
- draw(): Draws a frame specified by how many frames have elapsed (frameCounter), which frame to draw (currentFrame), and how long it should stay for (frameRate).


The behavior of the animation is as follows: The global draw() function calls draw() for each Sprite object on the screen. In turn, the Sprite's draw() function will call draw() for its current Animation object. The Animation object will continue to draw the first frame of the sequence until its frameCounter exceeds its frameRate - at that point, currentFrame will increment and the next image in the sequence will be displayed. The demo at the end of this post consists of Mega Man's RunRight animation, which consists of 4 images that are displayed for 5 frames each before looping back to the first.

Animation Graphic Format

MM.png, which contains the animation rows for "RunRight" and "RunLeft" animations


Using the Mega Man example, the first animation I wanted was of him running to the right. My goals in creating the format of the animation graphic were two fold: it should be easy to edit, and all animations for any given character should fit in one image. I decided on a format where each animation sequence will sit on a "row" of the graphic, with each cell being one frame. It uses PNG for its built-in transparency. Here is the example MM graphic with "RunLeft" and "RunRight":



Putting it all together

With information on the start x/y coordinates of an animation, how wide the steps are, and how many frames, the animation will now be able to render by switching frames after each n number of times the draw() loop has run. This is accomplished by the Animation classes own draw() function, which is called each frame by the Sprite class. While that might sound confusing, it breaks down rather nicely:

1) Load the image that contains your animation sequences.
-- megaManImage = new Image();
-- megaManImage.src = "img/assets/MM.png"

2) Create a new sprite with the name of the animation you will create as startAnimation (i.e. "RunRight").
-- megaMan = new Sprite(250, 400, 25, 29, "RunRight");

3) Use the 'addAnimation()' function of the sprite to add a new animation to the Sprite's animations array.
-- megaMan.addAnimation("RunRight", new Animation(megaManImage, 0, 0, 25, 29, 3, 5));

4) Add megaMan's draw() call to the global draw() function. This will call the draw() function for whatever animation name is in megaMan's currentAnimation variable (set to startAnimation by default).
-- function draw() { megaMan.draw(ctx) }

(ctx is the canvas context that's set in init(), we're passing it because drawing is handled by the context object).


I know this explanation is somewhat convoluted because I'm darting around everywhere, but that's actually how the entire thing came together. If you'd like, you can feel free to use sprite.js which contains these Sprite and Animation classes, and also check out the demo where you can press the left and right arrow keys to change megaMan's currentAnimation. As I create various other aspects of the game, they will be available for use also - just be aware that some of the code is (very) likely to change. Any questions? Leave them in the comments section and I'll do my best to answer them!

Next up: Level design. Why just build a level format and hand-code them, when you can waste your time with a level editor? Great...

3 comments:

  1. Looks great. Keep the good work, I'm reading this! ;)

    ReplyDelete
  2. Thanks for sharing your info. I really appreciate your efforts and I will be waiting for your further write ups thanks once again.
    flash to html5 converter

    ReplyDelete
  3. This is a very useful article, Great work html5

    ReplyDelete