HTML5 2D 游戏开发
Sprites
实现 Snail Bait 人物角色
系列内容:
此内容是该系列 # 部分中的第 # 部分: HTML5 2D 游戏开发
此内容是该系列的一部分:HTML5 2D 游戏开发
敬请期待该系列的后续内容。
和其他艺术形式(比如电影、戏剧和小说)一样,游戏也有一系列的人物,每个人物都扮演着特定的角色。例如,Snail Bait 有跑步小人(游戏主角)、硬币、红宝石、蓝宝石、蜜蜂、蝙蝠、纽扣和一个蜗牛,图 1 中展示了其中的大多数角色。在本系列的第 1 篇文章中(参阅 sprite:演员表 小节),已经介绍了这些人物以及它们在游戏中的角色。
图 1. Snail Bait 的人物

Snail Bait 中每个人物都是一个 sprite。Sprite 是可以赋予行为 的图形化对象,例如跑步小人可以奔跑、跳跃、坠落并与游戏中的其他 sprite 相撞,而红宝石和蓝宝石可以闪耀光芒、上下跳动,与跑步小人相撞后消失。
对于任何游戏来说,sprite 都是最基本的部分,因为游戏通常有很多 sprite,因此将其基本功能封装到可重用对象中是非常有意义的。在本文中,您将学习如何执行以下操作:
- 实现一个在任何游戏中均可重用的
Sprite
对象 - 从绘制 sprite 的对象(称之为 sprite artists)中解耦 sprite,以便在运行时灵活应用。
- 使用 sprite 表单 减少启动时间和内存需求
- 使用元数据创建 sprite
- 将 sprite 整合成为一个游戏循环
参阅 下载 部分,获取本文的完整样例代码。
Sprite 对象
我将 Snail Bait 的 sprite 实现为可在任何游戏中使用的 JavaScript 对象,因此,sprite 保存在游戏本身的文件中。我只是在 Snail Bait 的 HTML 中包含了该文件,如下所示:<script src='js/sprites.js'></script>
。
表 1 列出了 Sprite
属性:
表 1. Sprite
属性
属性 | 描述 |
---|---|
artist | 绘制 sprite 的对象。 |
behaviors | 一个行为数组,每一个都以某种方式操纵其 sprite。 |
left |
左上角 sprite 的 X 坐标。
|
top |
左上角 sprite 的 Y 坐标
|
width | sprite 的宽度,用像素表示。 |
height | sprite 的高度,用像素表示。 |
opacity | sprite 是不透明的还是透明的,或者介于两者之间。 |
type | 一个代表 sprite 类型的字符串,比如 bat 、bee 或 runner 。
|
velocityX | sprite 的水平速度,单位为像素/秒。 |
velocityY | sprite 的垂直速度,单位为像素/秒。 |
visible |
sprite 的可见度。如果值为 false ,则不需要绘制 sprite。
|
Sprite 是比较简单的对象,一些属性定义了其位置和大小(即 sprite 的边界框)、速度和可见度。还有一个属性,可用于将一个 sprite 与另一个 sprite 以及不透明 sprite 区分开,这意味着 sprite 可以是半透明的。
Sprite 将它们的绘制方式和行为方式分配给了其他属性,分别称为 artist
s 和 behaviors
。
清单 1 展示了 Sprite
构造函数,该函数将 sprite 的属性设置为初始值:
清单 1. Sprite
构造函数
var Sprite = function (type, artist, behaviors) { // constructor this.type = type || ''; this.artist = artist || undefined; this.behaviors = behaviors || []; this.left = 0; this.top = 0; this.width = 10; // Something other than zero, which makes no sense this.height = 10; // Something other than zero, which makes no sense this.velocityX = 0; this.velocityY = 0; this.opacity = 1.0; this.visible = true; return this; };
清单 1 中的所有构造函数参数都是可选的。如果没有指定行为,构造函数将会创建一个空数组,如果您创建一个 sprite 而没有指定类型,那么该类型将是一个空字符串。如果没有指定 artist,那么它的值将是 undefined。
除了这些属性之外,sprite 还有两个方法,表 2 中列出了这两个方法:
表 2. Sprite
方法
方法 | 描述 |
---|---|
draw(context) | 如果 sprite 是可见的并且有一个 artist 对象,则调用 sprite artist 的 draw() 方法 |
update(time, fps) |
对于每个 sprite 行为,应该调用 update() 方法。
|
表 2 中列出的方法的实现如清单 2 所示:
清单 2. Sprite
方法的实现
Sprite.prototype = { // methods draw: function (context) { context.save(); // Calls to save() and restore() make the globalAlpha setting temporary context.globalAlpha = this.opacity; if (this.artist && this.visible) { this.artist.draw(this, context); } context.restore(); }, update: function (time, fps) { for (var i=0; i < this.behaviors.length; ++i) { if (this.behaviors[i] === undefined) { // Modified while looping? return; } this.behaviors[i].execute(this, time, fps); } } };
您可以传递两个 Sprite
方法( draw()
和 update()
),这些方法是对 Canvas 2D 上下文的引用,这些方法会分别传递给 sprite 的 artist 和 behaviors 对象。
正如您在 清单 1 和 清单 2 中所看到的,sprite 并不复杂,大多数复杂的 sprite 都封装在 sprite 的 artist 和 behaviors 对象中。了解在运行时 可以更改 sprite 的 artist 和 behavior 很重要,因为这样您可以从这些对象中解耦 sprite。您可在本系列的下一篇文章中看到,实现多个 sprite 使用的一般行为是可行的,而且是十分可取的。
您已经了解了 sprite 的实现方法了,现在可以了解一下如何实现 sprite artists 了。
Sprite artists 和 sprite 表单
Sprite artists 可使用以下 3 种方法之一实现:
- 描边和填充 artist:绘制基本图形,比如线条、弧形和曲线
- 图像 artist:使用 2D 上下文的
drawImage()
方法绘制一个图像 - Sprite 表单 artist:从 sprite 表单绘制一个图像(也可使用
drawImage()
)
不管 artist 是什么类型,清单 2 所有 sprite artist 都必须满足以下条件:该对象必须执行一个 draw()
方法,此方法接受一个 sprite 和一个 Canvas 2D 上下文作为参数。
接下来,我们将讨论每个 artist 类型,先检查一下 sprite 表单。
描边和填充 artist
描边和填充 artist 没有标准实现,您需要使用 Canvas 2D 上下文的图形化功能,以实现它们。清单 3 显示了描边和填充 artist 的实现,绘制 Snail Bait 平台 sprite:
清单 3. 描边和填充 artist
// Stroke and fill artists draw with Canvas 2D drawing primitives var SnailBait = function (canvasId) { // constructor ... this.platformArtist = { draw: function (sprite, context) { var top; context.save(); top = snailBait.calculatePlatformTop(sprite.track); // Calls to save() and restore() make the following settings temporary context.lineWidth = snailBait.PLATFORM_STROKE_WIDTH; context.strokeStyle = snailBait.PLATFORM_STROKE_STYLE; context.fillStyle = sprite.fillStyle; context.strokeRect(sprite.left, top, sprite.width, sprite.height); context.fillRect (sprite.left, top, sprite.width, sprite.height); context.restore(); } }, };
正如您从 图 1 中所看到的,平台只不过是一些矩形,清单 3 中列出的平台 artist 使用 Canvas 2D 上下文的 strokeRect()
和 fillRect()
方法来绘制这些矩形。本系列第 2 篇文章(参阅这篇文章的 HTML5 Canvas 概述 小节)提供了关于这些方法的更多信息。矩形的位置和大小是由平台 sprite 边界框决定的。
图像 artist
与描边和填充 artist 不一样,图像 artist 有一个标准实现,如清单 4 所示:
清单 4. 图像 artist
// ImageArtists draw an image var ImageArtist = function (imageUrl) { // constructor this.image = new Image(); this.image.src = imageUrl; }; ImageArtist.prototype = { // methods draw: function (sprite, context) { context.drawImage(this.image, sprite.left, sprite.top); } };
您可以使用一个图像 URL 构造一个图像 artist,该 artist 的 draw()
方法在其 sprite 位置绘制整个图像。
Snail Bait 没有使用图像 artist 对象,因此从 sprite 表单绘制图像效率更高一些。
Sprite 表单
确保网站快速加载的一个最有效的方法是将您发出的 HTTP 请求数量减至最小。大多数游戏都使用了大量图像,如果对每个图像都发出一个 HTTP 请求,则会影响您的启动时间。鉴于这个原因,HTML5 游戏开发人员创建了一个包含所有游戏图像的大型图像,该图像称为 sprite 表单。图 2 显示了 Snail Bait 的 sprite 表单:
图 2. Snail Bait 的 sprite 表单

如果给定一个 sprite 表单,那么您需要使用一个方法将该 sprite 表单中的特定矩形绘制到一个画布上。幸运的是,Canvas 2D 上下文的 drawImage()
方法使您能够轻松地实现此操作。该技术被 sprite 表单 artists 对象所用。
Sprite 表单 artist
sprite 表单 artists 对象的实现如清单 5 所示:
清单 5. Sprite 表单 artist
// Sprite sheet artists draw an image from a sprite sheet SpriteSheetArtist = function (spritesheet, cells) { // constructor this.cells = cells; this.spritesheet = spritesheet; this.cellIndex = 0; }; SpriteSheetArtist.prototype = { // methods advance: function () { if (this.cellIndex == this.cells.length-1) { this.cellIndex = 0; } else { this.cellIndex++; } }, draw: function (sprite, context) { var cell = this.cells[this.cellIndex]; context.drawImage(this.spritesheet, cell.left, cell.top, // source x, source y cell.width, cell.height, // source width, source height sprite.left, sprite.top, // destination x, destination y cell.width, cell.height); // destination width, destination height } };
您可引用一个 sprite 表单和一个边界框数组(称为单元格)实例化 sprite 表单。这些单元格表示 sprite 表单的矩形区域,每个表单封装一个 sprite 图像。
sprite 表单 artist 也包含一个指向其单元格的指针。sprite 表单的 draw()
方法使用该指针访问当前单元格,然后使用 Canvas 2D 上下文的 drawImage()
方法的 9 个参数的版本将该单元格的内容绘制到 sprite 所在位置的画布上。
sprite 表单 artist 的 advance()
方法使单元格指针推进到下一个单元格,当指针指向最后一个单元格时返回第一个单元格。sprite 表单 artist 的 draw()
方法的后续调用绘制相应的图像。通过反复向前推进指针并绘制图像,sprite 表单 artist 可从 sprite 表单连续绘制一组图像。
正如您从 清单 5 所看到的,sprite 表单 artist 很容易实现。也容易使用它们,只需使用一个 sprite 表单和单元格实例化 artist 对象,然后根据需要调用 advance()
和 draw()
方法即可。麻烦的是如何定义单元格。
定义 sprite 表单单元格
清单 6 显示了 Snail Bait 游戏中蝙蝠、蜜蜂和蜗牛的 sprite 表单的单元格定义:
清单 6. Snail Bait sprite 表单的单元格定义
var BAT_CELLS_HEIGHT = 34, BEE_CELLS_WIDTH = 50, BEE_CELLS_HEIGHT = 50, ... SNAIL_CELLS_WIDTH = 64, SNAIL_CELLS_HEIGHT = 34, ... // Spritesheet cells................................................ batCells = [ { left: 1, top: 0, width: 32, height: BAT_CELLS_HEIGHT }, { left: 38, top: 0, width: 46, height: BAT_CELLS_HEIGHT }, { left: 90, top: 0, width: 32, height: BAT_CELLS_HEIGHT }, { left: 129, top: 0, width: 46, height: BAT_CELLS_HEIGHT }, ], beeCells = [ { left: 5, top: 234, width: BEE_CELLS_WIDTH, height: BEE_CELLS_HEIGHT }, { left: 75, top: 234, width: BEE_CELLS_WIDTH, height: BEE_CELLS_HEIGHT }, { left: 145, top: 234, width: BEE_CELLS_WIDTH, height: BEE_CELLS_HEIGHT } ], ... snailCells = [ { left: 142, top: 466, width: SNAIL_CELLS_WIDTH, height: SNAIL_CELLS_HEIGHT }, { left: 75, top: 466, width: SNAIL_CELLS_WIDTH, height: SNAIL_CELLS_HEIGHT }, { left: 2, top: 466, width: SNAIL_CELLS_WIDTH, height: SNAIL_CELLS_HEIGHT }, ];
定义单元格边界框是一个很繁琐的任务,需要投入一些时间来设计一个可帮您实现这些任务的工具。图 3 展示了这样一个工具,该工具可在 Core HTML Canvas 网站在线运行(参阅 参考资料):
图 3. 一个简单的 sprite 表单检查器

图 3 所示的应用程序展示一个图像并跟踪该图像中的鼠标移动。当您移动鼠标时,应用程序将绘制引导线,并更新应用程序左上角显示鼠标所在位置的读数。该工具使得确定每个图像和 sprite 表单的边界框变得非常容易。
现在关于如何实现 sprite 及其 artist 已经有一个不错的方式,可以看看 Snail Bait 如何创建和初始化其 sprite。
创建和初始化 Snail Bait sprite
Snail Bait 定义了最终包含 sprite 的数组,如清单 7 所示:
清单 7. 在游戏构造函数中定义 sprite 数组
var SnailBait = function (canvasId) { // constructor ... this.bats = [], this.bees = [], this.buttons = [], this.coins = [], this.platforms = [], this.rubies = [], this.sapphires = [], this.snails = [], this.runner = new Sprite('runner', this.runnerArtist); this.sprites = [ this.runner ]; // Add other sprites later ... };
清单 7 中的每个数组都包含相同类型的 sprite,bats
数组包含蝙蝠 sprite,bees
数组包含蜜蜂 sprite 等。该游戏也有一个包含所有游戏 sprite 的数组 。蜜蜂、蝙蝠等单个数组并不是必要的,事实上它们是多余的,但是它们可以提升性能,例如,游戏检查跑步小人是否位于一个平台上时,在 platforms
数组上进行迭代,比在 sprites
数组上进行迭代更为有效。
清单 7 还展示了该游戏如何创建跑步小人 sprite,以及如何将该 sprite 添加到 sprites
数组。因为这个游戏只有一个跑步小人,所以没有跑步小人数组。请注意,该游戏使用一个类型 runner
和一个 artist 来实例化跑步小人,但实例化时没有指定任何行为。这些行为(将在本系列下期文章中讨论)稍后将会添加到
代码中。
游戏开始时,Snail Bait(以及其他操作)会调用一个 createSprites()
方法,正如您在清单 8 中所看到的那样。
清单 8. 开始游戏
SnailBait.prototype = { // methods ... start: function () { this.createSprites(); this.initializeImages(); this.equipRunner(); this.splashToast('Good Luck!'); }, };
createSprites()
方法,创建除跑步小人之外的所有游戏 sprite,如清单 9 所示:
清单 9. 创建和初始化 Snail Bait sprite
SnailBait.prototype = { // methods ... createSprites: function() { this.createPlatformSprites(); this.createBatSprites(); this.createBeeSprites(); this.createButtonSprites(); this.createCoinSprites(); this.createRubySprites(); this.createSapphireSprites(); this.createSnailSprites(); this.initializeSprites(); this.addSpritesToSpriteArray(); },
createSprites()
调用可以帮助函数创建其他类型的 sprite,然后调用初始化 sprite 的方法,并将它们添加到 sprites
数组,这些函数的实现如清单 10 所示:
清单 10. 创建和初始化单个 sprite
SnailBait.prototype = { // methods ... createBatSprites: function () { var bat, batArtist = new SpriteSheetArtist(this.spritesheet, this.batCells), redEyeBatArtist = new SpriteSheetArtist(this.spritesheet, this.batRedEyeCells); for (var i = 0; i < this.batData.length; ++i) { if (i % 2 === 0) bat = new Sprite('bat', batArtist); else bat = new Sprite('bat', redEyeBatArtist); bat.width = this.BAT_CELLS_WIDTH; bat.height = this.BAT_CELLS_HEIGHT; this.bats.push(bat); } }, createBeeSprites: function () { var bee, beeArtist = new SpriteSheetArtist(this.spritesheet, this.beeCells); for (var i = 0; i < this.beeData.length; ++i) { bee = new Sprite('bee', beeArtist); bee.width = this.BEE_CELLS_WIDTH; bee.height = this.BEE_CELLS_HEIGHT; this.bees.push(bee); } }, createButtonSprites: function () { var button, buttonArtist = new SpriteSheetArtist(this.spritesheet, this.buttonCells), goldButtonArtist = new SpriteSheetArtist(this.spritesheet, this.goldButtonCells); for (var i = 0; i < this.buttonData.length; ++i) { if (i === this.buttonData.length - 1) { button = new Sprite('button', goldButtonArtist); } else { button = new Sprite('button', buttonArtist); } button.width = this.BUTTON_CELLS_WIDTH; button.height = this.BUTTON_CELLS_HEIGHT; button.velocityX = this.BUTTON_PACE_VELOCITY; button.direction = this.RIGHT; this.buttons.push(button); } }, createCoinSprites: function () { var coin, coinArtist = new SpriteSheetArtist(this.spritesheet, this.coinCells); for (var i = 0; i < this.coinData.length; ++i) { coin = new Sprite('coin', coinArtist); coin.width = this.COIN_CELLS_WIDTH; coin.height = this.COIN_CELLS_HEIGHT; this.coins.push(coin); } }, createPlatformSprites: function () { var sprite, pd; // Sprite, Platform data for (var i=0; i < this.platformData.length; ++i) { pd = this.platformData[i]; sprite = new Sprite('platform-' + i, this.platformArtist); sprite.left = pd.left; sprite.width = pd.width; sprite.height = pd.height; sprite.fillStyle = pd.fillStyle; sprite.opacity = pd.opacity; sprite.track = pd.track; sprite.button = pd.button; sprite.pulsate = pd.pulsate; sprite.power = pd.power; sprite.top = this.calculatePlatformTop(pd.track); this.platforms.push(sprite); } }, createSapphireSprites: function () { // Listing omitted for brevity. Discussed in the next article in this series. }, createRubySprites: function () { // Listing omitted for brevity. Discussed in the next article in this series. }, createSnailSprites: function () { // Listing omitted for brevity. Discussed in the next article in this series. }, };
清单 10 中展示的这些方法值得关注,原因有 3 个。第一,这些方法都非常简单:每个方法都创建了 sprite,设置其宽度和高度,并将它们添加到单个 sprite 数组中。第二,createBatSprites()
和 createButtonSprites()
使用多个 artist 创建类型相同的 sprite,createBatSprites()
方法可替代 artist,使一半蝙蝠的眼睛呈红色,另一半蝙蝠的眼睛呈白色,如图 4 所示。createButtonSprites()
方法使用 artist 方法绘制蓝色或金色纽扣。
图 4. 红眼蝙蝠和白眼蝙蝠

清单 10 中的这些方法的值得关注的第三个原因(也是最重要原因)是,它们都使用 sprite 元数据创建了 sprite。
使用元数据创建 sprite
清单 11 显示了一些 Snail Bait 的 sprite 元数据:
清单 11. Sprite 元数据
var SnailBait = function (canvasId) { // Bats.............................................................. this.batData = [ { left: 1150, top: this.TRACK_2_BASELINE - this.BAT_CELLS_HEIGHT }, { left: 1720, top: this.TRACK_2_BASELINE - 2*this.BAT_CELLS_HEIGHT }, { left: 2000, top: this.TRACK_3_BASELINE }, { left: 2200, top: this.TRACK_3_BASELINE - this.BAT_CELLS_HEIGHT }, { left: 2400, top: this.TRACK_3_BASELINE - 2*this.BAT_CELLS_HEIGHT }, ], // Bees.............................................................. this.beeData = [ { left: 500, top: 64 }, { left: 944, top: this.TRACK_2_BASELINE - this.BEE_CELLS_HEIGHT - 30 }, { left: 1600, top: 125 }, { left: 2225, top: 125 }, { left: 2295, top: 275 }, { left: 2450, top: 275 }, ], // Buttons........................................................... this.buttonData = [ { platformIndex: 7 }, { platformIndex: 12 }, ], // Metadata for Snail Bait's other sprites is omitted for brevity };
从元数据创建 sprite 是一个不错的主意,因为:
- Sprite 元数据位于某个地方,而不是遍布在代码中。
- 当从元数据中解耦这些创建 sprite 的方法时,这些方法更简单一些。
- Metadata 可从任何地方获取。
因为 sprite 元数据在代码中位于一个地方,因此很容易发现和修改它们。另外,由于元数据是在创建 sprite 的方法之外定义的,而这些方法比较简单,更容易理解和修改。最后,尽管 Snail Bait 元数据直接嵌入在代码中,但是 sprite 元数据还是可以从任何地方获取,比如可在运行时创建元数据的级别编辑器。总而言之,与直接在创建 sprite 的方法中指定 sprite 数据相比,元数据易于修改,更为灵活。
回忆一下 清单 9,创建游戏 sprite 之后,Snail Bait 的 createSprites()
方法调用了两个方法:initializeSprites()
和 addSpritesToSpriteArray()
。清单 12 展示了 initializeSprites()
方法:
清单 12. 初始化 Snail Bait sprites
SnailBait.prototype = { // methods ... initializeSprites: function() { this.positionSprites(this.bats, this.batData); this.positionSprites(this.bees, this.beeData); this.positionSprites(this.buttons, this.buttonData); this.positionSprites(this.coins, this.coinData); this.positionSprites(this.rubies, this.rubyData); this.positionSprites(this.sapphires, this.sapphireData); this.positionSprites(this.snails, this.snailData); }, positionSprites: function (sprites, spriteData) { var sprite; for (var i = 0; i < sprites.length; ++i) { sprite = sprites[i]; if (spriteData[i].platformIndex) { // put sprite on a platform this.putSpriteOnPlatform(sprite, this.platforms[spriteData[i].platformIndex]); } else { sprite.top = spriteData[i].top; sprite.left = spriteData[i].left; } } }, };
对于该游戏的 sprite 数组,initializeSprites()
调用了 positionSprites()
方法。该方法将 sprite 放到 sprite 元数据指定的位置。请注意,一些 sprite(比如纽扣和蜗牛)驻留在平台顶部。putSpriteOnPlatform()
方法如清单 13 所示。
清单 13. 将 sprite 放到平台上
SnailBait.prototype = { // methods ... putSpriteOnPlatform: function(sprite, platformSprite) { sprite.top = platformSprite.top - sprite.height; sprite.left = platformSprite.left; sprite.platform = platformSprite; }, }
给定一个 sprite 和一个平台,putSpriteOnPlatform()
方法将 sprite 放置在平台顶部,并在 sprite 中存储该平台的引用,以便进一步引用它们。
您可能会怀疑,但是清单 14 已经证实,将单个 sprite 添加到所有封装 sprites
的数组中是一件非常简单的事情:
清单 14. 创建和初始化 Snail Bait sprite
SnailBait.prototype = { // methods ... addSpritesToSpriteArray: function () { var i; for (i=0; i < this.bats.length; ++i) { this.sprites.push(this.bats[i]); } for (i=0; i < this.bees.length; ++i) { this.sprites.push(this.bees[i]); } for (i=0; i < this.buttons.length; ++i) { this.sprites.push(this.buttons[i]); } for (i=0; i < this.coins.length; ++i) { this.sprites.push(this.coins[i]); } for (i=0; i < this.rubies.length; ++i) { this.sprites.push(this.rubies[i]); } for (i=0; i < this.sapphires.length; ++i) { this.sprites.push(this.sapphires[i]); } for (i=0; i < this.snails.length; ++i) { this.sprites.push(this.snails[i]); } for (i=0; i < this.snailBombs.length; ++i) { this.sprites.push(this.snailBombs[i]); } }, };
现在,您已经了解了如何实现 sprite 和 sprite artists,以及 Snail Bait 如何创建和初始化其 sprite,接下来我将向您介绍如何将 sprite 合并到 Snail Bait 游戏循环中。
将 sprites 合并到游戏循环中
回顾一下本系列第二篇文章(参阅其 滚动背景 小节),Snail Bait 中几乎所有水平运动都是平移 Canvas 2D 上下文的结果。Snail Bait 总是将大多数 sprite 绘制在同一水平位置,表面上的水平运动完全是平移的结果。大多数 Snail Bait 的 sprite 与游戏平台同时水平移动,如清单 15 所示:
清单 15. 更新 sprite 位移
SnailBait.prototype = { draw: function (now) { this.setPlatformVelocity(); this.setTranslationOffsets(); this.drawBackground(); this.updateSprites(now); this.drawSprites(); }, setPlatformVelocity: function () { // Setting platform velocity was discussed in the second article in this series this.platformVelocity = this.bgVelocity * this.PLATFORM_VELOCITY_MULTIPLIER; }, setTranslationOffsets: function () { // Setting the background translation offset was discussed // in the second article in this series this.setBackgroundTranslationOffset(); this.setSpriteTranslationOffsets(); }, setSpriteTranslationOffsets: function () { var i, sprite; this.spriteOffset += this.platformVelocity / this.fps; // In step with platforms for (i=0; i < this.sprites.length; ++i) { sprite = this.sprites[i]; if ('runner' !== sprite.type) { sprite.offset = this.platformOffset; // In step with platforms } } }, ... };
draw()
方法为所有 sprite(除了跑步小人)设置平台速率以及平移位移。(跑步小人的水平位置是固定的,不会随着平台的移动而移动。)
设置平移位移以及绘制背景之后,draw()
方法使用 updateSprites()
和 drawSprites()
更新并绘制游戏 sprite。这些方法如清单 16 所示:
清单 16. 更新和绘制 sprite
SnailBait.prototype = { ... updateSprites: function (now) { var sprite; for (var i=0; i < this.sprites.length; ++i) { sprite = this.sprites[i]; if (sprite.visible && this.spriteInView(sprite)) { sprite.update(now, this.fps); } } }, drawSprites: function() { var sprite; for (var i=0; i < this.sprites.length; ++i) { sprite = this.sprites[i]; if (sprite.visible && this.spriteInView(sprite)) { this.context.translate(-sprite.offset, 0); sprite.draw(this.context); this.context.translate(sprite.offset, 0); } } }, spriteInView: function(sprite) { return sprite === this.runner || // runner is always visible (sprite.left + sprite.width > this.platformOffset && sprite.left < this.platformOffset + this.canvas.width); },
updateSprites()
和 drawSprites()
对所有游戏 sprite 都可进行迭代,然后分别更新和绘制 sprite,但只有 sprite 是可见的,或者出现在目前显示的画布中。
在绘制 sprite 之前,drawSprites()
方法使用 setTranslationOffsets()
方法中计算的 sprite 位移来平移背景,然后将背景平移回原始位置,使 sprite 看起来像是在进行水平运动。
结束语
在本文中,我们向您展示了如何实现 sprites 和 sprite artist,以及如何将 sprite 合并到游戏循环中,在下一期 HTML5 2D 游戏开发 系列文章中,您将学习如何实现 sprite 行为,以及将它附加到特定 sprite 中。下期见!
下载资源
- 样例代码 (j-html5-game4.zip | 3.9MB)
相关主题
- Core HTML5 Canvas:(David Geary,rentice Hall,2012 年):David Geary 的这本书对 Canvas API 和游戏开发进行了广泛介绍。您还可以查看 辅助专用网站和博客。
- Snail Bait:在任何支持 HTML5 的浏览器(最好是 Chrome V18 以上版本)上在线玩 Snail Bait。
- 令人兴奋的应用程序和 HTML5 Canvas:观看 David Geary 的 Strange Loop 2011 演讲。
- HTML5 Game Development:在 Norwegian Developer's Conference (NDC) 2011 观看 David Geary 的演讲。
- 平台游戏:在 Wikipedia 上通过阅读了解平台游戏。
- Side-scroller 视频游戏:在 Wikipedia 上通过阅读了解 side-scroller 视频游戏。
- Strategy 模式:阅读关于 Strategy 设计模式的 Wikipedia 文章。
- 游戏开发人员工具:在线运行 sprite 表单检查器。
- Replica Island:您可以下载这个面向 Android 的流行开源平台视频频游的资源。