HTML5 2D 游戏开发: Snail Bait 简介

开始您的第一个平台视频游戏

在本系列中,HTML5 专家 David Geary 将向您展示如何逐步实现一个 HTML5 2D 视频游戏。第一期的文章将向您展示一个已完成的游戏,然后引导您从头开始实现它。如果您曾经想要创建一个 HTML5 游戏,但又没时间掌握所有细节,那么本系列非常适合您。

David Geary, 总裁, Clarity Training, Inc.

David Geary 的照片David Geary 是一名作家、演讲家和顾问,也是 Clarity Training, Inc. 的总裁,他指导开发人员使用 JSF 和 Google Web Toolkit (GWT) 实现 Web 应用程序。他是 JSTL 1.0 和 JSF 1.0/2.0 专家组的成员,与人合作编写了 Sun 的 Web Developer 认证考试的内容,并且为多个开源项目作出贡献,包括 Apache Struts 和 Apache Shale。David 的 Graphic Java Swing 一直是关于 Java 的畅销书籍,而 Core JSF(与 Cay Horstman 合著)是关于 JSF 的畅销书。他还是 GWT Solutions 一书的作者。David 经常在各大会议和用户组发表演讲。他从 2003 年开始就一直是 NFJS tour 的定期演讲人,并且在 Java University 教授课程,三次当选为 JavaOne 之星。



2012 年 10 月 18 日

软件开发的优势在于:在合理的范围内让任何您能想象到的事物在屏幕上变成现实。不像其他领域受一些物理条件的束缚,软件开发人长期以来一直使用图形化 API 和 UI 工具包来实现富有创造性和引人注目的应用程序。最富有创造性的软件开发大概是游戏编程;比起让游戏景愿变成现实,优秀的创意就可使您通过少许努力便可获得更多的收益。

平台视频游戏(Platform video games)

Donkey KongMario Bros.Sonic the HedgehogBraid 是众所周知的畅销游戏,它们都是平台游戏。平台游戏曾一度占据视频游戏销售的三分之一。今天,它们的市场份额大幅下降,但仍有许多成功的平台游戏。

然而,有益的并不意味着容易的;事实上,恰恰相反。实现游戏(特别是视频游戏)需要对编程有着非凡的理解,能够很好地把握抓图和动画,还需要掌握大量数学知识、艺术和创造力的结合使用。而这仅仅只是一个开头,成功的游戏开发人员需要花费大量时间来雕琢其游戏,改良游戏玩法和图像,除此之外,还要实现很多与游戏玩法无关的方面,比如记分牌、指令、生命和级别之间的动画以及残局序列 (endgame sequences)。

本系列旨在向您显示如何实现一个 HTML5 视频游戏,这样您就可以开始创建自己的游戏了。

游戏:Snail Bait

在本系列中,我首先会向您展示如何使用 HTML5 Canvas API 实现一个平台视频游戏。该游戏是 Snail Bait,如图 1 所示。您可以在线播放该游戏;请参阅 参考资料,以获取该游戏的链接。确保您的浏览器中有 Canvas 硬件加速器(最近刚刚在大多数浏览器上实现,包括 Chrome V18 以上的版本);否则,Snail Bait 性能会大幅降低(参阅 HTML5 Canvas 性能 侧栏,获取有关的详细信息。)

图 1. Chorme 上运行的 Snail Bait
Chorme 上运行的 Snail Bait 屏幕截图

Snail Bait 中使用的 HTML5 技术

  • Canvas (2D API)
  • 基于脚本的动画的定时控制
  • Audio
  • CSS3(转换和媒体查询)

Snail Bait 是一款经典的平台游戏,游戏的主人公,我通常将其简称为跑步小人,在水平移动的浮动平台奔跑跳跃。跑步小人的最终目标是到达一个有规律地跳动的平台,并在该级别结束时获得一枚黄金纽扣。图 1 中显示了跑步小人、有规律地跳动的平台和黄金纽扣。

玩家使用键盘控制跑步小人:d 向左移动,k 向右移动,jf 跳起,p 暂停游戏。

游戏开始时,您有 3 条生命。游戏画布左上方会显示代表剩余生命数的跑步小人图标,正如您在 图 1 中所看到的那样。在跑步小人达到该级别终点的过程中,她必须避开一些坏家伙(蜜蜂、蝙蝠和蜗牛),同时试着抓住贵重物品,比如硬币、红宝石和蓝宝石。如果跑步小人碰到坏家伙,就会流血并失去一条生命,并且您必须回到该级别的起点。当跑步小人碰到 “好人” 时,您的积分将会增加,并会听到一个令人愉悦的声音。

WASD?

按照惯例,计算机游戏通常使用 w、a、s 和 d 键来控制游戏。该惯例的形成主要是为了让右手玩家同时使用鼠标和键盘。也可以让右手自由地按下空格键和修饰键(比如 CTRL 或 ALT)。Snail Bait 没有使用 WASD,因为它无法从鼠标或修饰键接收输入。但是您可以通过修改游戏代码来使用任意组合键,这个很容易。

坏家伙通常只是在附近徘徊,等待跑步小人撞上它。但是,蜗牛会定期发射蜗牛炸弹(图 1 中心显示的银球),炸弹和其他坏家伙一样,碰到跑步小人就时会炸毁她。

结束游戏的方法有两种:您失去 3 次生命,或者您到达有规律地跳动的平台(击中黄金纽扣就会有奖励积分)。不管采用哪种方式,游戏结束积分均如图 2 所示:

图 2. 游戏积分
Snail Bait 残局序列的屏幕截图

您在 图 1 中无法看到的是:所有一切都在不停的滚动,除了跑步小人,她的运动由您来控制。因为有滚动,所以将 Snail Bait 归类为 side-scroller 平台游戏。虽然这不是游戏中惟一的动作,但它引导我发现了 sprite 及其行为。


sprite:演员表

HTML5 Canvas 性能

就在不久之前,大多数浏览器已经为 CSS 转换实现了硬件加速,但尚未对 Canvas 实现此转换。Canvas 发展一直相对较快,尤其在与其他图形系统(比如 Scalable Vector Graphics (SVG))相比时,但是没有硬件加速的 Canvas 是无法与有加速硬件的系统相匹敌的。

现在,所有现代浏览器都实现了 Canvas 元素硬件加速。iOS 5 也是如此,这意味着有着流畅动画的基于 Canvas 的视频游戏现在不仅可以出现在桌面系统上,还可以在移动设备上实现。

除了背景之外,Snail Bait 中的一切都是 sprite,一个 sprite 是一个您可以在游戏画布上绘画的对象。sprite 不是 Canvas API 的一部分,但是易于实现,该游戏的 sprite 是:

  • 平台(无生命对象)
  • 跑步小人(主角)
  • 蜜蜂和蝙蝠(坏)
  • 纽扣(好)
  • 红宝石和蓝宝石(好)
  • 硬币(好)
  • 蜗牛(坏)
  • 蜗牛炸弹(坏)

除了从右向左滚动之外,几乎所有的游戏 sprite 都有其自己独立的行为。例如,红宝石和蓝宝石以不同的速率上下跳动,纽扣和蜗牛沿着它们所在的平台来回徘徊。

Replica Island

sprite 行为(是设计模式的一个示例)的想法来自 Replica Island,这是一款流行的开源 Android platform 游戏。多数 Snail Bait 图形来自 Replica Island(已得到使用许可)。请参阅 参考资料,获取 Wikipedia 和 Replica Island 主页上的 Strategy 设计模式链接。

独立运动是诸多 sprite 行为之一。Sprite 可以具有与运动无关的其他行为;例如,除了上下跳动之外,红宝石和蓝宝石还会闪闪发光。

每个 sprite 都有一个行为阵列。行为只是一个具有 execute() 方法的对象。在每个动画帧中,游戏都会调用每个行为的 execute() 方法。在该方法中,行为以某种方式(具体情况视游戏条件而定)操控其相关 sprite。例如,您按下 k 键让跑步小人向右移动,跑步小人的横向移动行为随后会在每个动画帧中将跑步小人向右移动,直至更改其方向。另一个行为是在正确位置跑动,该行为会定期改变跑步小人图像,使得跑步小人看起来像是在正确位置上跑动。这两个行为组合在一起使得跑步小人看起来似乎正在向左或向右跑动。

表 1 列出了游戏的 sprite 及其相应行为:

表 1. Snail Bait 的 sprite 和行为
Sprite行为
平台
  • 水平移动(所有 sprite,除了跑步小人和蜗牛炸弹,都以与平台一致的方式移动)
跑步小人
  • 在正确位置跑动
  • 横向移动
  • 跳跃
  • 坠落
  • 与坏家伙相撞并爆炸
  • 碰到 “好人” 并得分
蜜蜂和蝙蝠
  • 盘旋
  • 扇动翅膀
纽扣
  • 踱步
  • 倒塌
  • 变化:使坏家伙爆炸并结束游戏
硬币、红宝石和蓝宝石
  • 闪耀
  • 上下跳动
  • 踱步
蜗牛
  • 踱步
  • 射击炸弹
蜗牛炸弹
  • 从右向左移动(比平台快)
  • 撞到跑步小人并消失

本系列的后续文章将深入研究 sprite 和 sprite 行为。现在,为了向您提供一个高度概述,清单 1 展示了该游戏如何创建 runner sprite:

清单 1. 创建 sprite
var runInPlace = {  // Just an object with an execute method
   execute: function (sprite, time, fps) {
      // Update the sprite's attributes based on the time and frame rate
   }
};

var runner = new Sprite('runner', // name
                        runnerPainter, // painter
                        [ runInPlace,... ]); // behaviors

runInPlace 对象是在包含其他行为的数组中定义的,并给传递给跑步小人 sprite 的构造函数。在运行的时候,该游戏调用每个动画帧的 runInPlace 对象的 execute() 方法。


HTML5 游戏开发最佳实践

免费的资源

大多数游戏开发者需要借助一些图形、声音效果和音乐提供帮助。幸运的是,各种许可协议下都有大量资产免费提供。

Snail Bait 使用了:

  • 来自 freesound.org 的声音效果
  • 来自 soundclick.com 的影视原声
  • 来自 panelmonkey.org(网站已被攻击)的跑步小人 sprite
  • 来自 Replica Island 的其他图形

我将在本系列中讨论游戏开发最佳实践,在这里,从特定于 HTML5 的五个最佳实践开始:

我将在本系列的后续文章中对这 5 个最佳实践进行详细讨论;现在我们来快速浏览其中的每个最佳实践。

1. 窗口中失去焦点时暂停游戏

如果在浏览器中运行某个 HTML5 游戏,当您将焦点转移到另一个选项卡或者浏览器窗口时,大多数浏览器会固定游戏动画运行的帧速率,以节省 CPU 和电池源等资源。反过来,帧速率固定总是对碰撞检测算法造成严重干扰,该算法可以使得该游戏以最小帧速率运行。为了避免帧速率限制以及随之而来的碰撞检测崩溃,当窗口中失去焦点时,应该自动停止游戏。

2. 窗口再次获得焦点时实现倒计时

当您的游戏窗口再次获得焦点时,给用户几秒钟的准备时间来重启游戏是一个不错的主意。窗口再次获得焦点时,Snail Bait 使用了 3 秒钟的倒计时,如 图 3 所示:

图 3. Snail Bait 自动暂停
Snail Bait 自动暂停屏幕截图:背景上有一个大大的 3

3. 使用 CSS3 转换

图 4 是加载游戏后的屏幕截图:

图 4. CSS3 效果
Snail Bait 中 CSS3 效果的屏幕截图

图 4 中有两件事需要注意。第一个值得注意的是一个 toast,它是一些向玩家临时显示的事物,是可见的,比如说 Good luck!。当加载游戏时,该 toast 会先淡入,然后在五秒后淡出。第二,注意游戏画布之下的复选框(用于声音和音乐)和操作说明(告诉您哪些击键执行哪些功能)。游戏开始时,复选框和操作说明都是完全透明的,正如 图 4 所示;玩家开始之后,这些元素慢慢淡出,最后几乎看不见(如 图 3 所示),这样就不会分散玩家的注意力。

Snail Bait 通过 CSS3 变换使元素变暗并使 toasts 消失。

4. 对慢速运行的游戏进行检测并作出反应

和那些运行在严格控制环境下的控制台游戏不一样,HTML5 游戏在一个高度可变的、不可预测的混乱环境中运行。对于您的游戏而言,当玩家在另一个选项卡上播放 YouTube 视频时,或者在过度使用 CPU 或 GPU 时,游戏运行会变得异常慢,这屡见不鲜。或者也有可能您的玩家使用了一个跟不上节奏的浏览器。

作为一个游戏开发者,您必须能够预见这些不幸的事件并作出相应的反应。Snail Bait 不断监控帧速率,当它检测到在很多秒内帧速率多次跌破特定阈值的时候,会显示运行太慢,如图 5 所示:

图 5. 缓慢的每秒帧数检测
屏幕截图:toast 警告 Snail Bait 运行太慢,建议用户使用硬件加速的 HTML5 Canvas 来升级浏览器,并建议用户选择禁用后续 toast 显示

5. 融合社交特性

几乎所有的成功游戏都融合了一些社交因素,比如在 Twitter 或 Facebook 上发布得分。当 Snail Bait 玩家单击游戏结尾出现的 Tweet my score 链接时(参见 图 2),Snail Bait 会在另一个选项卡上打开 Twitter 链接并自动创建一条微博发布得分情况,如图 7 所示:

图 7. 微博文本
Snail Bait 自动生成的 tweet 文本的屏幕截图

现在,您对该游戏有了一个更高层次的理解,是时候看一些代码了。


Snail Bait 的 HTML 和 CSS

Snail Bait 代码统计

代码行数:

  • HTML:276
  • CSS:410
  • JavaScript:3,898

Snail Bait 是通过 HTML、CSS 和 JavaScript 实现的;但是,正如您从 Snail Bait code statistics 侧栏中看到的,大多数代码是 JavaScript。事实上,该系列的其余部分主要关注 JavaScript,只是偶尔谈及 HTML 和 CSS3。

图 8 显示了适合该游戏的 HTML 元素及其相应 CSS,忽略了其他元素的 HTML 和 CSS,比如 toasts 和积分:

图 8. 游戏的 HTML 和 CSS(省略了盒状阴影的说明)
带注释的屏幕截图显示了 Snail Bait 的 HTML 和 CSS(较少 toasts 和积分)

点击查看大图

图 8. 游戏的 HTML 和 CSS(省略了盒状阴影的说明)

带注释的屏幕截图显示了 Snail Bait 的 HTML 和 CSS(较少 toasts 和积分)

CSS 大多都很不起眼,除了我在 图 8 中高亮显示的那几个关注的属性。首先,我将 wrapper 元素的 margin 属性设置为 0 auto,这意味着包装器(以及其中包含的一起)在窗口中水平居中。其次,livessound-and-music 元素有一个 absolute 位置。如果采用了默认位置,即 relative,那么这些 relative 会扩展到与画布一样宽,它们的邻居(分别是记分和操作说明)将在它们之下运动。最后,keysexplanation CSS 类有一个 display 属性 inline,用于将相关元素放在同一行上。

清单 2 显示了 图 8 中的 CSS:

清单 2. game.css(摘录)
#arena {
   text-align: center;
   padding: 5px;
   width: 805px;
   height: 445px;
}

#copyright {
   margin-top: -35px;
   float: right;
   margin-right: 12px;
   padding: 2px;
   color: blue;
   text-shadow: 1px 1px 1px rgba(255,255,255,0.7);
   font-size: 0.8em;
}

.explanation {
   color: #ff0;
   text-shadow: 1px 1px 1px rgba(0,0,0,1.0);
   display: inline;
   margin-top: 5px;
   padding-right: 5px;
   padding-left: 5px;
   padding-bottom: 2px;
}
         
#game-canvas {
   border: 2px inset rgba(0,0,80,0.62);
   -webkit-box-shadow: rgba(0,0,0,0.5) 8px 8px 16px;
   -moz-box-shadow: rgba(0,0,0,0.5) 8px 8px 16px;
   -o-box-shadow: rgba(0,0,0,0.5) 8px 8px 16px;
   box-shadow: rgba(0,0,0,0.5) 8px 8px 16px;
}

#instructions {
   height: 30px;
   margin-right: 8px;
   padding-top: 6px;
   padding-left: 25px;

   -webkit-transition: opacity 2s;
   -moz-transition: opacity 2s;
   -o-transition: opacity 2s;
   transition: opacity 2s;

   color: #ff0;
   font-size: 1.05em;
   opacity: 1.0;
}

.keys {
   color: blue;
   text-shadow: 1px 1px 1px rgba(255,255,0,1.0);
   background: rgba(0,0,0,0.1);
   border: thin solid rgba(0,0,0,0.20);
   border-radius: 5px;
   margin-left: 10px;
   padding-right: 10px;
   padding-left: 10px;
   padding-bottom: 5px;
   display: inline;
}

#sound-and-music {
   position: absolute;
   top: 495px;
   margin-left: 10px;
   color: #ff0;
   text-shadow: 1px 1px 1px rgba(0,0,0,0.5);
   background: rgba(0,0,0,0.1);
   border-radius: 5px;
   border: thin solid rgba(0,0,0,0.20);
   padding-top: 2px;
   padding-bottom: 2px;
   z-index: 1;
}

#wrapper {
   margin: 0 auto;
   margin-top: 20px;
   padding: 5px;
   width: 817px;
   height: 520px;
}

正如您在清单 3 中看到的(清单 3 列出了 图 8 中显示的 HTML),该游戏的 HTML 是一堆 DIV 和画布,以及少许图像和几个复选框:

清单 3. game.html(摘录)
<!DOCTYPE html>
<html>
   <!-- Head........................................................-->

   <head>
     <title>Snail Bait</title>
   </head>

   <!-- Body........................................................-->

   <body>
      <!-- Wrapper..................................................-->

      <div id='wrapper'>
         <!-- Header.................................................-->

         <div id='header'>
            <div id='lives'>
               <img id='life-icon-left'   src='images/runner-small.png'/>
               <img id='life-icon-middle' src='images/runner-small.png'/>
               <img id='life-icon-right'  src='images/runner-small.png'/>
            </div>

            <div id='score'>0</div>
            <div id='fps'></div>
         </div>

         <!-- Arena..................................................-->

         <div id='arena'>
            <!-- The game canvas.....................................-->

            <canvas id='game-canvas' width='800' height='400'>
               Your browser does not support HTML5 Canvas.
            </canvas>

            <!-- Sound and music.....................................-->

            <div id='sound-and-music'>
               <div class='checkbox-div'>
                  Sound <input id='sound-checkbox'
                                  type='checkbox' checked/>
               </div>
   
               <div class='checkbox-div'>
                  Music <input id='music-checkbox' 
                                  type='checkbox' checked/>
               </div>
            </div>

            <!-- Instructions........................................-->

            <div id='instructions'>
               <div class='keys'>
                  d / k

                  <div class='explanation'>
                     move left/right
                  </div>
               </div>

               <div class='keys'>
                  f / j

                  <div class='explanation'>
                     jump
                  </div>
               </div>

               <div class='keys'>
                  p

                  <div class='explanation'>
                     pause

                  </div>
               </div>
            </div>

            <!-- Copyright...........................................-->

            <div id='copyright'> ©2012 David Geary</div>
         </div>
      </div>

      <!-- JavaScript................................................-->

      <script src='js/stopwatch.js'></script>
      <script src='js/animationTimer.js'></script>
      <script src='js/sprites.js'></script>
      <script src='js/requestNextAnimationFrame.js'></script>
      <script src='js/behaviors/bounce.js'></script>
      <script src='js/behaviors/cycle.js'></script>
      <script src='js/behaviors/pulse.js'></script>
      <script src='game.js'></script>
  </body>
</html>

canvas 元素是所有活动发生的地方。此画布有一个 2D 环境和一个功能强大的 API 来实现 2D 游戏,以及其他。canvas 元素中的文本是可退回文本,只有当该浏览器不支持 HTML5 Canvas 才显示。

关于该游戏 HTML 和 CSS 最后一个注意事项:注意画布的宽度和高度是通过 canvas 元素的 widthheight 属性来指定的。这些属性都是与 canvas 元素的大小以及 包含在此元素中的绘图表面的大小相关的。

另一方面,使用 CSS 设置 canvas 元素的宽度和高度,仅设置该元素的大小。绘图表面仍然保持其默认宽度和高度,分别为 300 和 150 像素。这意味着很可能 canvas 元素大小与绘图表面大小不匹配,而那时浏览器可能会调整绘图表面大小以适应该元素。大多数时间这会产生不利影响,因此绝不使用 CSS 设置 canvas 元素大小将是一个不错的主意。

在一个小画布上绘图然后通过 CSS 放缩?

一些游戏特意在一个小画布上绘制,然后使用 CSS 将画布扩大到适当大小。这样,您就不需要操作很多画布像素,这将会提高性能。通常,使用 CSS 进行画布扩展是硬件加速的,因此扩展的成本非常小。然而今天 — 由于几乎所有现代浏览器版本都附带有硬件加速 Canvas —。大多数情况下,这与在全尺寸画布上绘图一样快。

正如热映的电影 Pulp Fiction,您已经看到了故事结局。现在我们回到开始。


Snail Bait 的初始版本

图 9 显示了该游戏的起点,仅绘制了背景、平台和跑步小人。从一开始,平台和跑步小人都不是 sprite,相反是游戏直接绘制的。参阅 下载 部分获取创建背景和跑步小人的代码。

图 9. 绘制背景和跑步小人
Snail Bait 准备开始

清单 3 列出了该游戏 HTML 的起点,这只是 清单 2 的一个缩小版:

清单 3. game.html(初级版)
<!DOCTYPE html>
<html>
   <!-- Head.........................................................-->

   <head>
      <title>Snail Bait</title>
      <link rel='stylesheet' href='game.css'/>
   </head>

   <!-- Body.........................................................-->

   <body>
      <!-- Wrapper...................................................-->

      <div id='wrapper'>
         <!-- Header.................................................-->

         <div id='header'>
            <div id='score'>0</div>
         </div>

         <!-- Arena..................................................-->

         <div id='arena'>
            <!-- The game canvas.....................................-->

            <canvas id='game-canvas' width='800' height='400'>
               Your browser does not support HTML5 Canvas.
            </canvas>
         </div>
      </div>

      <!-- JavaScript................................................-->

      <script src='game.js'></script>
  </body>
</html>

清单 4 显示了 JavaScript:

清单 4. game.js(初始版)
// --------------------------- DECLARATIONS ----------------------------

var canvas = document.getElementById('game-canvas'),
    context = canvas.getContext('2d'),

   // Constants............................................................

   PLATFORM_HEIGHT = 8,  
   PLATFORM_STROKE_WIDTH = 2,
   PLATFORM_STROKE_STYLE = 'rgb(0,0,0)',

   STARTING_RUNNER_LEFT = 50,
   STARTING_RUNNER_TRACK = 1,

   // Track baselines
   //
   // Platforms move along tracks. The constants that follow define
   // the Y coordinate (from the top of the canvas) for each track.

   TRACK_1_BASELINE = 323,
   TRACK_2_BASELINE = 223,
   TRACK_3_BASELINE = 123,

   // Images

   background  = new Image(),
   runnerImage = new Image(),

   // Platforms
   //
   // Each platform has its own fill style, but the stroke style is
   // the same for each platform.

   platformData = [  // One screen for now
      // Screen 1.......................................................
      {
         left:      10,
         width:     230,
         height:    PLATFORM_HEIGHT,
         fillStyle: 'rgb(255,255,0)',
         opacity:   0.5,
         track:     1,
         pulsate:   false,
      },

      {  left:      250,
         width:     100,
         height:    PLATFORM_HEIGHT,
         fillStyle: 'rgb(150,190,255)',
         opacity:   1.0,
         track:     2,
         pulsate:   false,
      },

      {  left:      400,
         width:     125,
         height:    PLATFORM_HEIGHT,
         fillStyle: 'rgb(250,0,0)',
         opacity:   1.0,
         track:     3,
         pulsate:   false
      },

      {  left:      633,
         width:     100,
         height:    PLATFORM_HEIGHT,
         fillStyle: 'rgb(255,255,0)',
         opacity:   1.0,
         track:     1,
         pulsate:   false,
      },
   ];

// ------------------------- INITIALIZATION ----------------------------

function initializeImages() {
   background.src = 'images/background_level_one_dark_red.png';
   runnerImage.src = 'images/runner.png';

   background.onload = function (e) {
      startGame();
   };
}

function drawBackground() {
   context.drawImage(background, 0, 0);
}

function calculatePlatformTop(track) {
   var top;

   if      (track === 1) { top = TRACK_1_BASELINE; }
   else if (track === 2) { top = TRACK_2_BASELINE; }
   else if (track === 3) { top = TRACK_3_BASELINE; }

   return top;
}

function drawPlatforms() {
   var pd, top;

   context.save(); // Save context attributes on a stack
   
   for (var i=0; i < platformData.length; ++i) {
      pd = platformData[i];
      top = calculatePlatformTop(pd.track);

      context.lineWidth = PLATFORM_STROKE_WIDTH;
      context.strokeStyle = PLATFORM_STROKE_STYLE;
      context.fillStyle = pd.fillStyle;
      context.globalAlpha = pd.opacity;

      // If you switch the order of the following two
      // calls, the stroke will appear thicker.
      
      context.strokeRect(pd.left, top, pd.width, pd.height);
      context.fillRect  (pd.left, top, pd.width, pd.height);
   }

   context.restore(); // Restore context attributes
}

function drawRunner() {
   context.drawImage(runnerImage,
      STARTING_RUNNER_LEFT,
      calculatePlatformTop(STARTING_RUNNER_TRACK) - runnerImage.height);
}

function draw(now) {
   drawBackground();
   drawPlatforms();
   drawRunner();
}

function startGame() {
   draw();
}

// Launch game

initializeImages();

JavaScript 访问 canvas 元素,随后获取画布的 2D 上下文的引用。然后,该代码使用上下文的 drawImage() 方法来绘制背景和跑步小人图像。在本例中,我将使用 drawImage() 的由 3 个参数组成的变体,在画布中的特定 (x,y) 目的地上绘制图像。

在设置上下文的行宽度、描边风格、填充风格和全局 alpha 属性之后 ,drawPlatforms() 函数会通过对矩形进行描边和填充来绘制平台。请注意对 context.save()context.restore() 的调用:这些调用之间的属性设置都是临时的。我会在本系列的下一篇文章中讨论这些方法。

游戏从加载背景图像时开始。现在,开始开发游戏只需绘制背景、sprite 和跑步小人即可。下一个挑战是让这些静态图像动起来。


结束语

在本系列的下一篇文章中,我将从画布上下文的 2D API 的概述开始,然后讨论动画,通过滚动背景让一切都动起来。您将了解如何实现视差,让平台看起来比背景更近一些,还将了解如何确保您的 sprite 以固定速率运动,而不管动画的帧速率如何。下次再见!


下载

描述名字大小
Snail Bait 背景和跑步小人的代码j-html5-game1.zip718KB

参考资料

学习

获得产品和技术

  • Replica Island:您可以下载这个面向 Android 的流行开源平台视频频游的资源。

讨论

  • 加入 developerWorks 中文社区。浏览开发人员驱动的博客、论坛、组和维基,并与其他 developerWorks 用户进行交流。

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology, Open source, Web development
ArticleID=841159
ArticleTitle=HTML5 2D 游戏开发: Snail Bait 简介
publish-date=10182012