65.9K
CodeProject 正在变化。 阅读更多。
Home

HTML5 游戏:如何在 Canvas 中使用 EaselJS 制作精灵动画

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (3投票s)

2011 年 10 月 26 日

CPOL

5分钟阅读

viewsIcon

42499

我决定使用EaselJS,它被用来编写《PiratesLoveDaisies》这款很棒的HTML5塔防游戏。在本教程中,我们将学习如何使用现有的精灵元素并对其进行动画制作。

当你使用HTML5 Canvas元素编写休闲游戏时,你需要一种方法来处理你的精灵。有几种库可以帮助你编写游戏,包括ImpactJSCraftyJS

引言

在官方的EaselJS网站上,你会找到有趣的示例和一些基本文档。我们将使用精灵示例作为基础,以及XNA 4.0 Platformer示例中提供的资源。

如果你关注我的博客,你知道我喜欢玩这个示例。我已经在之前的两篇文章中使用过它

平台游戏示例已由XNA团队更新,并适用于Xbox 360、PC和Windows Phone 7:App Hub – 平台游戏。下载它来玩玩,然后提取精灵与EaselJS一起使用。

我将使用2个PNG文件作为精灵序列的来源。

一个奔跑的怪物,包含10个不同的精灵。

HTML5-Gaming/image001.png

我们的怪物处于空闲状态,包含11个不同的精灵

HTML5-Gaming/image002.png

注意:这些示例在Firefox 5.0中无法正常工作,显然是由于其Canvas实现中的一个bug。已在IE9、IE10、Chrome 12、Opera 11和Firefox Aurora 7.0中测试通过。

教程1:构建SpriteSheet和BitmapSequence

我们将从让怪物从画布的一端跑到另一端开始。

第一步是用这段代码加载PNG文件中包含的完整序列

var imgMonsterARun = new Image();

function init() {
    //find canvas and load images, wait for last image to load
    canvas = document.getElementById("testCanvas");

    imgMonsterARun.onload = handleImageLoad;
    imgMonsterARun.onerror = handleImageError;
    imgMonsterARun.src = "img/MonsterARun.png";
}

这段代码将首先被调用以初始化我们游戏的內容。加载完成后,你就可以开始游戏了。

EaselJS公开了一个SpriteSheet对象来处理精灵。因此,使用这段代码

var spriteSheet  = new SpriteSheet(
        imgMonsterARun, //image to use
        64, //width of each sprite
        64, //height of each sprite
        {    
            walk_left: [0, 9]
        });

...我指示我希望创建一个名为“walk_left”的新序列,它将由imgMonsterARun图像构成。该图像将被分割成10帧,每帧大小为64x64像素。这是加载我们的精灵并创建序列的核心对象。如果你愿意,可以从同一个PNG文件中创建多个序列,就像EaselJS网站上的老鼠精灵示例一样。

之后,你需要使用BitmapSequence对象。它有助于动画化序列并将精灵定位在屏幕上。

让我们回顾一下这个BitmapSequence的初始化代码

// create a BitmapSequence instance to display and play back the sprite sheet:
bmpSeq = new BitmapSequence(spriteSheet);
    
// set the registration point (the point it will be positioned and rotated around)
// to the center of the frame dimensions:
bmpSeq.regX = bmpSeq.spriteSheet.frameWidth/2|0;
bmpSeq.regY = bmpSeq.spriteSheet.frameHeight / 2 | 0;

// start playing the first sequence:
bmpSeq.gotoAndPlay("walk_left");     //animate
    
// set up a shadow. Note that shadows are ridiculously expensive. You could display hundreds
// of animated rats if you disabled the shadow.
bmpSeq.shadow = new Shadow("#454", 0, 5, 4);

bmpSeq.name = "monster1";
bmpSeq.speed = 1;
bmpSeq.direction = 90;
bmpSeq.vX = 3|0.5;
bmpSeq.vY = 0;
bmpSeq.x = 16;
bmpSeq.y = 32;
        
// have each monster start at a specific frame
bmpSeq.currentFrame = 0;
stage.addChild(bmpSeq);

BitmapSequence对象的构造函数只需要SpriteSheet元素作为参数。然后我们给序列命名,设置一些参数,比如速度和我们第一帧的初始位置。最后,我们使用Stage对象及其addChild()方法将此序列添加到显示列表中。

接下来,你需要决定在动画循环中要做什么。这个动画循环每xxx毫秒被调用一次,并允许你更新精灵的位置。为此,EaselJS公开了一个Ticker对象,它提供了一个以设定间隔广播的集中式tick或心跳。

你所要做的就是订阅tick事件并实现一个.tick()方法,该方法将被回调。

这段代码是注册事件的示例

Ticker.addListener(window);
// Best Framerate targeted (60 FPS)
Ticker.setInterval(17);

这是每17毫秒(尽可能)被调用以更新怪物位置的代码

function tick() {
    // Hit testing the screen width, otherwise our sprite would disappear
    if (bmpSeq.x >= screen_width - 16) {
        // We've reached the right side of our screen
        // We need to walk left now to go back to our initial position
        bmpSeq.direction = -90;
    }

    if (bmpSeq.x < 16) {
        // We've reached the left side of our screen
        // We need to walk right now
        bmpSeq.direction = 90;
    }

    // Moving the sprite based on the direction & the speed
    if (bmpSeq.direction == 90) {
        bmpSeq.x += bmpSeq.vX;
        bmpSeq.y += bmpSeq.vY;
    }
    else {
        bmpSeq.x -= bmpSeq.vX;
        bmpSeq.y -= bmpSeq.vY;
    }

    // update the stage:
    stage.update();
}

你可以在这里测试最终结果:easelJSSpritesTutorial01,查看完整的源代码。

但是等等!这个动画有问题!

  1. 动画步骤很奇怪,因为角色太快地循环浏览其不同的精灵
  2. 角色只正常地从右到左行走——否则看起来他像在跳月球舞! 

我们来修复它。

教程2:控制动画速度和翻转精灵

我找到的最简单的修复动画速度的方法是使用模运算符来避免在每次tick时绘制/更新我的序列。

EaselJS 0.3.2目前有一个关于此问题的开放 issue:https://github.com/gskinner/EaselJS/issues/60

为了让角色正常地从左到右行走,我们只需要翻转每一帧。

EaselJS为此公开了一个SpriteSheetUtils对象和一个flip()方法。

// {nameOfFlippedSequence:["derivativeSequence", flipHorizontally, flipVertically, optionNameOfNextSequence]}
spriteSheet = SpriteSheetUtils.flip(
spriteSheet,
{
    walk_right: ["walk_left", true, false, null]
});

你基本上是基于翻转水平的“walk_left”序列创建了一个名为“walk_right”的派生序列。

最后,这是减慢速度动画并处理基于角色位置播放哪个序列的代码

function tick() {
    // To slow down the animation loop of the sprite, we're not redrawing during each tick
    // With a Modulo 4, we're dividing the speed by 4
    var speedControl = Ticker.getTicks() % 4;

    if (speedControl == 0) {
        // Hit testing the screen width, otherwise our sprite would disappear
        if (bmpSeq.x >= screen_width - 16) {
            // We've reached the right side of our screen
            // We need to walk left now to go back to our initial position
            bmpSeq.direction = -90;
            bmpSeq.gotoAndPlay("walk_left")
        }

        if (bmpSeq.x < 16) {
            // We've reached the left side of our screen
            // We need to walk right now
            bmpSeq.direction = 90;
            bmpSeq.gotoAndPlay("walk_right");
        }

        // Moving the sprite based on the direction & the speed
        if (bmpSeq.direction == 90) {
            bmpSeq.x += bmpSeq.vX;
            bmpSeq.y += bmpSeq.vY;
        }
        else {
            bmpSeq.x -= bmpSeq.vX;
            bmpSeq.y -= bmpSeq.vY;
        }

        // update the stage:
        stage.update();
    }
}

你可以在这里测试最终结果:easelJSSpritesTutorial02

教程3:加载多个精灵并播放多个动画

是时候加载怪物的空闲状态了。

这里的想法是让怪物执行一次往返行程,然后播放空闲状态。

我们将不得不从Web服务器加载多个PNG文件。等待所有资源加载完毕非常重要,否则你可能会尝试绘制尚未下载的资源。

这是一个简单的方法

var numberOfImagesLoaded = 0;

var imgMonsterARun = new Image();
var imgMonsterAIdle = new Image();

function init() {
    //find canvas and load images, wait for last image to load
    canvas = document.getElementById("testCanvas");

    imgMonsterARun.onload = handleImageLoad;
    imgMonsterARun.onerror = handleImageError;
    imgMonsterARun.src = "img/MonsterARun.png";

    imgMonsterAIdle.onload = handleImageLoad;
    imgMonsterAIdle.onerror = handleImageError;
    imgMonsterAIdle.src = "img/MonsterAIdle.png";
}

function handleImageLoad(e) {
    numberOfImagesLoaded++;

    // We're not starting the game until all images are loaded
    // Otherwise, you may start to draw without the resource and raise 
    // this DOM Exception: INVALID_STATE_ERR (11) on the drawImage method
    if (numberOfImagesLoaded == 2) {
        numberOfImagesLoaded = 0;
        startGame();
    }
}

这段代码非常简单。例如,它没有正确处理错误,在第一次失败时尝试重新下载图像。

当你构建游戏时,如果你的JavaScript库没有实现内容下载管理器,你需要自己编写一个。

要添加空闲序列并设置位置参数,你只需要以前看到的类似的代码

var spriteSheetIdle = new SpriteSheet(
        imgMonsterAIdle, //image to use
        64, //width of each sprite
        64, //height of each sprite
        {    
            idle: [0, 10]
        });

bmpSeqIdle = new BitmapSequence(spriteSheetIdle);

bmpSeqIdle.name = "monsteridle1";
bmpSeqIdle.speed = 1;
bmpSeqIdle.direction = 0;
bmpSeqIdle.x = 16;
bmpSeqIdle.y = 32;

现在,在tick()方法中,一旦我们到达屏幕的左侧,就需要停止行走动画,并播放空闲动画。

这是停止你的怪物行走的[代码]

if (bmpSeq.x < 16) {
    // We've reached the left side of our screen
    // We need to walk right now
    bmpSeq.direction = 90;
    bmpSeq.gotoAndStop("walk_left");
    stage.removeChild(bmpSeq);
    bmpSeqIdle.gotoAndPlay("idle");
    stage.addChild(bmpSeqIdle);
}

你可以在这里测试最终结果:easelJSSpritesTutorial03

就这样!如果你准备好学习更多,请查看HTML5游戏:使用EaselJS构建核心对象和处理碰撞

© . All rights reserved.