HTML5 2D 游戏开发(三): 设置舞台

实现游戏对象、暂停、冻结、解冻和键盘输入

在本系列文章中,HTML5 专家 David Geary 将向您展示如何逐步实现一个 HTML5 2D 视频游戏。在本期文章中,您将学习如何将游戏代码封装在一个对象中,如何实现暂停和取消暂停,以及如何使用 CSS3 过渡来实现游戏重启的倒计时。

David Geary, 作者和演讲家, Clarity Training, Inc.

2013 年 1 月 21 日

游戏开发的许多方面都和玩游戏没有关系。显示说明、暂停游戏、级别之间的过渡和滚动游戏分数,这些都是游戏开发人员必须在游戏本身以外实现的一些特性。

当游戏的灵感来临时,这些灵感中通常不包括显示高分数或级别之间的过渡的巧妙方式,开发人员会很自然地深入研究如何实现游戏机制,但对于游戏的基础架构却没有太多的想法。但在大多数项目中,如果想在开发后添加功能,所需的工作量比从一开始就添加功能要大得多。

在本系列的 上一期文章 中,我讨论了图形和动画,这些是 Snail Bait 游戏的基础内容。在本文中,我将临时转向去实现该游戏的一些基础架构。首先,我将 Snail Bait 的代码封装在一个 Game 对象中。最初实现该游戏时,我就是从这一步开始的,但在上一期 文章中,我不想在对象中实现它们而混淆了对图形和动画的讨论,所以我将对 Game 对象的介绍推迟到了现在。

我还将告诉您如何暂停和冻结 Snail Bait,以及随后如何利用动画倒计时解冻并重启游戏。在文章的结尾,我会回到游戏机制的主题,向您展示如何通过处理键盘事件来控制跑步小人的垂直位置。

在本文中,您将学习以下内容:

  • 将游戏函数封装在一个对象中。

  • 暂停和恢复游戏。

  • 当窗口失去焦点时自动暂停游戏。

  • 当窗口重新获得焦点时,利用动画的倒计时继续游戏。

  • 暂时显示给用户的消息(被称为 toast)。

  • 处理键盘输入。

在本文中,您将学习如何定义和实例化 JavaScript 对象,如何使用 CSS3 过渡,以及如何结合使用 setTimeout() 和这些过渡来实现分步动画。

游戏对象

在本系列文章中,到现在为止,我已经实现了所有 Snail Bait 函数,并将它们的几个变量定义为全局变量。当然,我们以后不会再这样做。如果您尚未了解全局变量的可恶之处,请参阅 参考资料,获得来自 Douglas Crockford 和 Nicholas Zakas 等 JavaScript 名人的支持论据。

从现在开始,我不再使用全局变量,而是将所有 Snail Bait 函数和变量封装在一个对象中。该对象由两部分组成,如清单 1 和清单 2 所示。(本文的完整样例代码请参见 下载。)

清单 1 是本游戏的构造函数,它定义了对象的属性:

清单 1. 本游戏的构造函数(部分清单)

var SnailBait = function (canvasId) {
this.canvas = document.getElementById(canvasId);
this.context = this.canvas.getContext(‘2d’);

// HTML elements

this.toast = document.getElementById(‘toast’),
this.fpsElement = document.getElementById(‘fps’);

// Constants

this.LEFT = 1;
this.RIGHT = 2;

// Many more attributes are defined in the rest of this function
};

清单 2 是本游戏的原型,它定义了对象的方法:

清单 2. 是本游戏的原型(部分清单)

SnailBait.prototype = {
// The draw() and drawRunner() methods were
// discussed in the second article in this series.

draw function (now) {
this.setPlatformVelocity();
this.setOffsets();

  this.drawBackground();

  this.drawRunner();
  this.drawPlatforms();

},

drawRunner: function () {
this.context.drawImage(this.runnerImage,
this.STARTING_RUNNER_LEFT,
this.calculatePlatformTop(this.runnerTrack) - this.RUNNER_HEIGHT);
},

// Many more methods are defined in the rest of this object
};

为了在整个系列中添加一些新特性,我需要添加和删除一些方法,以及修改一些方法来实现。表 1 列出了 Snail Bait 的方法,因为它们会在本文结尾处出现:

表 1. Snail Bait 在此开发阶段的方法(按调用顺序列出)

方法

描述

initializeImages()

初始化游戏的图像。背景图像的 onload 事件处理器调用 start()。

start()

通过调用 requestAnimationFrame() 启动游戏,在可以绘制第一个动画帧的时候,它调用了 animate() 方法。

splashToast() [1]

向玩家显示一个临时消息。

animate() [2]

如果游戏没有暂停,此方法将会绘制下一个动画帧,并调用 requestNextAnimationFrame() 来安排 animate() 的另一次调用。如果游戏暂停,那么 animate() 会等待 200 毫秒,然后调用 requestNextAnimationFrame()。

calculateFps()

根据自最后一个动画帧起所经过的时间,计算帧速率。

draw()

绘制一个动画帧。

setTranslationOffsets()

为背景和平台设置过渡偏移。

setBackgroundTranslationOffset()

根据当前时间设置背景过渡偏移。

setPlatformTranslationOffset()

根据当前时间设置平台过渡偏移。

setPlatformVelocity()

将平台速度设置为背景速度的倍数,以产生轻微的视差效果。

drawBackground()

平移 Canvas 坐标系统,绘制背景两次,并将坐标系平移回它的原始位置。

drawRunner() [3]

使用 drawImage() 绘制跑步小人。

drawPlatforms() [3]

使用 2D 上下文的 strokeRect() 和 fillRect() 绘制矩形平台。

calculatePlatformTop()

针对给定轨道计算平台顶部的 Y 坐标(平台在三个水平轨道之一上移动)。

turnLeft()

将背景和平台向右滚动。

turnRight()

将背景和平台向左滚动。

togglePaused() [1]

切换游戏的暂停状态。

[1] 在本文中介绍
[2] 由浏览器调用
[3] 在本系列的下一期文章中将被替换

函数与方法

作为某个对象的成员 JavaScript 函数被称为方法,而独立的函数被简单地称为函数。

在本系列前两期文章中,我介绍过在 表 1 中列出的大部分方法,它们在当时只是函数。在本文中,我会讨论两个新的方法:togglePaused() 和 splashToast(),还会修改其他方法,比如 animate()。

清单 1 和 清单 2 中的 JavaScript 定义了一个函数和一个原型,但没有实例化一个 SnailBait 对象。我会在下一节中完成此操作。

启动游戏

SnailBait 的全局对象

如 清单 1 和 清单 3 所示,Snail Bait 只有两个全局对象:SnailBait 函数和 snailBait 对象。

清单 3 显示了启动游戏的 JavaScript。该清单的开头定义了三个 SnailBait 方法的实现:animate()、start() 和 initializeImages()。

清单 3. 启动

SnailBait.prototype = {

// The ‘this’ variable in the animate() method is
// the window object, so the method uses snailBait instead

animate: function (now) {
snailBait.fps = snailBait.calculateFps(now);
snailBait.draw(now);

  requestNextAnimationFrame(snailBait.animate);

},

start: function () {
this.turnRight(); // Sets everything in motion
this.splashToast(‘Good Luck!’, 2000); // “Good Luck” is displayed for 2 seconds

  requestNextAnimationFrame(this.animate);

},

initializeImages: function () {
this.background.src = ‘images/background_level_one_dark_red.png’;
this.runnerImage.src = ‘images/runner.png’;

  this.background.onload = function (e) {

    // ...the 'this' variable is the window object,
    // so this function uses snailBait instead.

    snailBait.start();
  };

},
}; // End of SnailBait.prototype

// Launch game

var snailBait = new SnailBait(); // Note: By convention, the object
// reference starts with lowercase, but
// the function name starts with uppercase

snailBait.initializeImages();

JavaScript 的挑剔的 this 对象

如果您曾使用过经典的面向对象的语言(如 Java),那么您应该预料到,某个对象的 this 变量总是指向与方法相关联的对象。

JavaScript 最麻烦的一件事情是,this 变量是变化的。在清单 2 中,animate() 方法和背景图像的 onload 事件处理器中的 this 变量涉及 window 对象,而不是 snailBait 对象,所以这些方法可以直接访问 snailBait 对象。

清单 3 中的 JavaScript 实例化一个 SnailBait 对象,并调用了它的 initializeImages() 方法,该方法实现背景图像的 onload 事件处理器。当图像加载时,该事件处理器调用了 start() 方法。

start() 方法调用 turnRight(),它设置移动中的背景和平台。还调用了 splashToast(),以显示 Good Luck! 两秒。最后,start() 调用了 requestNextAnimationFrame() polyfill(我们曾在本系列第二期文章中讨论过它,请参阅那篇文章的 A requestAnimationFrame() polyfill 部分),它最终调用该游戏的 animate() 方法。

animate() 方法将绘制当前帧,然后调用 requestNextAnimationFrame()(指定它本身作为回调函数)来保持动画。

这就是游戏开始的方式。接下来,我将告诉您在开始游戏之后如何暂停它。

暂停游戏

HTML5 游戏(尤其是视频游戏)必须能够暂停。在清单 4 中,我已经修改了 Snail Bait 的游戏循环,以便暂停和取消暂停游戏:

清单 4. 暂停和取消暂停

var SnailBait = function (canvasId) {

this.paused = false,
this.PAUSED_CHECK_INTERVAL = 200; // milliseconds

};

SnailBait.prototype = {
animate: function (now) {
if (snailBait.paused) {

    // Check again in snailBait.PAUSED_CHECK_INTERVAL milliseconds

    setTimeout( function () {

        requestNextAnimationFrame(snailBait.animate);

    }, snailBait.PAUSED_CHECK_INTERVAL);
  }
  else {

    // The game loop from Listing 1

    snailBait.fps = snailBait.calculateFps(now);
    snailBait.draw(now);
    requestNextAnimationFrame(snailBait.animate);
  }

},

togglePaused: function () {
this.paused = !this.paused;
},
};

togglePaused() 方法简单地切换该游戏的 paused 变量。当该变量为 true (意味着游戏已暂停)时,animate() 方法不会执行游戏循环。

每秒检查 60 次(假设帧速率为 60fps),查看是否应该恢复某个暂停的帧,这是没必要的,且效率低下,因此,清单 4 中的 animate() 方法只会等待 200ms,然后调用 requestNextAnimationFrame() polyfill,在到时间绘制下一个动画帧时,它就会安排对 animate() 的另一次调用。

当窗口失去焦点时自动暂停

W3C 的 Timing control for script-based animations(基于脚本的动画时序控制) 规范对于使用 requestAnimationFrame() 实现的动画具有下列规定:

如果页面目前不可见,可以大幅裁剪该页面上的动画,那么就不会经常更新这些页面,因此只会占用极少的 CPU 资源。

术语大幅节流意味着,浏览器以极低的帧速率(通常介于 1 到 10 fps 之间)调用您的动画回调,如图 1 所示,帧速率在窗口重新获得焦点后立即达到 6fps:

图 1. 在失去焦点和重新获得焦点后的 Snail Bait
这里写图片描述

大幅节流的帧速率可能对碰撞检测算法造成破坏,该算法通常基于帧速率确定是否发生了(或将发生)碰撞。您可以在游戏的窗口失去焦点时暂停游戏,并在窗口重新获得焦点时重新启动它,避免因大幅节流的帧速率而造成碰撞检测的崩溃。在清单 5 中,您可以看到如何做到这一点:

清单 5. 自动暂停

window.onblur = function () { // window looses focus
if (!snailBait.paused) {
snailBait.togglePaused();
}
};

window.onfocus = function () { // window regains focus
if (snailBait.paused) {
snailBait.togglePaused();
}
};

当窗口失去焦点时,您不仅要暂停游戏,还应该在暂停游戏时冻结 它。

冻结游戏

暂停游戏所涉及的不仅仅是简单地中止动画。游戏应该恢复到离开它们时的确切位置。 清单 4 的出现满足了这一要求。毕竟,在游戏暂停后,什么也不会发生,就好像游戏已经恢复为和它暂停之前一样。但实际情况并非如此,因为所有动画(包括 Snail Bait 的动画)的主要参数是时间。

如我在第二期文章中所讨论的(请参阅这篇文章的 requestAnimationFrame() 部分),requestAnimationFrame() 将时间传递给指定的回调函数。在使用 Snail Bait 的情况下,该回调函数是 animate() 方法,它随后会将时间传递给 draw() 方法。

当游戏暂停时,即使动画没有运行,时间依然有增无减地继续前进。而且,由于 Snail Bait 的 draw() 方法根据从 animate() 接收的时间绘制下一个动画帧,所以 清单 4 中实现的 togglePaused() 会导致在恢复暂停游戏时,游戏的时间寸步难行。

清单 6 显示了 Snail Bait 如何避免暂停的游戏在恢复时出现时间的变化:

清单 6. 冻结游戏

var SnailBait = function (canvasId) {

this.paused = false,
this.pauseStartTime = 0,
this.totalTimePaused = 0,
this.lastAnimationFrameTime = 0,

};

SnailBait.prototype = {

calculateFps: function (now) {
var fps = 1000 / (now - this.lastAnimationFrameTime);
this.lastAnimationFrameTime = now;

  if (now - this.lastFpsUpdateTime > 1000) {
    this.lastFpsUpdateTime = now;
    this.fpsElement.innerHTML = fps.toFixed(0) + 'fps';

  }

  return fps;

},

togglePaused: function () {
var now = +new Date();

  this.paused = !this.paused;

  if (this.paused) {
    this.pauseStartTime = now;
  }
  else {
    this.lastAnimationFrameTime += (now - this.pauseStartTime);
  }

},
};

在 清单 6 中,我修改了 Snail Bait 的 togglePaused() 和 calculateFps() 方法,以计算游戏被暂停(如果有的话)的时间量。

为了计算前一个动画帧的帧速率,我从当前时间减去我绘制最后一帧的时间,并用 1000 被除以该值,这使得我获得的帧速率是以秒为单位,而不是以毫秒为单位。(请参阅第二期文章的 以 fps 计算动画速率 部分,了解关于计算帧速率的更多信息。)

当游戏恢复时,我将游戏暂停的时间量加到最后一个动画帧的时间。这个加法有效地清除了暂停,游戏恢复到在暂停开始时它的确切位置。

当窗口获得焦点时解冻游戏

当游戏恢复时,玩家会喜欢平稳地过渡到操作,为他们提供一些时间来重新获得控制。在这段时间里,在恢复游戏之前提供有关剩余时间量的反馈,这会是一个好主 意。Snail Bait 通过在 toast 中显示倒计时来实现该反馈,所以我从 toast 的概述开始这个讨论。

Toast

toast 是游戏暂时向玩家显示的某些消息,像图 2 中的 Good Luck! toast:

图 2. toast
这里写图片描述

像 Snail Bait 本身,Snail Bait toast 是使用 HTML、CSS 和 JavaScript 的组合来实现的,如接下来的三个清单所示。

清单 7 显示一个 toast 的 HTML 代码:

清单 7. toast:HTML







    <div id='toast'></div>
    ...

  </div>
  ...


实现 Snail Bait 的 Good Luck! toast 的 CSS 如清单 8 所示:

清单 8. toast:CSS

toast {

position: absolute;

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

opacity: 0;
z-index: 1;
display: none;
}

清单 9 显示 Good Luck! toast 的 JavaScript:

清单 9. toast:JavaScript

var SnailBait = function () {

this.toast = document.getElementById(‘toast’),
this.DEFAULT_TOAST_TIME = 3000, // 3 seconds

};

SnailBait.prototype = {

start: function () {

snailBait.splashToast(‘Good Luck!’);
},

splashToast: function (text, howLong) {
howLong = howLong || this.DEFAULT_TOAST_TIME;

  toast.style.display = 'block';
  toast.innerHTML = text;

  setTimeout( function (e) {
    toast.style.opacity = 1.0; // After toast is displayed
  }, 50);

  setTimeout( function (e) {
    toast.style.opacity = 0; // Starts CSS3 transition

    setTimeout( function (e) {
        toast.style.display = 'none'; // Just before CSS3 animation concludes
    }, 480);
  }, howLong);

},

}

正如前面这三个清单中的实现,toast 只是 DIV,您可以在 清单 7 中看到。事情在 清单 8 中变得更为有趣,其中列出了 DIV 的 CSS。DIV 的位置是 absolute,这意味着它可以显示在其他 DIV 的上面或下面,而不是前面或后面。toastDIV 的 z-index 值也是 1,这意味着它始终显示在游戏画布的上面,其 z-index 的默认值为 0。最后,toast 元素的 CSS 定义一个绑定到 opacity 属性的 0.5 秒的过渡,当更改该属性时,CSS 用 0.5 秒时间将 DIV 从之前的不透明度平滑过渡到新的值。

在 清单 9 的 splashToast() 方法中,事情变得更加有趣,toast 会在一段指定的时间内显示。当 Snail Bait 调用 splashToast() 时,默认的显示时间为 3 秒,toast 淡入 0.5 秒,短暂地显示 2.5 秒,然后淡出 0.5 秒。下面是它的工作原理:

splashToast() 方法首先将 toastDIV 的 display 属性设置为 block,这通常会使得 DIV 变得可见。但是,因为它的 opacity 属性的初始值为 0,所以 toastDIV 仍保持不可见。然后 splashToast() 将 toastDIV 的内部 HTML 设置为您传递给方法的文本,但不透明度设置保持不变,所以设置文本也不会使得 DIV 可见。

为了使得 toastDIV 可见,我将它的不透明度设置为 1.0。该设置触发因我在 清单 8 中指定的过渡而产生的 CSS3 动画,但是,仅当不透明度设置稍后(在本例中是 50 毫秒)产生看起来很奇怪的 setTimeout() 的结果时,才会修改不透明度设置,在该函数中它是封闭的。这是因为:

只能对有中间状态的元素属性指定 CSS3 过渡。例如,如果您将不透明度从 0.2 修改为 0.3(随机选择的两个数字),中间不透明度为 0.21、0.22 等。

过渡需要中间状态,这是有一定道理的;如果没有中间状态,就没有一个明确的方法来指定过渡的动画。例如,这就是为什么您不能为 display 属性指定一个过渡的原因,它没有中间状态。不仅如此,如果您修改了 display 属性,那么 CSS3 将不再接受您为其他任何属性指定的任何过渡。这也是有一定道理的,因为您在让 CSS3 执行互相冲突的两件事:例如,通过修改 display 属性让元素立即可见,但又使用 opacity 属性的过渡使其慢慢地淡入视图。CSS3 不能两件事同时做,所以它选择了修改 display 属性。

半透明的 DIV 和事件

经过前面关于 splashToast() 的讨论之后,您可能想知道为什么该方法要这么麻烦地操作 toastDIV 的 display 属性。为什么不直接操纵的 DIV 的不透明度使其变得可见或不可见呢?答案是,除非您明确地有意这样做,让不可见的 DIV 悬停在那里,否则这并不是一个好主意,因为它们很可能以其他令人惊讶的方式(如拦截事件)来显示它们的存在。

如果 splashToast() 同时设置 toastDIV 的 display 和 opacity 属性,CSS3 会忽略不透明度的过渡,因此,在设置 display 属性之后,该方法会将不透明度设置为 1.0,更确切地说,在大约 50ms 之后会执行该操作。

最后,当所需的显示时间过去后,splashToast() 会将 toastDIV 的 opacity 属性重新设置为 0,这会再次触发一个 0.5 秒的 CSS3 动画。在 CSS3 动画开始两秒钟后之后,splashToast() 方法会将 display 属性重新设置为 0。

解冻 Snail Bait

当 Snail Bait 在暂停后恢复播放时,它通过三秒钟的倒计时让玩家有时间做好准备,如图 3 所示:

图 3. 解冻过程中的倒计时
这里写图片描述

清单 10 显示了倒计时的 JavaScript:

清单 10. 倒计时:JavaScript

var SnailBait = function (canvasId) {

this.toast = document.getElementById(‘toast’),
};

window.onblur = function (e) { // Pause if unpaused
if (!snailBait.paused) {
snailBait.togglePaused();
}
};

window.onfocus = function (e) { // unpause if paused
var originalFont = snailBait.toast.style.fontSize;

if (snailBait.paused) {
snailBait.toast.style.font = ‘128px fantasy’;

  snailBait.splashToast('3', 500); // Display 3 for one half second

  setTimeout(function (e) {
    snailBait.splashToast('2', 500); // Display 2 for one half second

    setTimeout(function (e) {
        snailBait.splashToast('1', 500); // Display 1 for one half second

        setTimeout(function (e) {
          snailBait.togglePaused();

          setTimeout(function (e) { // Wait for '1' to disappear
              snailBait.toast.style.fontSize = originalFont;
          }, 2000);
        }, 1000);
    }, 1000);
  }, 1000);

}
};

当 Snail Bait 窗口重新获得焦点时,它会使用 splashToast() 方法启动倒计时。每个数字淡入 0.5 秒,然后淡出 0.5 秒。一旦倒计时为零,onfocus 处理器就会重新启动游戏。

然而,如果玩家在倒计时过程中激活了另一个窗口或选项卡,清单 10 中的代码就会无法正常工作,因为无论窗口是否获得焦点,游戏都会在倒计时结束时重新开始。这很容易解决,利用一个 windowHasFocus 标实即可,如清单 11 所示:

清单 11. 在倒计时期间失去焦点的处理

var SnailBait = function (canvasId) {

this.windowHasFocus = true,

};

SnailBait.prototype = {

splashToast: function (text, howLong) {
howLong = howLong || this.DEFAULT_TOAST_TIME;

  toast.style.display = 'block';
  toast.innerHTML = text;

  setTimeout( function (e) {
    if (snailBait.windowHasFocus) {
        toast.style.opacity = 1.0; // After toast is displayed
    }
  }, 50);

  setTimeout( function (e) {
    if (snailBait.windowHasFocus) {
        toast.style.opacity = 0; // Starts CSS3 transition
    }

    setTimeout( function (e) {
        if (snailBait.windowHasFocus) {
          toast.style.display = 'none';
        }
    }, 480);
  }, howLong);

},

};

window.onblur = function (e) { // pause if unpaused
snailBait.windowHasFocus = false;

if (!snailBait.paused) {
snailBait.togglePaused();
}
};

window.onfocus = function (e) { // unpause if paused
var originalFont = snailBait.toast.style.fontSize;

snailBait.windowHasFocus = true;

if (snailBait.paused) {
snailBait.toast.style.font = ‘128px fantasy’;

  snailBait.splashToast('3', 500); // Display 3 for one half second

  setTimeout(function (e) {
    snailBait.splashToast('2', 500); // Display 2 for one half second

    setTimeout(function (e) {
        snailBait.splashToast('1', 500); // Display 1 for one half second

        setTimeout(function (e) {
          if ( snailBait.windowHasFocus) {
              snailBait.togglePaused();
          }

          setTimeout(function (e) { // Wait for '1' to disappear
              snailBait.toast.style.fontSize = originalFont;
          }, 2000);
        }, 1000);
    }, 1000);
  }, 1000);

}
};

键盘输入

在 Snail Bait 中,玩家可以使用键盘来控制跑步小人,所以我会对该游戏如何处理键盘输入做一个简短的讨论,并以此来结束这篇文章。d 和 k 键可以让移动的跑步小人向左和向右移动,而 j 和 f 则分别让跑步小人跳跃和下落。图 4 显示了跳上第三个平台的轨道之后的跑步小人:

图 4. 在轨道之间跳跃之后的奔跑
这里写图片描述

您只能将键盘事件侦听器添加到 focusable HTML 元素。canvas 元素是不能获得焦点的,所以 Snail Bait 将一个 onkeydown 事件处理器添加到 window 对象,如清单 12 所示:

清单 12. 对键盘输入的反应

var runnerTrack = 1,
BACKGROUND_VELOCITY = 42;

function turnLeft() {
bgVelocity = -BACKGROUND_VELOCITY;
}

function turnRight() {
bgVelocity = BACKGROUND_VELOCITY;
}

window.onkeydown = function (e) {
var key = e.keyCode;

if (key === 80 || (paused && key !== 80)) { // p
togglePaused();
}

if (key === 68 || key === 37) { // d or left arrow
turnLeft();
}
else if (key === 75 || key === 39) { // k or right arrow
turnRight();
}
else if (key === 74) { // j
if (runnerTrack === 3) {
return;
}
runnerTrack++;
}
else if (key === 70) { // f
if (runnerTrack === 1) {
return;
}
runnerTrack–;
}
};

重要的是要认识到,Snail Bait 的游戏循环在不断运行。在浏览器已经准备好画下一个动画帧时,浏览器就会调用 animate() 函数,而 animate() 反过来又不断地调用 draw()(在清单 2 中列出)。

由于游戏循环在不断运行,键盘事件处理器只需设置游戏的变量即可。例如,当您按下 k 将跑步小人向右移动,事件处理器会将 bgVelocity 设置为 BACKGROUND_VELOCITY = 42(像素/秒),当您按下 d 将跑步小人向左移动,事件处理器会将 bgVelocity 设置为 -42 像素/秒。一直到后来,当游戏绘制下一个动画帧时,那些设置才会生效。

结束语

在本系列的下一篇文章中,我将向您演示如何将 Snail Bait 的插入图形转换成被称为精灵 (sprite) 的动画对象。您将看到如何采用几种不同的方式绘制精灵,包括从一个精灵表 (spritesheet) 绘制它们,并且您将看到如何将它们纳入 Snail Bait 的现有代码中。下次再见!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值