EnchantJS-HTML5-游戏编程教程-全-

EnchantJS HTML5 游戏编程教程(全)

原文:HTML5 Game Programming with enchant.js

协议:CC BY-NC-SA 4.0

零、简介

如果你正在读这篇文章,毫无疑问你对制作网络游戏有一定程度的兴趣。作为一个在这个领域有些经验的人,我觉得有资格告诉你,你来对地方了。

在过去的十年中,网络游戏发生了巨大的变化,虽然网络游戏的核心概念往往会随着时间的推移而缓慢变化,但我们用来创建它们的工具却变化得更快。在今天的 web 上,必须编写大量的代码才能在浏览器中完成简单的、与游戏相关的任务。

开源游戏引擎 enchant.js 通过大幅减少编写游戏所需的代码量来解决这一问题,并包括几个后台工作的后备和兼容性功能,以保持事情在多个浏览器之间顺利运行,而无需您做任何特殊的事情。在它的祖国日本,它已经成为一个非常受欢迎的工具,现在被越来越多的西方程序员使用。

如果你是 web 编码的初学者,不要担心!我们一步一步地向您介绍 enchant.js 的基本工作原理、JavaScript(enchant.js 使用的 web 通用脚本语言)以及 enchant . js 游戏的基本元素。然后我们转向更高级的话题。我们涵盖了一个游戏的所有必需品,包括场景、精灵、交互性等等。此外,我们为每个概念提供教程。在本书的第二部分,我们将向您展示如何创建几个游戏,包括一个打地鼠游戏(二维和三维)和一个经典的街机空间射击游戏。

虽然书中的一些代码示例链接到了免费编程环境code.9leap.net的工作版本,但通过搜索该标题并转到本书页面的源代码部分,可以在 Apress 网站(www.apress.com)上找到本书中的所有代码示例。

学习游戏编程是一个有趣和令人兴奋的冒险,有娱乐性的回报。如果您有关于该库的问题,请不要犹豫,通过官方 enchant.js subreddit ( reddit.com/r/enchantjs)、enchantjs.com或我们的脸书页面(搜索“enchant.js”)联系我们。我们的用户群体每天都在增长,既有全新的,也有经验丰富的游戏程序员。

祝你的游戏编程之旅好运!

布兰登·麦金尼斯

enchant.js 技术布道者

一、开始 enchant.js 开发

enchant.js 框架由位于东京的泛在娱乐公司(UEI)秋叶原研究中心开发,最初于 2011 年 4 月发布。从那以后,它在日本相当受欢迎,并拥有越来越多来自其他国家的粉丝。enchant.js 框架是一个基于 HTML5 和 JavaScript 的游戏引擎和独立代码库,使您能够开发可以在 PC 或 Mac 或 iPhone、iPad 和 Android 设备上运行的应用。虽然使用该引擎创建的游戏应用可以在许多不同类型的设备上运行,但大多数都是为智能手机的使用而创建和优化的。

近年来,Adobe Flash 作为交互平台的衰落导致现代网页游戏开发者转向其他基于浏览器的无处不在的平台,如 HTML5 和 JavaScript,为他们的用户创建基于浏览器的体验。然而,虽然 JavaScript 最初被开发为非专业开发人员可访问的语言,但是今天在浏览器中用于游戏创作的 JavaScript 的复杂性通常需要投入大量时间来学习和有效使用。作为一个开源的游戏库,enchant.js 通过为开发人员提供游戏创作功能来降低这种复杂性,这大大减少了您编写基于浏览器的游戏的学习曲线,并提高了您的游戏开发速度。

有了这本书,你可以开始快速轻松地创建和发布游戏。如果你完全是编程初学者,也不用担心。我们将带您了解所有基础知识,以便您可以快速上手并运行。如果您处于中级水平,我们也为您提供高级内容。我们描述了 enchant.js 框架的不同部分,并创建了几个游戏,包括经典游戏,如打地鼠和街机射击游戏,同时让您熟悉 enchant.js 库提供的所有功能。

访问 enchant.js 网站

要快速开始使用 enchant.js,请看一下三个主要的 enchant.js 网站。每个网站都有特定的功能,可以帮助你更快更容易地创建和分享游戏。

  • http://enchantjs.com:下载 enchant.js 代码库,查找资源,阅读编程技巧
  • 在基于云的在线环境中开发、编辑和测试游戏
  • http://9leap.net:上传、玩和分享游戏

在 enchant.js 主网站上,您可以了解该库并下载源代码来开发自己的游戏。您还可以在code.9leap.net上的在线环境中开发游戏,以获得流畅的体验。开发完你的游戏,就可以发布到9leap.net了。图 1-1 显示了站点的基本关系。

9781430247432_Fig01-01.jpg

图 1-1 。网站之间的关系

enchantjs.com

位于http://enchantjs.com的 enchant.js 官方网站提供了关于库、教程、技巧和资源的参考信息。该网站定期更新来自 enchant.js 开发人员的关于新版本和新特性的帖子,并且完全是日语和英语双语。该网站的默认语言是英语。如果碰巧出现了日语版本,并且您希望显示英语版本,请使用屏幕右侧的旗帜图标将语言改回英语。图 1-2 显示 enchant.js 的主页

9781430247432_Fig01-02.jpg

图 1-2 。enchantjs.com首页

code.9leap.net

http://code.9leap.net网站上,您可以直接在 web 浏览器中执行 HTML/JavaScript 编辑、测试和共享。code.9leap.net网站支持 enchant.js 的导入,也可以轻松上传到9leap.net,允许整个游戏开发周期,从编程到发布,都在浏览器中进行。它可以在 PC 或 Mac 上使用,也可以在 iPad 等设备上使用。

图 1-3 显示了code.9leap.net登录页面。在本书出版时,该网站正处于测试开发阶段。

9781430247432_Fig01-03.jpg

图 1-3 。【code.9leap.net】登录页面

9leap.net

位于http://global.9leap.net的 9leap 网站由 UEI 和 D2 通信公司(www.d2c.co.jp/en)主办,旨在发现和提升年轻开发者。该网站允许你上传、玩和分享游戏。9leap 网站包括许多像您一样的开发人员用 enchant.js 创建的游戏。在您开始创建自己的游戏之前,尝试一下该网站上的一些游戏,感受一下 enchant.js。图 1-4 显示的是 9leap 主页。

9781430247432_Fig01-04.jpg

图 1-4 。【9leap.net 首页

你也可以在网站上参加游戏开发竞赛。竞赛决赛入围者赢得奖品,如最新的 PC 和 Mac 电脑、书店礼品卡等。此外,作为 9leap 项目的一部分,UEI 定期在日本举办 9leap 游戏编程训练营,并开始在美国举办训练营。通常,这些夏令营以游戏编程研讨会开始,并提供指导以帮助参与者创建他们自己的简单游戏。有关更多信息,请访问enchantjs.com网站。

兼容性和版本

以下浏览器和设备支持 enchant.js:

  • Internet Explorer (IE) 9.0 和更高版本
  • 适用于 Mac OS X、Windows 和 Linux 的 Chrome 10 及更高版本
  • 适用于 Mac OS X 和 Windows 的 Safari 5 及更高版本
  • 适用于 Mac OS X 和 Windows 的 Firefox 3.6 及更高版本
  • 适用于 iPhone 和 iPad 的 iOS 4 及更高版本
  • Android 2.1 及更高版本

表 1-1 显示了 enchant.js 的主要版本,在本书出版时,enchant.js 的最新版本是 0.6.2 版。

表 1-1 。enchant.js 的最新版本和新增功能

版本 添加的功能
0.6.2(当前)
  • Do better on Android
  • Improved key binding.

|
| 0.6.1 |

  • 改良网络音频
  • Improved timeline

|
| 0.6.0 |

  • web 音频 API
  • 唐/坎瓦斯科
  • Animation engine
  • Core class

|
| 0.5.2 | |
| 0.5.1 |

  • [bug fix]

|
| 0.5.0 |

  • Sound support on iPhone
  • Support rotation & scaling attribute
  • Support canvas
  • 经过时间支持

|

image 从 0.6.1 版本开始,enchant.js 在 MIT 许可下授权。联系 Ubiquitous Entertainment ( http://global.uei.co.jp/)咨询有关公司使用 enchant.js 代码的问题。

enchant.js 的特性

enchant.js 旨在使游戏编程更简单,它提供了几个功能,使开发人员(无论是专家还是新手)更容易创建游戏。该库的主要特点是它的面向对象的方法,它处理游戏代码的特定方式,它通过插件的可扩展性,以及它的内容库。

面向对象编程

面向对象编程(OOP) 是一种强调被操作对象而不是操作过程的方法论。为了说明这个概念,enchant.js 中可以在屏幕上显示的每个图形都是一个对象。屏幕上实际可见的是另一个对象的一部分,称为显示对象树。通过发出将图形对象加入显示对象树的命令,图形对象在屏幕上变得可见。

列表 1-1 显示了一个玩家对象和一个敌方角色对象通过在显示对象树中注册而被创建并显示在屏幕上。现在,不要担心这段代码如何工作的细节。我们在这里提供一个代码示例,只是为了让您快速了解 OOP 的实际应用。稍后,我们将带您详细了解代码示例。

清单 1-1。 使用面向对象编程创建两个对象并在屏幕上添加它们

//Player object creation
var player = new Sprite(32, 32);
player.image = game.assets['player.png'];

//Enemy character object creation
var enemy = new Sprite(32, 32);
enemy.image = game.assets['enemy.png'];

//Registration in the display object tree
var scene = game.currentScene;
scene.addChild(player);
scene.addChild(enemy);

异步处理

异步处理是独立于主代码集运行的处理。把这当成多任务处理。如果计算机正在接收要一个接一个运行的命令行,然后在第一组命令仍在运行时开始接收运行其他命令的命令,这就是异步处理的一个例子。

enchant.js 异步处理用户或其他程序发起的操作或事件。当用户不发出任何命令时,程序只是等待而不做任何事情,而不是在后台连续运行代码。此外,当用户被迫等待一个程序完成处理时,这种异步特性使得同时发出其他命令成为可能。

清单 1-2 显示了指定在特定事件发生时运行的代码段。这被称为事件处理。在这个代码示例中,我们处理每一帧中的玩家对象,同时也处理触摸事件。具体来说,每次绘制一个帧,我们都希望运行一些代码(或者像开发人员有时说的那样“被执行”)。当玩游戏的用户点击或“触摸”角色时,我们希望执行不同的代码。我们将在第三章中详细介绍这一过程。

清单 1-2。 异步处理用于创建、设置和处理一个角色

//player (character) object creation
var player = new Sprite(32, 32);

//handling of the character in each frame
player.addEventListener(Event.ENTER_FRAME, function() {
    ...
});

//handling touch events
player.addEventListener(Event.TOUCH_START, function(e) {
    var x = e.localX;
    var y = e.localY;
    ...
});

插件扩展性

您可以通过各种插件来扩展 enchant.js 的功能,以添加更多功能。例如,一些插件允许你开发支持 D-pad 和模拟棒等设备的游戏,或者创建看起来像漫画书的互动游戏。在本书后面的章节中,我们将向您展示如何使用几个插件。

我们在这里没有提供所有 enchant.js 插件的详尽列表,因为您可以在enchantjs.com网站上找到该列表以及每个插件的详细信息。您可以从网站下载插件(参见 enchant.js 下载包)。

图像和声音

如果你是一个独立的游戏程序员,寻找用于角色、怪物、风景等的图像可能是一个挑战。由于 enchant.js 附带了一个免版税(用于非商业游戏)的原始游戏图像分类,以及来自以前 UEI 游戏版本的材料,您可以通过使用图像库为自己的游戏创建图像。图像包含在主 enchant.js 包中。图 1-5 展示了几个广泛可用的角色形象的例子。

9781430247432_Fig01-05.jpg

图 1-5 。enchant.js 包中的角色图像示例

你也可以从enchantjs.com的下载页面下载独立于主库的 zip 文件中的声音。例如,您可以包括背景音乐、爆炸声、激光射击、枪声、当角色捡起物品时可以使用的信号等等。

JavaScript、HTML5 和 CSS

现代网站通常由三种类型的代码组合而成:JavaScript、HTML5 和层叠样式表(CSS)。通常,HTML 提供核心内容,CSS 用于样式和格式,JavaScript 用于与页面元素交互并提供动画。我们在这一章中提供了 HTML5 和 CSS 的简要总结,但这本书总体上主要关注 JavaScript,因为这是编写 enchant.js 游戏的语言。

JavaScript

JavaScript 是一种面向对象的解释型编程语言,最初的开发目的是增加网页的交互性和动态性。JavaScript 的一个例子在清单 1-3 中显示。你可能听说过一种叫做 Java 的编程语言,但它与 JavaScript 完全不同。JavaScript 保证可以在所有主流浏览器上运行,无需安装任何额外的软件。如果您以前从未见过 JavaScript,请不要担心!我们会在第二章中详细介绍。

清单 1-3。 显示“Hello,World!”在屏幕上

document.write('<p>Hello, World!</p>');

HTML 和 HTML5

HTML 是超文本标记语言的缩写,是一种用于描述 Web 上文本的标记语言。在 enchant.js 中,HTML 用于加载 JavaScript 并控制智能手机上的某些浏览器操作(如缩放)。清单 1-4 中显示了一个例子。如果你打开一个普通的文本文件,在其中键入来自清单 1-4 的代码,并将其保存为“index.html”,该文件可以通过你的浏览器打开。浏览器会显示“Hello World!这是 HTML 页面的内容部分。

清单 1-4。 HTML 示例在浏览器中显示内容

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, user-scalable=no">
        <meta name="apple-mobile-web-app-capable" content="yes">
        <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
        <title>HelloWorld</title>
        <style type="text/css">
            body {
                margin: 0;
            }
        </style>
    </head>
    <body>
<p>Hello World! This is the content part of an HTML page.</p>
    </body>
</html>

HTML5 是 HTML 几个新特性的统称。表 1-2 显示了主要特征的例子。

表 1-2。【HTML5 的新特性

新的 HTML 功能 描述
<canvas>元素 支持二维图形的绘制
<audio>元素 允许声音回放
<video>元素 支持视频播放
应用缓存 允许脱机执行应用
跨域消息传递 允许在域之间传输信息
XMLHttpRequest二级
网络存储 使用客户端保存数据
SQL 数据库
索引数据库
Web 工作器 支持后台处理以改善用户体验
服务器发送事件 允许与服务器进行双向通信
Web 套接字
文件 API 允许访问本地文件

在 enchant.js 中,不直接使用 HTML5 函数,而是通过 enchant.js 库访问。在本书中,我们不提供 HTML 和 HTML5 的详细讨论。

半铸钢ˌ钢性铸铁(Cast Semi-Steel)

CSS 代表级联样式表,和 HTML 一起用来定义网页的外观,比如颜色、文本和大小。HTML 也可以用来指定页面的外观,但是它的正确用法是使用 CSS 来指定内容和定义内容的外观。使用 CSS 编辑动态网页尤其容易。清单 1-5 展示了如何将网页主体元素的背景改为灰色并指定字体。

清单 1-5。 改变网页背景并指定字体

body { background-color: #DDDDDD; font: 30px sans-serif; }

在 enchant.js 中,CSS 函数不是直接使用的,而是通过 enchant.js 库访问的。在本书中,我们不提供 CSS 的详细讨论。

制作“Hello World!”出现在屏幕上

对于我们的第一个 enchant.js 应用,我们将向您展示如何创建一个非常简单的程序来创建一个标签“Hello World!”这个练习的目的是用简单的方式向你展示 enchant.com 游戏的元素是如何组合在一起的。我们稍后会创建更复杂的游戏。

  1. 去 enchantjs.com 点击下载按钮下载最新的 enchant.js 包(不是开发版)。

  2. After unzipping the file, open a text editor, copy and paste the code shown in Listing 1-6, and save the file as “index.html” inside the folder the enchant.js package unzipped to.

    清单 1-6。 Index.html:加载 enchant.js 和主游戏代码

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
            <title>HelloWorld</title>
            <script type="text/javascript" src="enchant.js"></script>
            <script type="text/javascript" src="main.js"></script>
        </head>
        <body>
        </body>
    </html>
    

    代码将文件指定为 HTML 文件,告诉浏览器我们使用的是 UTF-8 字符编码(enchant.js 就是用这种编码编写的),给它一个标题“HelloWorld”,然后告诉它从一个名为“main.js”的文件加载我们的游戏代码,我们接下来将创建这个文件。

  3. Create a new file in your text editor, copy and paste the code shown in Listing 1-7, and save the file as “main.js” inside the same folder.

    清单 1-7。 简单的 enchant.js 应用显示“Hello World!”屏幕上的

    enchant();
    var game;
    
    window.onload = function(){
           game = new Core(320, 320);
           game.onload = function(){
                   sign = new Label();
                   sign.text = "Hello World!";
                   game.rootScene.addChild(sign);
           };
           game.start();
    };
    

清单 1-7 中的代码告诉 enchant.js 用enchant();命令启动,创建游戏,并指定游戏屏幕的尺寸。页面完全加载后,我们指定创建名为“sign”的新标签的指令,指定文本为“Hello World!”,然后将其添加到游戏屏幕。最后,用game.start(),我们运行那些指令。

现在,如果你在浏览器中打开 index.html,你应该会看到“你好,世界!”显示在屏幕上。在这个例子中,唯一使用的文件是 enchant.js、index.html 和 main.js

这是一个非常基本的例子,但从中你可以看到 index.html 加载了 enchant.js,index.html 也加载了我们用 main.js 编写的游戏代码。我们的下一个例子本质上将更加图形化,并向你介绍在线编程环境,code.9leap.net

创造一只滑冰熊

对于我们的第二个 enchant.js 应用,我们让一只熊在屏幕上从左向右滑动,并优化应用以在智能手机上显示。您可以找到我们在http://code.9leap.net/codes/show/19822创建的代码示例。

正如我们之前提到的,code.9leap.net为 enchant.js 中的游戏编码准备了所有必要的元素。我们在这个应用中使用了code.9leap.net,我们将带您完成所有必要的步骤。请注意code.9leap.net仍处于测试阶段,可能会与本节中的截图略有不同。

以下步骤展示了在code.9leap.net上创建 enchant.js 应用的整个开发过程。我们将在接下来的小节中详细介绍每个步骤。

  1. 开始使用您的应用
  2. 导入 enchant.js 库
  3. 编辑源代码
  4. 预览您的结果
  5. 完成编辑您的源代码

开始使用您的应用

code.9leap.net上开发游戏的第一步是创建一个账户。以下步骤向您展示了如何创建帐户。如果您已经创建了一个帐户,那么您可以跳过这一步,登录并继续创建项目。

当使用 enchant.js 编程时,我们强烈建议使用 Google Chrome 作为您的首选浏览器。虽然使用 Safari 和 Firefox 也有不错的结果,但我们不能保证 Internet Explorer 在与 enchant.js 库或与 enchant.js 相关的网站交互时的稳定性。

  • 1.  Open http://code.9leap.net in your favorite browser and click the Sign up link, which is located below the username and password input areas. See Figure 1-6. (If the Japanese site appears, scroll down and click Switch To English).

    9781430247432_Fig01-06.jpg

    图 1-6 。code.9leap.net 登录屏幕

  • 2.选择用户名和密码,并将其输入到相应的字段中。阅读服务条款后,选择“我同意服务条款”。完成后,点击位于输入表单下方的注册按钮。

  • 3.  You will be redirected to the dashboard of your new account. An explanation of this screen follows in the next step. The next time you visit the site, you can just enter in your login information to log in.

    创建帐户或登录后,您将被重定向到仪表板,如图 1-7 所示。仪表板提供了关于您的帐户的所有必要信息。在右边你会发现你已经创建的所有项目的列表。(当然,创建新帐户后,该列表最初将为空。)在左侧,您会发现一个包含各种类别的示例项目列表。

    9781430247432_Fig01-07.jpg

    图 1-7 。code.9leap.net 仪表板

    要创建 enchant.js 应用,首先需要创建一个项目。项目是包含管理应用所需的所有文件的包。要开发一个应用,您需要一个项目。

  • 4.  Click the English Tutorials category in the Sample Projects list, shown in Figure 1-8.

    9781430247432_Fig01-08.jpg

    图 1-8 。英语教程

  • 5.  After you click a category, it will expand, displaying all projects in the category. You will see a list of Beginner projects that you can fork or view. Forking the project creates a copy in your project list. Viewing a project allows you to see the code together with a screen showing the result of the code when executed. Click Fork next to the Beginner 01 project. In the pop-up, confirm your action by clicking Fork again, as shown in Figure 1-9.

    9781430247432_Fig01-09.jpg

    图 1-9 。叉子弹出

  • 6.  After you click Fork for the second time, a source code editing screen is displayed with code already populated inside it. Shown in Listing 1-8, this code displays the string “Hello,Bear” using enchant.js, similar to the way we created the “Hello World!” program in a preceding section.

    清单 1-8。 屏幕上显示“你好,小熊”

    enchant(); //the magic words that start enchant.js
    
    window.onload = function() {
        game = new Game();
        game.onload = function() {          //Prepares the game
            hello = new Label("Hello,Bear");   //Create a new label with the words "Hello,Bear"
            hello.x = 10;                   //Place the label 10 pixels to the right (0 will always be the left border)
            hello.y = 150;                  //Place the label 150 pixels from the top (0 will always be the top border)
            game.rootScene.addChild(hello); //Show the label on the active screen
        }
        game.start(); //Begin the game
    }
    

就像我们前面的例子一样,这段代码在屏幕上创建了一个标签。这里,我们将标签放在离屏幕中心更近的地方,并将文本指定为“Hello,Bear!”而不是“你好,世界!”这一次我们不会使用源代码,而是重写它来制作我们自己的 enchant.js 程序。图 1-10 和表 1-3 显示了代码编辑屏幕的不同区域。

9781430247432_Fig01-10.jpg

图 1-10 。源代码编辑屏幕

表 1-3 。源代码编辑屏幕的元素

| 标题编辑区 | 你的申请的标题。 |
| 账户区域 | 注销和仪表板访问。 |
| 代码预览区 | 显示应用预览的代码区域。 |
| 提示区 | 这里将显示教程的提示或说明。 |
| 资源选择 | 一个下拉菜单,使您能够在 JavaScript (main.js)、HTML (index.html)、级联样式表(style.css)和提示(tips.json)之间切换。默认情况下,该菜单设置为 main.js。下拉菜单位于代码预览区域的正上方。 |
| 运行按钮 | 在预览区域执行源代码。 |
| 格式按钮 | 格式化源代码编辑区域中的源代码。 |
| 添加资源按钮 | 用于添加项目中使用的资源(图像、声音等)。 |
| 完成按钮 | 完成源代码编辑并返回仪表板。 |
| 源代码编辑区 | JavaScript 源代码、HTML 文件和编辑技巧。导入库时也使用。 |
| 图像区域 | 显示项目的所有本地图像。 |

接下来,您需要编辑标题(应用名称)。

  • 7.在源代码编辑屏幕上的标题编辑区(直接位于右上角 code.9leap 徽标的右侧)更改标题。现在输入“HelloEnchant”。

  • 8.  Change the title of the HTML file. Select index.html, which is the HTML file of the application, in the resource selection. The file will be displayed in the source code editing area, and should be prepopulated with some code. There you will find the title of the HTML file. In this case it is currently set to <title>untitled</title>. As shown in Figure 1-11, change it to <title>HelloEnchant</title> for our current application.

    9781430247432_Fig01-11.jpg

    图 1-11 。编辑标题

导入 enchant.js 库

库是一个文件或一组文件,它允许其他程序使用特定的功能。为了构建我们的应用,我们使用 enchant.js 库。通过 HTML 文件中的脚本标签将库加载到项目中。有时库需要将图像添加到项目中,但是我们现在不需要这样做。

请记住,enchant.js 中的插件也是库,因此它们需要加载自己的脚本标签。这就是为什么您会在下面的清单中看到引用诸如 tl.enchant.js 等文件的行。

要添加 enchant.js 库,请执行以下操作:

  1. Remove the lines of code containing nineleap.enchant.js and tl.enchant.js. You should already be at index.html from the preceding steps. These lines of code will be inside the script tags (sections of code marked by <script>). These tags tell the browser that everything between <script> and </script> should be treated as a line of code. When src= is included in the script tag, it tells the browser to load lines of code from a file outside index.html. After removing the two lines of code, your <script> tags should match Listing 1-9.

    清单 1-9。 脚本标签加载 enchant.js,插件,游戏代码

    <script src='/static/enchant.js-latest/enchant.js'></script>
    <script src='/code.9leap.js'></script>
    <script src='main.js'></script>
    

image 注意main . js 脚本标签导入应用代码,所以这个标签始终是必需的。切勿更改或删除它。

您可以使用code.9leap.js库添加在code.9leap.net中开发 enchant.js 应用时所需的任何特性;但是,对于我们当前的应用,不要对库进行更改。/static/enchant.js-latest/enchant.js标签导入 enchant.js 库。当你在code.9leap.net中开发应用时,你可以使用/static/enchant.js-latest/路径引用最新发布的 enchant.js。enchant.js 插件nineleap.enchant.jstl.enchant.js也是如此。

编辑源代码

要编辑源代码,请从资源下拉列表中选择 main.js。该屏幕应如图 1-12 所示。

9781430247432_Fig01-12.jpg

图 1-12 。编辑源代码

删除源代码编辑区域中显示的所有内容,因为您需要在此复制并粘贴一些代码。JavaScript 区分大小写,所以复制代码时要小心。接下来,执行以下步骤。

  1. Initialize the enchant.js library. This allows you to use all the classes and methods included in the library. Listing 1-10 shows how to initialize the enchant.js library. The parentheses after the word “enchant” indicate that the previous word is a method, which runs a predefined set of code, and the semicolon at the end (😉 indicates the end of a single statement of code.

    清单 1-10。 初始化 enchant.js 库

    enchant();
    
  2. On a new line under the enchant() method, designate code to be run after the page has loaded completely, as shown in Listing 1-11. Assigning code to the onload function of an object called the window, which, as you may suspect, represents the window in the browser, specifies that code to be run after loading.

    清单 1-11。 指定加载完成后运行的代码

    window.onload = function() {
        //code to be executed
    };
    
  3. Create the core object by replacing “//code to be executed” with the code shown in Listing 1-12. The line that begins with two forward slashes indicates that the line is a comment, and will not be processed as code. To create a game, enchant.js needs a core object to add game elements to. Typing in “Core(320,320)” creates a game screen with a width of 320 pixels and a height of 320 pixels, respectively. The format of “new Core(320,320)” is called a constructor, and we use constructors to create new objects.

    清单 1-12。 创建核心对象

    var game = new Core(320, 320);
    
  4. Preload the required image of a bear skating, as shown in Listing 1-13. To use images in enchant.js, you must preload them first. The image we load for this application is shown in Figure 1-13. Normally, this image must be uploaded into the code.9leap project or included in the same folder as your index.html file, but because we forked the project, this image is already in the project.

    清单 1-13。 预载一只滑冰熊的形象

    game.preload('chara1.gif');
    

    9781430247432_Fig01-13.jpg

    图 1-13 。熊图片:chara1.gif

  5. Designate a function to be run once the game has loaded completely, as shown in Listing 1-14. We create this in much the same way as we did with window.onload, and we do this because any game object we create can be created successfully only if the core object has completely loaded.

    清单 1-14。 创建game.onload功能

    game.onload = function() {
    };
    
  6. Create the bear sprite, as shown in Listing 1-15. We must first create a new variable as a new Sprite object, with dimensions of 32 pixels wide by 32 pixels high, specify the image we preloaded to be used as the image of the bear, and then specify which part of the image we want to use. We want to use the image of the skating bear, which is the fifth image counting from the top left. Frame numbering begins with 0, so the skating bear is frame number 4 within the image. Note that this code must be typed inside the curly braces of the game.onload function.

    清单 1-15。 创造熊精灵

    var bear = new Sprite(32, 32);
    bear.image = game.assets['chara1.gif'];
    bear.frame=4;
    
  7. Create an event listener to move the bear by three pixels to the right every frame, as shown in Listing 1-16. We cover event listeners in more detail later, but for now simply be aware that this code tells the program to move the bear every frame. Add it after the code you entered in Listing 1-16, still inside the game.onload curly braces.

    清单 1-16。 创建事件监听器来移动熊

    bear.addEventListener(Event.ENTER_FRAME, function() {
        this.x += 3; //move by 3 pixels
    });
    
  8. Add the bear to the game’s root scene, as shown in Listing 1-17. We’ve created the bear, but it still won’t be shown on the screen unless we add it to the game’s root, or main, scene. Enter this code after the code you entered in Listing 1-16, still inside the game.onload curly braces.

    清单 1-17。 将熊添加到游戏的根场景

    game.rootScene.addChild(bear);
    
  9. Under the game.onload curly braces, but still inside the window.onload curly braces, start the game. See Listing 1-18.

    清单 1-18。 开始游戏

    game.start();
    
  10. Check your code. Your code should match what is shown in Listing 1-19. You can ignore the comments (lines starting with two forward slashes). If it all matches, hit the Run button in the upper-right corner, and you should see your bear skate across the screen!

***清单 1-19。*** 让熊滑过屏幕

```js
//initialization of enchant.js
enchant();

//code written here will be executed once the HTML file is completely loaded
window.onload = function() {
    //game object creation
    var game = new Core(320, 320);

    //image loading
    game.preload('chara1.gif');

    //execution once the image has loaded completely
    game.onload = function() {
        //Sprite creation
        var bear = new Sprite(32, 32);
        bear.image = game.assets['chara1.gif'];
        bear.frame=4;

        //frame loop to move the bear every frame
        bear.addEventListener(Event.ENTER_FRAME, function() {
            this.x += 3; //move by 3 pixels
        });

        //add the bear to the display object tree
        game.rootScene.addChild(bear);
    };
    game.start();
};
```

现在让我们添加一些信息来优化智能手机上的玩家体验。
  1. Select the index.html file from the resource drop-down menu. Adjust the content of your file to match the content shown in Listing 1-20.
***清单 1-20。*** 为智能手机调整 HTML】

```js
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <style type="text/css">
        body {
            margin: 0;
        }
    </style>
    <link rel='stylesheet' href='style.css' type='text/css'>
    <script src='/static/enchant.js-latest/enchant.js'></script>
    <script src='/code.9leap.js'></script>
    <script src='main.js'></script>
    <title>HelloEnchant</title>
</head>
<body>
</body>
</html>
```

让我们来看看我们添加的新 HTML 标签。以下代码将字符编码设置为 UTF-8 (unicode),这种字符编码与大多数现代语言中使用的几乎所有字符都兼容。

  • <meta charset="utf-8">

下面的代码禁止用户缩放(zoom)以确保游戏看起来总是正确的。

  • <meta name="viewport" content="width=device-width, user-scalable=no">

下面的代码将显示设置为全屏。

  • <meta name="apple-mobile-web-app-capable" content="yes">

下面的代码更改了状态栏的显示方式。

  • <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">

下面的代码将 HTML 正文边距设置为 0,以保持不同浏览器之间的外观一致。

  • <style type="text/css"> body { margin: 0; } </style>

我们将在本书的所有应用中使用这个 HTML 文件。除了改变我们导入的库,我们唯一改变的是标题。

预览您的结果

如图 1-14 所示,当您编辑完源代码后,点击屏幕右上角的运行按钮保存您的进度。如果你输入的源代码正确,熊应该从左向右滑。

9781430247432_Fig01-14.jpg

图 1-14 。展示预览

如果预览看起来很奇怪,请检查您的源代码以确保它匹配。特别要注意丢失的分号(;)、放错位置的句号、拼错的名字。

我们可以将字符串打印到控制台,这是一个专门为开发人员提供的特殊输出屏幕,方法是使用一种叫做 log output 的东西。它可以用来检查变量值和程序执行的方式。要在日志屏幕上打印名为sum的变量的值,使用列表 1-21 中的符号。

清单 1-21。 显示控制台中变量的名称

console.log("Total = " + sum); console.log("sum = " + sum);

如果您使用 Google Chrome 作为浏览器,您可以使用以下步骤来查看日志的输出。

  1. Open the console by right-clicking the part of the window running the game and select Inspect Element, shown in Figure 1-15.

    9781430247432_Fig01-15.jpg

    图 1-15 。从下拉菜单中选择检查元素

  2. 将会打开“开发工具”窗口。单击控制台按钮显示控制台。如果您的应用由于 JavaScript 语法错误而无法运行,错误类型和行号将显示在控制台中。

完成源代码编辑

要完成源代码编辑,请单击源代码编辑屏幕上的 finish 按钮。您将被重定向回您的仪表板。

在设备上执行并上传到9leap.net

正如我们在本章开始时所说的,到目前为止,许多用 enchant.js 创作的游戏都是为在移动浏览器上玩而设计的。一旦你完成了游戏编码,我们建议你检查你的源代码,在移动设备上测试你的游戏,如果你愿意的话,在9leap.net上分享你的游戏。

源代码查看屏幕

点击仪表板上的项目名称,打开您自己的项目或示例项目的源代码视图,如图 1-16 所示。在那里,你可以查看和执行源代码,获得在其他设备上运行代码的 URL,创建一个标签嵌入博客,并在9leap.net上发布游戏。在您的仪表板上,单击 HelloEnchant 打开您刚刚创建的项目的源代码视图。在屏幕的上部,您会发现各种按钮,其功能在表 1-4 中有说明。

9781430247432_Fig01-16.jpg

图 1-16 。源代码查看屏幕

表 1-4 。源代码查看屏幕上的按钮

| 编辑按钮 | 返回源代码编辑屏幕进行编辑。 |
| 叉形按钮 | 用这个源代码开始一个新项目。 |
| 下载按钮 | 将项目使用的源代码和资源下载到本地硬盘上。 |
| 发布到9leap.net | 在9leap.net (beta)上分享您的应用。 |
| 嵌入按钮 | 创建一个标签,用于将应用嵌入到博客、网站等中。 |
| 删除按钮 | 删除此项目。 |

在这些按钮下面,您可以在左侧看到应用的预览,当您打开屏幕时,会自动执行该预览。源代码出现在右边。

在设备上执行

要在 iPhone、Android 或其他设备上通过code.9leap.net执行 enchant.js 应用,请执行以下步骤。

  1. Check the project ID by looking at the URL of the project, as shown in Figure 1-17. In this case, the ID is 21345.

    9781430247432_Fig01-17.jpg

    图 1-17 。检查项目 ID

  2. Use this ID to create a URL that can be opened on mobile devices. If the URL shown in the window is http://code.9leap.net/codes/show/21345, then the mobile URL will be http://coderun.9leap.net/codes/21345. This example is shown in Figure 1-18.

    9781430247432_Fig01-18.jpg

    图 1-18 。在设备上运行代码

嵌入博客

要创建标签以将 enchant.js 应用嵌入到博客或网站中,请在源代码视图中单击“嵌入”按钮。将出现一个嵌入标签,如图图 1-19 所示。将此标签复制并粘贴到您的网站或博客的 HTML 代码中。如果你有一个简单的网站,你可以将标签直接粘贴到网站的<div><body>标签中,但是如果你使用一个内容管理系统,比如 WordPress,你可能需要在编写或编辑一个新的页面或文章时启用 HTML 编辑来成功粘贴这个标签。

9781430247432_Fig01-19.jpg

图 1-19 。嵌入弹出

提交到9leap.net

当你的 enchant.js 应用完成后,试试在9leap.net上分享吧!你可以先把它设置为私有模式,这样你就有机会在公开之前测试它。按照以下步骤共享您的应用。(在撰写本书时,发布到 9leap 按钮仍在测试中。)

  1. 单击源代码查看屏幕中的下载按钮。你的浏览器会下载游戏的压缩文件。

  2. Go to 9leap.net, shown in Figure 1-20, and log in using your Twitter account. (If you don’t have one, you’ll need to register one before you can use 9leap.net.) The button might appear in English or Japanese because the site is still in the beta stage.

    9781430247432_Fig01-20.jpg

    图 1-20 。9leap.net 上的登录按钮

  3. Click Game Upload/Edit Screen, as shown at the top of the screen in Figure 1-21.

    9781430247432_Fig01-21.jpg

    图 1-21 。游戏上传/编辑链接

  4. Click on Add New, as shown in Figure 1-22.

    9781430247432_Fig01-22.jpg

    图 1-22 。添加新按钮

  5. Accept the terms of service, fill in your information, and click Send, as shown in Figure 1-23. This information is used to consider the game for contests on the site.

    9781430247432_Fig01-23.jpg

    图 1-23 。发送按钮

  6. 在空白处填入你的游戏信息。这些字段在表 1-5 中解释。对于游戏文件,找到从code.9leap.net保存的游戏的 zip 文件。你需要包括一个截图,可以是游戏的图片文件或者截图。(请参见http://bit.ly/11m6e4了解 Mac 的屏幕截图信息,参见http://bit.ly/ZFSdT了解 PC 的屏幕截图信息。)请确保指明您希望使用哪种许可证来许可您的游戏。完成后,点击游戏后按钮,如图图 1-24 所示。

表 1-5 。游戏信息字段

| 游戏标题 | 输入不超过 40 个字符的游戏名称。 |
| 型 | 为你的游戏选择一个流派。 |
| 游戏解说 | 用不超过 1000 个字符的篇幅解释你的游戏目标以及如何玩。 |
| 游戏文件 | 包含游戏的小于 10MB 的 zip 文件。 |
| 截图 | 你的游戏截图(小于 1MB - jpg/png/gif)。 |
| 运行时环境 | 指出游戏浏览器的兼容性。 |
| 推特设置 | 指明您是否希望 9leap 在 Twitter 上发布您的游戏。 |
| 设为私有 | 如果您希望您的游戏只有您一个人可以查看,请选择此选项。 |
| 提交参赛作品 | 如果你的游戏有资格参加目前正在 9leap 上举行的比赛,它们将会出现在这里。 |
| 源代码许可 | 如果你想指定一个许可证来保护你的游戏,请在这里指定。 |

9781430247432_Fig01-24.jpg

图 1-24 。上传按钮

私人设置可以在任何时候从游戏编辑设置页面更改。

成功上传游戏后,将显示共享完成屏幕,您可以在游戏上传/编辑屏幕上查看您的游戏。参见图 1-25 。

9781430247432_Fig01-25.jpg

图 1-25 。上传的游戏出现在游戏上传/编辑界面

结论

在本章中,我们介绍了 enchant.js 的基本特性,并展示了 enchant.js 游戏如何融入网页结构。我们在code.9leap.net上谈到了 JavaScript、HTML5 和 CSS,以及如何开始编写游戏代码。最后,我们看了看如何在global.9leap.net上与 enchant.js 社区分享你的游戏,以及玩其他用户创建的游戏如何给你自己的游戏带来灵感。

在下一章中,我们将介绍 JavaScript 的构建模块,这是编写 enchant.js 的语言。学习 JavaScript 将为你开始用 enchant.js 创建自己的游戏打下基础。

二、JavaScript 基础知识

正如我们在第一章中所说,enchant.js 是用 JavaScript 编程语言编写的。当您在 enchant.js 中编写游戏时,您将使用 JavaScript 语言编写命令和其他代码,因此在深入研究 enchant.js 游戏之前,我们需要研究简单的 JavaScript。如果你已经熟悉 JavaScript,可以跳过这一章。

JavaScript 是用于网页脚本的主要编程语言。与大多数用于创建必须安装在计算机上才能运行的软件应用的编程语言不同,JavaScript 代码在客户端 web 浏览器上运行。鉴于 enchant.js 和用 enchant.js 编写的游戏都是用 JavaScript 创建的,所以理解 JavaScript 的基本概念是必不可少的。如果您是编程新手,学习这些基础知识将使您受益匪浅,因为它们恰好也是面向对象语言的构建块,并且是学习其他流行编程语言(如 Java 和 C++)的有用起点。

一种编程语言的语法,或者更确切地说,这种语言的元素编写的特定方式,被称为语法。在本章中,你将通过在 code.9leap.net 网站上编写代码来学习 JavaScript 的构建模块、语法和功能。我们将带您一步一步地完成这一过程,并通过一系列简单的代码项目来解释每一个构建模块。

汇总列表

在本章中,我们将向您展示如何执行以下操作:

  1. 声明一个变量
  2. 给变量赋值
  3. 添加变量
  4. 检查变量的值
  5. 使用变量本身操作变量
  6. 使用增量操作变量
  7. 比较变量
  8. 看看两个变量有多相似
  9. 操纵比较
  10. 用 If 语句实现逻辑
  11. 创建一个对象
  12. 更智能地使用 While 和 For 循环语句
  13. 用 Break 中断循环
  14. 用 Continue 跳过循环迭代
  15. 生成随机数
  16. 用开关定义场景
  17. 将编号数据存储在一个数组中
  18. 在关联数组中存储无编号的数据
  19. 使用功能节省时间
  20. 查看变量的位置
  21. 用原型制作物体蓝图

声明一个变量

变量是值的容器,在大多数情况下可以随时用新值更新,因此得名。它们的值“不同”,所以它们是“可变的”使用变量优于显式值是因为变量的动态特性。如果在计算中包含一个变量,然后又更改了该变量的值,则下次执行相同的计算时,将使用新值。

要使用一个变量,首先必须用var语句声明它,它告诉应用在内存中为该变量保留一个位置。在 JavaScript 的任何一条语句的末尾,都必须包含一个分号(;).

要声明三个名为num0num1sum的变量,请执行以下操作:

  1. 如果您还没有帐户,请转到http://code.9leap.net并登录或注册。

  2. 转到http://code.9leap.net/codes/show/26854,点击 fork 按钮,将空白的 JavaScript 模板分叉。

  3. 通过直接修改屏幕左上角 code.9leap 徽标右侧的字段来更改项目的标题。选择适当的东西,比如“声明一个变量”(对于所有将来派生此代码的示例,请以这种方式更改标题,以跟踪您的单个项目。)

  4. Type in the code shown in Listing 2-1.

    清单 2-1。 声明一个变量

    var num0;
    var num1;
    var sum;
    

命名变量

您可以在变量名中使用以下字符:

  • 字母(A-Z,A-Z)
  • 数字(但不是变量名的第一个字符)
  • 下划线(_)

表 2-1 显示了 JavaScript 中不能用作变量名的保留字。

表 2-1。JavaScript 中的保留字

image

表 2-2 显示了当前未被使用,但将来被使用的概率很高的单词。我们建议在命名变量时避免使用它们。

表 2-2。保留文字供将来使用

image

您不能使用 JavaScript 中预定义的任何其他单词,如Stringtrue等。完整名单请见www.javascripter.net/faq/reserved.htm

给变量赋值

一旦变量被声明,就可以给它赋值。要给num0num1赋值,请执行以下操作:

  1. Below the Listing 2-1 code you entered, type in the code shown in Listing 2-2.

    清单 2-2。 给变量赋值

    num0 = 100;
    num1 = 200;
    

image 注意变量可以通过使用

var num3 = 400;

添加变量

变量可以用来代替数字进行运算。如果使用变量执行算术运算,变量的当前值将用于计算。要将num0num1相加并将结果分配给sum,请执行以下操作:

  1. Below the Listing 2-2 code you entered, type in the code shown in Listing 2-3.

    清单 2-3。 添加变量

    sum = num0 + num1;
    

基本算术运算符

你不仅仅局限于添加变量或数字。所有基本的算术运算都可以执行。表 2-3 显示了基本操作符。

表 2-3 。基本算术运算符

操作员 描述 例子
+ 添加 a + b(将 a 和 b 相加)
减法 a–b(从 a 中减去 b)
* 增加 a * b(a 和 b 相乘)
/ 分开 a/b(a 除以 b)
% 剩余物 a % b(a 除以 b 的余数。换句话说,7 % 4 = 3,因为 4 一次进入 7,余数为 3。)

运算符具有优先级:乘法和除法首先发生,然后才是加法和减法。要改变优先顺序,如果你想在计算其他部分之前计算特定部分,这可能是有用的,使用括号,如清单 2-4 所示。您不需要复制这段代码。仅供参考。

清单 2-4。 用括号改变操作顺序

var result0 = 1 + 2 * 3;        //result0 will be 7
var result1 = (1 + 2) * 3;      //result1 will be 9

检查变量的值

要查看sum的值,请执行以下操作:

  1. Below your current code, type in the code shown in Listing 2-5. The document.write() command tells the program to display whatever is within the parentheses on-screen, and uses the plus sign to combine, or concatenate, values together to display a final result. Commands like this are called methods, and can be identified by the parentheses at the end of the method name. The period before write indicates that it is a method of the document object, a predefined set of code allowing JavaScript to have access to the browser window. We’ll cover objects in more detail later.

    清单 2-5。 在屏幕上显示总和的值

    document.write("Total amount = " + sum);
    
  2. 单击运行按钮。(我们在下文中将其简称为 Run。)预览区应该显示“总金额= 300”。

如果你想对照一个完成的例子检查你的代码,你可以在http://code.9leap.net/codes/show/19823找到一个。

数据类型

为什么我们必须在清单 2-5 中的document.write()内放置的内容加上引号?这是因为document.write()只接受字符串值。字符串用于存储文本字符串,是 JavaScript 中五种基本数据类型之一。其中三种数据类型——数字、字符串和布尔——用于存储数据,而另外两种用于表示变量的当前状态。表 2-4 显示了五种数据类型。

表 2-4。基本数据类型

类型 分配值 用法示例
Number 基数为 10 var num = 10
基数为 8 (0 附加到值的开头) var num = 011:
基数为 16 (0x 附加到值的开头) var num = 0x 9a;
浮点,基数为 10(使用句点[。]) 凡 fnum = 3.1415
浮点,指数(使用句点[。]和 E) var fnum =-3.1 和 12;
Boolean truefalse 其中标志= true
String 用双引号或单引号括起来的字符串 var str = "字符串";
var str = '字符串';
null 已知该对象存在,但没有值 var obj = null;
undefined 对象不存在、不返回值或尚未初始化

除了基本的数据类型,JavaScript 中还有复杂的数据类型:

  • 对象:对象是多种基本数据类型和/或其他对象的集合,通常由开发人员预定义或定义。如果你要创建一个保龄球游戏,你很可能有多个对象代表保龄球瓶,另一个对象代表保龄球。
  • 数组:数组是一组索引有序的数据。在保龄球游戏示例中,您的保龄球瓶对象可以全部存储在一个数组中。添加到数组中的每一项都分配有一个数字,从 0 开始,每添加一项就增加 1。
  • 关联数组:关联数组是一组没有特定顺序的命名数据。与常规数组不同,关联数组可以将一个项与任何其他值或项配对在一起。想象一下,我们的保龄球游戏是 3d 游戏,保龄球瓶上方悬挂着几盏灯。关联阵列可用于将每个保龄球瓶与特定的灯连接在一起。
  • 函数:函数执行一个定义的计算或一组代码。在保龄球游戏的例子中,如果保龄球与瓶接触,可以使用一个函数来使保龄球瓶翻倒。

我们将在后面更详细地介绍复杂的数据类型。

image 注意在 C/C++和 Java 这样的编程语言中,变量有静态数据类型(比如integer或者string),你不能把一种数据类型的值赋给另一种数据类型的变量。换句话说,如果你有一个integer变量,你不能给这个变量赋值一个字符串。

在 JavaScript 中,情况并非如此。任何数据类型的值都可以赋给一个变量,完全不同数据类型的变量可以在以后重新分配给同一个变量。名为foo的变量可以用foo = 10;赋一个数值,然后下一行可以声明foo = "bar";为同一个变量赋一个字符串值。这种灵活性是 JavaScript 的一个主要优点,但缺点是有时在执行计算时必须格外小心。稍后我们会看到这样的例子。

使用变量本身操作变量

许多游戏都包含一个分数,当玩家获得分数时,该分数会不断更新。要更新这个分数,包含它的变量必须增加。请执行以下操作来了解如何操作:

  1. 清除您编写的代码,直到清单 2-5 。

  2. Type in the code shown in Listing 2-6.

    清单 2-6。 通过引用自身操纵变量

    var score = 10;
    score = score + 20;
    document.write(score);
    
  3. 单击运行。预览屏幕应该显示“30”

  4. score = score + 20;替换为score += 20;

  5. 再次单击运行。结果是一样的。步骤 4 使用自引用操作的缩写形式,称为复合赋值操作符。

复合赋值运算符

复合赋值运算符是同时对一个变量赋值和另一个运算的运算符。表 2-5 显示了这些有用的操作符。

表 2-5。复合赋值运算符

操作员 描述 例子
+= 添加 a += 10(相当于 a = a + 10)
–= 减法 a –= 10(相当于 a = a–10)
*= 增加 a *= 10(相当于 a = a * 10)
/= 分开 a /= 10(相当于 a = a / 10)
%= 剩余物 a %= 10(相当于 a = a % 10)

使用增量操作变量

当变量需要增加值 1 时,可以使用更简短的操作符。执行以下操作来查看它的运行情况:

  1. 清除您当前的代码。

  2. Type in the code in Listing 2-7.

    清单 2-7。 声明并递增变量

    var num = 1;
    num++;
    document.write(num);
    
  3. 单击运行。预览屏幕应该显示“2”,表明++将 num 的值增加 1。

  4. 清除您当前的代码。

  5. Type in Listing 2-8.

    清单 2-8。 在一个语句内递增一个变量

    var num = 1;
    document.write(num++);
    num = 1;
    document.write(++num);
    
  6. 单击运行。屏幕首先显示“1”,然后显示“2”在类似于document.write()的语句中,如果增量运算符(++)在变量之后,那么document.write()语句将首先执行,如果运算符出现在变量之前,那么运算符将在document.write()之前执行。

image 注意增量运算符的反义词是减量运算符(--)。减量运算符的工作方式与增量运算符完全相同,只是它从附加的变量中减去 1。

比较变量

JavaScript 包含名为关系运算符的命令,比较两个值并根据结果返回布尔值truefalse。执行以下操作来查看它们的运行情况:

  1. 清除您当前的代码。

  2. Type in the code in Listing 2-9.

    清单 2-9。 比较数值

    var num0 = 4;
    var num1 = 7;
    document.write(num0 >= num1);
    document.write(num0 < num1);
    document.write(num0 === num1);
    
  3. 单击运行。屏幕先显示false,因为 4 不大于等于 7;显示true,因为 4 小于 7;又显示了false,因为 4 不等于 7。

表 2-6 显示了可用的关系运算符。

表 2-6 。关系运算符

操作员 描述 例子
== 相等的 a == b
=== 严格等价 a === b
!= 不等价 答!= b
!== 严格不等价 答!== b
> 大于 a > b
>= 大于或等于 a >= b
< 不到 a < b
<= 小于或等于 a <= b

看看两个值有多相似

通过执行以下操作,查看等价(==)和严格等价(===)关系运算符有何不同:

  • 1.清除您当前的代码。

  • 2.  Type in the code in Listing 2-10.

    清单 2-10。 不同等价运算符

    var num0 = 4;
    var num1 = "4";
    document.write(num0 == num1);
    document.write(num0 === num1);
    
  • 3.单击运行。屏幕首先显示 true,因为如果被比较的值之一是字符串,则符号==会暂时将所有值转换为字符串。这叫做型转换。然后屏幕显示 false,因为===符号不执行类型转换。要评估为相同,比较值必须是相同的数据类型。

image 注意等价运算符的反义词是不等价运算符(!=!==),它们执行几乎完全相同的运算。然而,!=操作符执行类型转换,而!==操作符不执行。

操纵比较

有时您会想要操纵比较如何解析(true / false)。要查看如何操作这些比较,请执行以下操作:

  • 4.清除您的代码。

  • 5.  Type in the code in Listing 2-11.

    清单 2-11。 操纵比较

    document.write(!(4 > 3));
    document.write((4 < 7) && (5 < 13));
    document.write((4 > 5) || (4 < 5));
    
  • 6.单击运行。

屏幕会先说false是因为语句(4 > 3)解析为true!逻辑运算符反转其后的布尔语句(true / false),使之成为false。然后屏幕列出true,因为(4 < 7)和(5 < 13)都评估为true。只有当它周围的两个语句都是true时,&& ( and)逻辑运算符才返回 true。最后屏幕再次显示true,因为在(4 > 5)和(4 < 5)中,有一个是true。如果至少有一个语句是true,则|| ( or)运算符解析为true。表 2-7 显示了逻辑运算符。

表 2-7。逻辑运算符

操作员 描述 例子
! false如果 a 是truetrue如果 a 是false ! a
&& true如果 a 和 b 都是true,否则为false a && b
&#124;&#124; true如果 a 或 b 为true,否则为false a &#124;&#124; b

用 If 语句实现逻辑

有时,当且仅当满足特定条件时,我们希望在代码中执行特定的操作。请执行以下操作来了解如何操作:

  1. 清除您当前的代码。

  2. Type in the code in Listing 2-12.

    清单 2-12。 用 If 语句实现逻辑

    var num0 = 4;
    var num1 = 8;
    if (num0 >= num1) {
        document.write(num0 + " is greater than or equal to " + num1 + ".");
    }
    else if (num0 < num1) {
        document.write(num0 + " is less than " + num1 + ".");
    }
    
  3. 单击运行。屏幕将显示“4 小于 8”改变num0num1的值,看看如何改变结果。只有当第一条if语句评估为false ( num0 >= num1返回false)时,才会评估else if()语句。

创建一个对象

对象是一个结构,它可以包含自己的变量(称为属性)和方法。当它们属于一个对象时,它们被称为实例属性实例方法。要创建一个使您能够获取当前日期的对象,请执行下列操作:

  • 1.清除您的代码。

  • 2.  Type in the code in Listing 2-13. This code creates a variable as an instance of an object called the Date object, which contains methods that can give us information about the current date. The new Date() part of the code is called the constructor of the Date object, and by assigning it to a variable, you create the object.

    清单 2-13。创建日期对象

    var date = new Date();
    
  • 3.  Type in the code in Listing 2-14. The methods of the date object return information about the year, month, or day of the current date. When getMonth() is called, 1 is added to the result because getMonth() counts January as 0, February as 1, and so on. Table 2-8 shows the methods that can be used with the Date object.

    清单 2-14。 给变量分配日期信息

    var y = date.getFullYear();
    var m = date.getMonth() + 1;
    var d = date.getDate();
    

表 2-8 。日期对象的方法

方法 影响 返回值
getFullYear() 返回四位数的公历年 {Number} 4 位数的公历年
getMonth() 返回月份- 1(因为它是一个数组引用) {Number}月 1 日
getDate() 返回日期 {Number}天
getDay() 返回一周中的第几天 一周中的第{Number}天
getHours() 返回小时 {Number}小时
getMinutes() 返回分钟 {Number}分钟
getSeconds() 返回第二个 {Number}秒
getMilliseconds() 返回毫秒 {Number}毫秒
  • 4.  Type in the code in Listing 2-15. You are concatenating multiple strings and values of the month, day, and year variables together into a single variable, text.

    清单 2-15。 创建一个字符串并显示它

    var text = "The date is " + m + "/" + d + "/" + y;
    document.write(text);
    
  • 5.单击运行。屏幕上会显示一个字符串,显示当前日期。如果遇到问题,可以在http://code.9leap.net/codes/show/19827查看完整的代码示例。

Date对象只是 JavaScript 预定义的众多对象之一。表 2-9 包含了一些与 JavaScript 捆绑在一起的主要对象。

表 2-9 。JavaScript 包含的主要对象

对象名称 目标描述
窗户 浏览器窗口
文件 浏览器中的网页
设计 网页中的框架
历史 浏览器历史记录
位置 当前页面位置
HTML 超链接
小应用 网页中的嵌入式程序
区域 图像上的可点击区域
形式 Web 表单
图像 网页上的图像
透明元素的图层
链接到外部样式资源
按钮 可点击按钮
检验盒 可标记的复选框
fileupload(文件上载) 文件上传对话框
隐藏的 隐藏密码
密码 接受密码
收音机 可标记单选按钮
重置 清除表单中的选项
文本 单行文本
链接区 文本区域
挑选 下拉菜单上的选择
排列 值数组
布尔代数学体系的 布尔值(true / false)
日期 存储日期
事件 随着事件(如单击)发生
功能 功能(方法)
数学 数值计算
航海家 浏览器信息
数字 存储一个号码
目标 存储代码元素
正则表达式 正则表达式
线 字符串

这个庞大的对象列表乍一看可能令人望而生畏,但它只是作为一个快速参考。如果您想了解更多信息,请参阅 Mozilla Developer Network 的 JavaScript guide 中的“预定义核心对象”一节,这是一个关于 JavaScript 基本核心对象及其用途的极好资源。

image 注意如果你熟悉 C/C++或 Java 等面向对象语言,你可能会熟悉的概念。在这些语言中,类充当对象的预定义模板。可以为给定的类定义属性和方法,然后从该类创建的所有对象都将继承这些属性和方法。

在 JavaScript 中,类的概念在技术上是不存在的,取而代之的是原型的概念。一个原型是一个特别指定的对象,可以为其定义属性和方法。当创建新对象时,它们可以从原型创建,并且将具有与原型相同的属性和方法。

起初,类和原型的概念可能看起来很相似,但是它们的限制是不同的。我们不会详细讨论基于类的编程和基于原型的编程,但主要的区别是从原型创建的对象可以用它们自己的函数定义覆盖预定义的原型函数,而作为类的一部分创建的对象通常不能这样做。

更智能地使用 While 和 For 循环语句

重复让你的编码生活变得更容易,因为在很多情况下,程序可以很容易地被设计来为你执行重复的任务。假设我们需要设计一种方法,将 1 到 1000 之间的所有数字相加,并将该值赋给一个变量(int)。如果我们手动添加 1 到 100 之间的每一个数字,我们最终会得到类似于清单 2-16 中的代码。

清单 2-16。 一种不切实际的将顺序数字相加的方法

int num = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 + 32 + 33 + 34 + 35 + 36 + 37 + 38 + 39 + 40 + 41 + 42 + 43 + 44 + 45 + 46 + 47 + 48 + 49 + 50 + 51 + 52 + 53 + 54 + 55 + 56 + 57 + 58 + 59 + 60 + 61 + 62 + 63 + 64 + 65 + 66 + 67 + 68 + 69 + 70 + 71 + 72 + 73 + 74 + 75 + 76 + 77 + 78 + 79 + 80 + 81 + 82 + 83 + 84 + 85 + 86 + 87 + 88 + 89 + 90 + 91 + 92 + 93 + 94 + 95 + 96 + 97 + 98 + 99 + 100;

哎哟。这看起来不像是一种很实用的消磨时间的方式,不是吗?为了加快速度,我们可以使用loop语句来为我们执行这个重复的计算。要查看实际效果,请执行以下步骤:

  1. 清除您的代码。

  2. Type in the code in Listing 2-17 to first create a variable, i, which will be used to control the loop (in other words, the control variable), and then create two other variables to store the results from the upcoming loops.

    清单 2-17。 创建变量

    var i;
    var sumFor;
    var sumWhile;
    
  3. Create a while loop to add together all numbers from 1 to 100 by typing in the code in Listing 2-18 after your current code. First, you must give sumWhile a value of 0 because you will be using a self-referencing operator (+=) in your code, so it must have a value before you begin. Next, you give i a value of 1. This number represents both the first time through the while loop, and the first number to be added to sumWhile. Finally, you enter the while loop. At the beginning of the while loop, the program will check to see if i has a current value equal to or less than 100. If it does, it will move into the loop, add the current value of i to sumWhile, and then add one to i. The loop will then check again to see if i is less than or equal to 100 and start again. This will continue repeatedly until the last value, 100, is added to sumWhile.

    清单 2-18。While 循环

    sumWhile = 0;
    i = 1;
    while (i <= 100) {
            sumWhile += i;
            i++;
    }
    document.write("The result using a while loop: " + sumWhile + "<BR />");
    

    image 注意document.write()方法中的“< BR / >”是一个 HTML 标签(用括号括起来的 HTML 元素的名称,指定要插入的网页元素),它告诉系统在显示更多文本之前向下移动一行。

  4. 单击运行。屏幕将显示 5050 的结果。

  5. Now do the same thing with a different kind of loop, the for loop, by typing in the code in Listing 2-19 below your current code. The for loop has three statements at the beginning of it. The first (i = 1;) declares and creates a variable to control the loop. The second one (i <= 100;) is checked before the for loop is run or rerun, exactly the same as the statement at the beginning of the while loop. Finally, the last statement (i++) is run when a for loop completes, before the second statement is checked against for a rerun.

    清单 2-19。For 循环

    sumFor = 0;
    for (i = 1; i <= 100; i++) {
            sumFor += i;
    }
    document.write("The result using a for loop: " + sumFor + "<BR />");
    
  6. 单击运行。屏幕将显示 5050 两次,因为两个循环计算的是相同的东西。如果遇到问题,您可以在http://code.9leap.net/codes/show/19828 .对照完整的代码样本检查您的代码

image 提示如果一个for语句只有一个命令,就像清单 2-19 中的一样,花括号就没有必要了。例如,清单中的for循环可以重写为

for (i = 1; i <= 1000; i++) sumFor = sumFor + i;

用 Break 中断循环

偶尔,在一个循环的执行过程中,我们希望停止处理该循环中的代码,并在循环结束后继续处理代码。这很容易用break语句来完成,该语句将当前处理从循环中断开,即使循环的条件仍然满足。要查看它的运行情况,请执行以下操作:

  1. 清除您的代码。

  2. Type in the code in Listing 2-20.

    清单 2-20。 打破循环

    var sumWhile = 0;
    var i = 1;
    while (true) {
        sumWhile += i;
        i++;
        if (i > 100) break;
    }
    document.write(sumWhile);
    
  3. 单击运行。结果和之前一样。当i等于 100 时,它被加到sumWhile,变成 101,然后因为它大于 100,while循环停止。

用 Continue 跳过循环迭代

在其他情况下,您可能希望跳过循环中剩余的代码,重新开始。只要满足了forwhile循环的条件表达式,就可以用continue语句来完成。发出该语句后,循环代码的下一次迭代将立即开始。像往常一样,在for循环中,当返回到循环代码的开始时,for循环的递增/递减表达式被执行。通过编写一个只计算从 0 到 100 的偶数的程序,执行以下步骤来验证这一点:

  1. 清除您的代码。

  2. Type in the code from Listing 2-21. The line if (i % 2 != 0) continue; says if the remainder after dividing i by 2 is not 0 (if i is odd), skip the remaining code in the current for loop iteration and begin the next for loop iteration immediately.

    清单 2-21。 利用继续跳过奇数

    var sumFor = 0;
    for (i = 1; i <= 100; i++) {
        if (i % 2 != 0) continue;
        sumFor = sumFor + i;
    }
    document.write("The sum of all even numbers up to 100 is " + sumFor);
    
  3. 单击运行。显示结果 2550。

生成随机数

如果你想让敌人角色随机出现在你的游戏中,或者如果你想创建一个游戏来告诉你当天的运气,并随机给你分配一个“运气”值,你需要能够创建随机数。执行以下操作,了解它在算命游戏中的工作原理:

  1. 清除您的代码。

  2. Type in the code in Listing 2-22 to create variables and assign a random number to num.

    清单 2-22。 为算命游戏创造变量

    var num;
    var text = "";
    num = Math.floor(Math.random() * 4);
    

    Math.random()返回一个从 0(包括 0)到 1(不包括 1)的值。将它乘以 4,得到一个从 0 到 4 的值,但不包括 4。Math对象的floor()方法将圆括号中的内容向下舍入。因此,您刚刚编写的代码将随机返回 0、1、2 或 3。

  3. Create the fortune-telling game using your random variable by typing in the code in Listing 2-23 below your current code.

    清单 2-23。 用 If 语句算命

    if (num===0) {
        text = "Super Lucky";
    } else if (num===1) {
        text = "So-so Luck";
    } else if (num===2) {
        text = "Minor Luck";
    } else {
        text = "Bad Luck";
    }
    document.write("Your fortune: " + text);
    

    这里,您根据num的值给text赋值,然后输出结果。

  4. 单击运行。你会随机得到一个关于你当前“幸运度”的回答

用开关定义场景

在最后一个代码示例中,使用了几个if语句来确定应该给text分配什么值。然而,有一种更简单的方法来实现这一点,不需要所有的if语句。请执行以下操作来了解如何操作:

  1. Delete the document.write() statement and all if statements. Your code should look as it does in Listing 2-24.

    清单 2-24。 为开关设置示例

    var num;
    var text = "";
    num = Math.floor(Math.random() * 4);
    
  2. Add the switch statement by typing in the code in Listing 2-25 below your current code.

    清单 2-25。 算命开关声明

    switch (num) {
            case 0:
                    textSwitch = "Excellent Luck";
                    break;
            case 1:
                    textSwitch = "Moderate Luck";
                    break;
            case 2:
                    textSwitch = "Small Luck";
                    break;
            default:
                    textSwitch = "Bad Luck";
                    break;
    }
    

    switch语句接受括号中的任何值,找到与该数字匹配的大小写,然后执行其下的代码。如果当前案例中没有break语句,switch 语句将继续执行下一个案例的代码,依此类推,直到遇到break语句或到达switch语句的结尾。如果没有找到匹配,则执行default案例。

  3. 单击运行。你会得到一笔随机产生的财富。

将编号数据存储在数组中

数组用于跟踪数据集,这在处理地图、字符串(文本字符数组)等时是必要的。执行以下操作,查看如何在数组中创建一组数字索引(编号)数据:

  1. 清除您的代码。

  2. Create an array to represent a collection of three devices by typing in the code in Listing 2-26.

    清单 2-26。 创建设备阵列

    var array = new Array(3);
    array[0] = "iPhone";
    array[1] = "Android";
    array[2] = "Computer";
    

    就像当我们创建一个Date对象的实例时,您使用一个构造函数来创建一个数组(new Array(3))。Array(3)告诉程序创建一个有 3 个空格的数组。

  3. Create a string to save parts of the array into it, and then iterate through the array, adding all of the array elements to the string before displaying it. Listing 2-27 shows how to do this.

    清单 2-27。 遍历一个数组

    var text = "";
    for (var i = 0; i < array.length; i++) {
        text += array[i] + "<BR />";
    }
    document.write(text);
    

    注意复合赋值操作符+=是如何被用来在字符串末尾添加字符的。使用一个for循环允许你自动遍历一个数组中的所有元素。

  4. 单击运行。屏幕将显示数组中的三个元素。

image 注意你不需要指定更大的数组来添加更多的条目到数组中。例如,在上面的例子中键入array[3]="iPad";不会导致错误。

数组对象的方法

在上一个示例中,您创建了一个数组对象。该对象带有几个可以在其上执行的方法。例如,如果您在代码末尾键入array.push("Galaxy");,它会将“Galaxy”添加到数组中最后一项之后的任何位置。表 2-10 提供了数组对象方法的总结。

表 2-10 。数组对象的方法

种类 密码 影响
添加元素 push(元素) 将元素添加到数组的末尾。
unshift(元素) 将元素添加到数组的开头。
移除元素 pop() 移除数组末尾的元素。
shift() 移除数组开头的元素。
排序元素 sort() 按升序对数组元素进行排序。
reverse() 对数组中的元素进行逆序排序。
提取元素 slice(开始,结束) 提取指定开始和结束位置内的元素,并用它们创建一个新数组。
slice(开始) 从指定的起始位置到数组的末尾提取元素,并用它们创建一个新数组。

在关联数组中存储无编号的数据

如果您想为刚刚创建的数组中的每个设备存储一种颜色,该怎么办?为此,您需要使用关联数组而不是常规数组。执行以下操作以创建关联阵列:

  1. 清除您的代码。

  2. Create your associative array by typing in the code in Listing 2-28.

    清单 2-28。 创建关联数组

    var obj = new Object();
    obj["iPhone"]     = "White";
    obj["Android"]    = "Black";
    obj["Computer"]   = "Silver";
    

    在 JavaScript 中,关联数组不是作为数组对象存储的,因为所有数组对象都是索引的,数组中的每个实体都包含一个键值(从 0 开始)和一个成对值。这里,我们使用 JavaScript 中对象作为值数组的能力,将设备名称和颜色的字符串链接在一起。

  3. Create a string, add the associative array objects, and then display the string by typing in the code in Listing 2-29.

    清单 2-29。 手动遍历关联数组

    var textObj = "";
    textObj += "The iPhone is " + obj["iPhone"] + ".<BR />";
    textObj += "The Android is " + obj["Android"] + ".<BR />";
    textObj += "The Computer is " + obj["Computer"] + ".<BR />";
    document.write(textObj);
    
  4. 单击运行。设备的颜色列表显示在屏幕上。

因为对象数组的索引方式不同于标准数组,所以不能用简单的for循环遍历它们。

使用功能节省时间

你已经见过自带函数的对象,比如数组,但是如果你打算做几乎任何需要你多次输入相同代码的事情,你可以定义你自己的函数来节省你的时间。请执行以下操作来了解如何操作:

  1. 清除您的代码。

  2. Define a function that accepts an argument (whatever is passed in parentheses), and then writes it on the screen after “I will buy the,” by typing in the code in Listing 2-30.

    清单 2-30。 定义购买()功能

    function buy(device) {
        document.write("I will buy the " + device + ".<BR />");
    }
    
  3. Call the function using “iPhone” and “Android” as arguments by typing in the code in Listing 2-31.

    清单 2-31。 调用功能

    buy("iPhone");
    buy("Android");
    
  4. 单击运行。屏幕将显示“我将购买”行两次,因为您调用了该函数两次。

查看变量的位置

我想让你想象一下,你要写的程序发生在一个中世纪的王国,有几个村庄和一座城堡。为了便于解释,假设这个王国的国王名叫鲍勃。在整个王国,如果有人谈论鲍勃,每个人都知道他们在谈论国王。然而,如果在一个给定的村庄里,还有一个叫鲍勃的人呢?在那个村子里,人们可能称当地居民鲍勃为“我们的鲍勃”,但在城堡里,如果有人称“鲍勃”,很可能每个人都会认为这个人就是国王。

这在本质上是范围的概念。它指的是程序中变量的可见性。换句话说,范围是变量的上下文。要了解其工作原理,请执行以下操作:

  1. 清除您的代码。

  2. Create a variable representing King Bob and assign him a value of 39 to represent his age by typing in the code in Listing 2-32. Because he is the ruler of the kingdom, we will create him as a variable at the beginning of the program, giving him a global scope. This means that if you type “bob” anywhere in the program, the program will know you mean King Bob.

    清单 2-32。 神树王鲍勃

    var bob = 39;
    
  3. Create a function that creates a variable representing a random Bob in one of the kingdom’s villages, assign him a random age between 10 and 50, and then increase that age by one to simulate the villager Bob celebrating his birthday, before displaying that Bob’s age on the screen, by typing in the code in Listing 2-33.

    清单 2-33。 创建函数创建随机 Bob

    function villager() {
            var bob = Math.floor(Math.random() * 50 + 10);
            this.bob += 1;
            document.write("King Bob's age is " + bob + ".<BR />");
            document.write("Villager Bob, this Bob, is " + this.bob + ".");
    }
    

    请注意,当您引用函数内部的 Bob 时,您使用this.bob来指定您指的是函数内部的那个。为了解释村民 Bob 的可见性,我们说他有函数作用域,而不是像 King Bob 那样有全局作用域。

  4. Call the villager function by typing in the code in Listing 2-34 below your current code.

    清单 2-34。 调用村民函数

    Villager();
    
  5. 单击运行查看结果。您将看到国王鲍勃的年龄为 39 岁,然后是村民鲍勃的随机年龄。

image 注意你不能引用在一个函数之外的函数中创建的变量。这是函数范围的限制。例如,我们不能在函数外部键入this.bob来引用村民 Bob。在villager()之外,我们唯一能看到或与之互动的鲍勃就是鲍勃大王。

用原型制作物体蓝图

原型本质上是一套可以用来创造物体的蓝图。通过创建具有属性和方法的原型,我们可以轻松地创建具有原型中指定的相同属性和方法的新对象。要创建和使用包含名称和编程语言的软件原型,请执行以下操作:

  1. 清除您的代码。

  2. Create the prototype first as a function, and then add properties by typing in the code in Listing 2-35.

    清单 2-35。 创建原型和属性

    function Software() {
    }
    
    Software.prototype.name     = "";
    Software.prototype.language = "";
    
  3. Create a function for the prototype that displays information about it by typing in the code in Listing 2-36.

    清单 2-36。 创建原型功能

    Software.prototype.output = function() {
        document.write(this.name + " is written in " + this.language + "<BR />");
    };
    
  4. Type in the code in Listing 2-37 to create a new object, accounting, as an instance of the Software prototype; specify values for its properties; and then call its function to display information on the screen.

    清单 2-37。创建会计对象

    var accounting = new Software();
    
    accounting.name     = "Spreadsheet Master";
    accounting.language = "Java";
    
    accounting.output();
    
  5. 单击运行。您将看到一条消息,说明 Spreadsheet Master 是用 Java 编写的。

  6. 尝试在accounting对象下创建另一个对象,并调用output()函数。如果你遇到麻烦,查看http://code.9leap.net/codes/show/19835中的工作示例。

image 注意以前面的代码为例,用accounting作为对象名,在这里输入类似于accounting.secondLanguage = "C++";.secondLanguage之前没有用var或者任何东西声明过,就可以随时给这个对象添加属性。这是一份即时声明。使用accounting.showLanguage = function() {document.write(this.language)};功能也可以以类似的方式添加到对象中。

结论

恭喜你!如果您已经通读了本节并使用了 code.9leap.net 的示例代码,那么您现在应该对变量、运算符、字符串、数组、函数和原型有所了解。这些是 JavaScript 的构建块,也是当今使用的许多面向对象语言的构建块。理解这些基础知识为你学习如何在 enchant.js 中编写自己的 HTML5 游戏打下了基础。

在第三章中,我们深入研究了 enchant.js 库,了解了精灵、场景、标签、表面等等。这些是 enchant.js 提供的基本特性。随着我们在《??》第三章和《??》第四章中的进展,你会开始看到这些特性是如何结合在一起创造一个游戏的。

三、enchant.js 的基本特性

既然您已经熟悉了 JavaScript 的基础知识,那么您应该对 enchant.js 的元素如何交互以及为什么要以特定的方式编写元素有了更好的理解。在这一章中,我们从你开始创作游戏需要知道的 enchant.js 的基本概念开始,然后我们探索集成这些概念的项目。我们涵盖了继承和显示对象树,这是每个 enchant.js 游戏都存在的两个元素,然后我们涵盖了标签、精灵、表面、触摸事件和基于屏幕的界面元素。我们在练习中大量使用了 code.9leap.net。一旦你读完这一章,你将对游戏的主要组件以及如何创建它们有一个基本的了解。

汇总列表

  1. 遗产
  2. 查看游戏的元素
  3. 创建标签
  4. 创建精灵
  5. 用曲面绘制地图
  6. 使用触摸进行互动
  7. 使用 D-pad 进行互动

继承

在面向对象编程中,继承是将一个对象归类为一个更大类别的成员的关系。为了更好地理解这一点,想象一个苹果。苹果是一种水果,借用面向对象编程的术语,我们可以说它继承了水果的品质。例如,苹果像所有其他水果一样含有种子。这类似于面向对象编程中的继承。

可以继承和被继承的对象的一个例子是 节点。游戏中的每一个物品都被称为一个节点。图 3-1 显示了不同类型的节点及其分类。

9781430247432_Fig03-01.jpg

图 3-1 。节点的分类

图中的每个项目都有自己的功能和属性。此外,每个项目还具有与层次结构中它上面的类别相同的功能和属性。例如,Node类包含一个名为moveBy(x, y)的函数,它移动节点的量等于作为 x 轴和 y 轴的参数传递的量。因为这个函数是Node类的一部分,所以一个EntityLabelSpriteMapGroup或者Scene都可以调用moveBy()。这叫继承。

查看游戏元素

创建游戏时,并不是游戏的所有部分都能立即看到。如果我们创建一个射击游戏,敌人出现在屏幕的顶部,并不是所有的敌人在游戏开始时都是可见的。如果是的话,这个游戏很可能要么太难被击败,要么很快结束。

相反,我们随着时间的推移在场景中创建和注册敌人,使他们可见。游戏中所有可见的元素都是称为显示对象树的对象层次结构的一部分。 显示对象树的根对象称为场景。创建游戏时,默认创建一个场景。这个默认场景被称为rootScene。可以创建多个场景,可以将多个对象注册到显示对象树中的场景,如图图 3-2 所示。

9781430247432_Fig03-02.jpg

图 3-2 。显示对象树

如果rootScene是活动场景,只有注册到它的对象在屏幕上可见。注册到场景 1 或场景 2 的对象将不可见。通过向场景中添加对象,移除对象,以及改变哪个场景是活动的,我们可以改变游戏过程中屏幕上显示的内容。我们将通过本章后面提供的代码示例,用更多的实际操作细节来检验这一点。

创建标签

在游戏中有很多情况下需要在屏幕上显示文本,例如当用户的分数在游戏过程中发生变化,关卡有时间限制,或者主要角色有特定的生命值。任何出现在游戏中的文本,除了风格化的艺术,都应该存在于标签中。enchant.js 中的标签是以一种允许它们在字体、颜色和位置方面容易操作的方式创建的。

作为本节的初始练习,我们将完成创建一个 enchant.js 游戏所需的所有步骤,该游戏可以创建随机位置和颜色的标签。

设置游戏

当您编写创建和执行游戏的 JavaScript 时,您必须遵循特定的格式来初始化或激活 enchant.js 库,并指定页面加载后要运行的游戏代码。请按照以下步骤查看如何操作:

  1. Go to http://code.9leap.net/codes/show/27204 and fork the code to have a workable blank template. You might need to log in or create an account to do this.

    image 注意如果你没有使用 code.9leap.net,你需要将 enchant.js 库导入到你的项目中,这将在第一章中介绍。

  2. Type in the code shown in Listing 3-1.

    清单 3-1。 初始化 enchant.js 库

    enchant();
    

    这个命令告诉 enchant.js 初始化创建和运行游戏所需的所有代码。

  3. Under that, type in the code shown in Listing 3-2.

    清单 3-2。 创建 window.onload()

    window.onload = function() {
    }
    

这指示浏览器只有在加载了所有其他内容之后才运行花括号({})中的代码。这确保了在游戏真正开始之前,enchant.js 运行所需的一切都已存在。每个 enchant.js 游戏都必须有这两个组件才能运行。我们在花括号中输入了几乎所有的游戏代码。

制作核心物品并开始游戏

既然我们已经为我们的游戏完成了必要的基础工作,我们可以创建游戏的核心,即Core对象。Core对象是游戏本身,在创建时定义了游戏的大小。游戏的所有元素(场景、标签、精灵等等)都是在这个Core对象中创建的。要创建它,请执行以下操作:

  1. Modify your window.onload statement to match Listing 3-3. This creates the Core object, specifying the size of the game to be 320 pixels wide (first argument) by 320 pixels tall (second argument). Assume that all code instructions past this point should be entered within the curly brackets, beneath the last line of code entered unless stated otherwise.

    清单 3-3。 创建核心对象

    window.onload = function() {
            var game = new Core(320, 320);
    }
    

    image 注意在 0.6 版本之前,Core对象被称为Game对象。如果你使用早期版本的 enchant.js 创建的游戏,你可能会看到使用Game对象而不是Core对象(var game = new Game(320,320))的符号。这种情况已经改变,因为 enchant.js 越来越多地用于非游戏应用。

    实际上,如果你遇到一个Game对象,你可以把它当作一个Core对象。

  2. Place your cursor at the end of the line that reads var game = new Core(320, 320); and press Enter a few times to move your cursor down a few lines. Type in the code in Listing 3-4. This command tells the game to start and should always appear at the end of your window.onload statement.

    清单 3-4。 开始游戏

    game.start();
    

创建标签

下一步是创建一个带有消息的标签。执行以下操作来创建一个:

  1. After the line var game = new Core(320, 320);, type in the code in Listing 3-5. This creates a label with the text we designate in quotes.

    清单 3-5。 创建标签

    var label = new Label("Hello World!");
    

将标签添加到根场景

如果此时单击 Run 按钮,将不会发生任何事情。正如我们前面提到的,游戏中的物体必须添加到活动场景中才能可见。执行以下操作来添加它:

  1. Type in the code in Listing 3-6 below the last line of code entered. This adds the label we created to the rootScene.

    清单 3-6。 给 rootScene 添加标签

    game.rootScene.addChild(label);
    
  2. 单击运行按钮。“你好世界!”出现在屏幕的左上角,因为标签现在是默认场景rootScene的一部分。

更改标签的属性

我们之前讨论了 enchant.js 中的对象如何拥有自己的属性,这些属性控制着游戏中这些对象的特征。要更改标签的特征,请执行以下操作:

  1. Above the line game.rootScene.addChild(label);, type in the code in Listing 3-7. This makes several changes to the appearance of the label.

    清单 3-7。 改变标签的属性

    label.font = "16px sans-serif";
    label.color = "rgb(255,0,0)";
    label.x = 50;
    label.y = 100;
    
  2. 单击运行。标签以不同的位置、字体和颜色出现。

格式化字体

在清单 3-7 中,我们用label.font = "16px sans-serif"改变了标签的字体。

指定的第一个属性是字体的大小。这由一个数字表示,以像素为单位指定字体的大小,后跟字母“px ”,两者之间没有空格。

接下来,指定字体的名称。可以使用预定义的字体名称(字体系列名称),如“MS PGothic”,但有些设备不包含这些字体。如果没有特定字体的设备试图呈现标签,用户可能会遇到错误。对于用户来说,错误从来都不是有趣的事情,这就是为什么你的字体最好使用通用名称,如下所示。请注意,衬线是字母末端的风格化点,最常见于印刷中使用的字母。

  • 无衬线字体:哥特式字体(无衬线)
  • 衬线:罗马字体(带衬线)
  • 单空间:固定宽度字体

格式颜色

在清单 3-7 中,我们用label.color = "rgb(255,0,0)";改变了标签的颜色。

enchant.js 中的颜色可以为标签和任何其他接受颜色的元素设置,使用任何对 CSS(级联样式表)中的颜色有效的格式。如果您不熟悉 CSS,请不要担心,因为我们将在这里介绍不同的格式。CSS 中有六种有效的颜色分配方式,如表 3-1 所示。由于该属性被定义为一个字符串,请确保将赋值用双引号("")括起来。

表 3-1 。不同的方式指定颜色红色

值(本例中为红色) 描述
" #ff0000 " " #RRGGBB "格式。这些值在“00 FF”之间定义为以 16 为基数的数字。
" #f00 " “#RGB”格式。这些值定义在“0 \u F”之间,以 16 为基数。这种格式创建的颜色与“#ff0000”相同,只是一种缩写格式。
rgb(255,0,0) “rgb(R,G,B)”格式。这些值被定义为以 10 为基数的“0 \u 255”之间的数字。
" rgb(100%,0%,0%)" “rgb(R,G,B)”格式。这些值被定义为以 10 为基数的百分比,介于“0%∞100%”之间。
「rgba(255,0,1.0)」 “rgba(R,G,B,A)”格式。RGB 定义在“0 \u 255”之间,透射率(alpha 透明度)定义在“0.0 \u 1.0”之间(0 表示完全透明,1 表示完全不透明)。
“红色” 颜色由名称定义。

此外,表 3-1 中的大多数列表表示三种颜色的混合:红色、绿色和蓝色。为了指定红色,我们选择红色的最大值(以 16 为基数的 ff)和绿色和蓝色的最小值(以 16 为基数的 00)。在 16 进制中,0–9 与 10 进制相同(正常计数),然后 10 = A,11 = B,依此类推,直到 16 = F,16 进制中字母的大小写没有区别(也称为十六进制格式)。

定义位置

通过设置标签的 x 和 y 属性值来定义位置。在清单 3-7 中,我们用label.x = 50;将标签的位置设置为距离游戏屏幕左边缘 50 像素,用label.y = 100;设置为距离游戏屏幕上边缘 100 像素。

创建一个为我们创建标签的函数

优化是编程中的一个常见概念。这意味着使用尽可能少的代码,或者使用代码快捷方式,来创建一个游戏或其他应用。在我们的游戏中,我们将创建多个标签,因此创建一个为我们创建和配置标签的函数要比手动多次创建标签容易得多,而且这个函数可以被调用无数次。请执行以下操作来了解如何操作:

  1. Delete the code you wrote from Listings 3-6 and 3-7 and replace it with the code in Listing 3-8. This function will accept four arguments, create a Label with the text argument, specify the color to be whatever was indicated for the color argument, set the position with the x and y arguments, and then add the label to the rootScene.

    清单 3-8。创建 addLabel 函数

    game.addLabel = function(text, color, x, y) {
            var label = new Label(text);
            label.font = "16px sans-serif";
            label.color = color;
            label.x = x;
            label.y = y;
            game.rootScene.addChild(label);
    };
    

    这本身不会产生标签。相反,它包含创建标签的说明。要创建标签,我们必须调用函数并提供参数。

  2. Under the preceding code, type in the code in Listing 3-9.

    清单 3-9。 调用 addLabel

    game.addLabel("50 Points", "rgb(255,0,0)", 50, 50);
    game.addLabel("100 Points", "rgb(50,0,100)", 50, 200);
    
  3. 单击运行。将出现红色标签和紫色标签。

创建随机数函数

为什么我们需要随机数?因为我们要给我们创建的标签分配随机属性,并且需要随机数来完成。执行以下操作创建随机数函数:

  1. Go to the very bottom of your code, below the last semicolon of the window.onload statement, and type in the code in Listing 3-10.

    清单 3-10。 随机数功能

    function rand(num){
            return Math.floor(Math.random() * num);
    }
    

floor()random()都是Math对象的方法,这是 JavaScript 中预定义的对象。rand()函数的定义接受一个参数num,该参数乘以Math.random()的结果。Math.random()将返回一个介于 0 和 1 之间的小数值。Math.floor将结果向下舍入到最接近的整数,然后该数字将作为rand()函数的结果返回(提供)。

image 注意十进制值Math.random()可以返回的范围包括 0 但不包括 1。例如,如果我们将Math.random()乘以 100,理论上可以返回的最小值是 0,理论上可以返回的最大值是非常接近 100 的值,但不完全是 100(换句话说,接近 99.99999999999)。用Math.floor()将它向下舍入,得到一个介于 0 和 99 之间的随机整数。

例如,如果我们调用rand(100),程序返回 0.88134 作为Math.random()的返回值,它将首先乘以我们指定的参数(100),变成 88.134。然后通过Math.floor()将其向下舍入到 88 并返回。创建这个函数的理由将在下一节中变得显而易见。

使用 ENTER_FRAME 事件监听器自动创建标签,并定期处理

现在我们已经有了轻松创建标签所必需的函数,我们需要能够以固定的时间间隔调用该函数的东西,也称为周期性处理。我们可以用一个事件监听器来做这件事。事件侦听器是代码的一部分,它不断地寻找特定的事件,当事件发生时,事件侦听器触发我们创建的特定代码。请执行以下操作来了解如何操作:

  1. Delete the two game.addLabel() statements you created in Listing 3-9 and replace them with the code in Listing 3-11. This code instructs the program to execute the code within the curly braces ({}) every time the ENTER_FRAME event occurs. This event occurs every frame.

    清单 3-11。 ENTER_FRAME 事件监听器

    game.rootScene.addEventListener(Event.ENTER_FRAME, function() {
    });
    

    您可以为任何Node对象(不仅仅是rootScene)创建事件监听器,并设置它们监听 enchant.js 中的任何事件。表 3-2 列出了 enchant.js 中的主要事件

    表 3-2。事件

    事件类型 描述 事件发布者
    Event.A_BUTTON_DOWN 按下 a 按钮时发生的事件 核心,场景
    EventA_BUTTON_UP 释放 a 按钮时发生的事件 核心,场景
    Event.ADDED 将节点添加到组时发生的事件 结节
    Event.ADDED_TO_SCENE 将节点添加到场景中时发生的事件 结节
    Event.B_BUTTON_DOWN 按下 b 按钮时发生的事件 核心,场景
    Event.B_BUTTON_UP 释放 b 按钮时发生的事件 核心,场景
    Event.DOWN_BUTTON_DOWN 按下向下按钮时发生的事件 核心,场景
    Event.DOWN_BUTTON_UP 释放向下按钮时发生的事件 核心,场景
    Event.ENTER 场景开始时发生的事件 事件
    Event.ENTER_FRAME 处理新帧时发生的事件 核心,场景
    Event.EXIT 场景结束时发生的事件 事件
    Event.EXIT_FRAME 帧处理即将结束时发生的事件 核心
    Event.INPUT_CHANGE 按钮输入改变时发生的事件 核心,场景
    Event.INPUT_END 按钮输入结束时发生的事件 核心,场景
    Event.INPUT_START 按钮输入开始时发生的事件 核心,场景
    Event.LEFT_BUTTON_DOWN 按下左键时发生的事件 核心,场景
    Event.LEFT_BUTTON_UP 释放左键时发生的事件 核心,场景
    Event.LOAD 游戏加载完成时调度的事件 核心
    Event.PROGRESS 游戏加载期间发生的事件 核心
    Event.REMOVED 从组中删除节点时发生的事件 结节
    Event.REMOVED_FROM_SCENE 从场景中移除节点时发生的事件 结节
    Event.RENDER 呈现实体时发生的事件 实体
    Event.RIGHT_BUTTON_DOWN 按下右键时发生的事件 核心,场景
    Event.RIGHT_BUTTON_UP 释放右键时发生的事件 核心,场景
    Event.TOUCH_END 当与节点相关的触摸结束时发生的事件 结节
    Event.TOUCH_MOVE 当与节点相关的触摸移动时发生的事件 结节
    Event.TOUCH_START 与节点相关的触摸开始时发生的事件 结节
    Event.UP_BUTTON_DOWN 按下向上按钮时发生的事件 核心,场景
    Event.UP_BUTTON_UP 释放向上按钮时发生的事件 核心,场景
  2. Type in the code in Listing 3-12 inside the curly braces to create and assign variables to represent a random score to be displayed; the value of red (in the RGB color component of the label), green, and blue; and the x and y position.

    清单 3-12。 创建变量并分配随机值

    var score = rand(100);
    var r = rand(256);
    var g = rand(256);
    var b = rand(256);
    var x = rand(300);
    var y = rand(300);
    
  3. Below the code you just entered, but still inside the curly braces, call the addLabel() function we created earlier, passing the random variables as the arguments of the function, by typing in the code in Listing 3-13.

    清单 3-13。 调用 addLabel()函数

    game.addLabel(score + " Points", "rgb(" + r + "," + g + "," + b + ")", x, y);
    
  4. 单击运行。标签可以在屏幕上快速创建。

使用帧和模数 减慢处理速度

标签的创建在这一点上可能看起来有点失控,因为在您单击 Run 按钮之后,不久整个屏幕就会完全充满标签。幸运的是,你可以放慢速度。请执行以下操作来了解如何操作:

  1. Enter the code you wrote in Listing 3-12 and 3-13 inside an if statement, as shown in Listing 3-14. Here, game.frame is equal to the number of frames that have elapsed in a game, and the modulo operator (%) will return the remainder after dividing the number of frames by 3. If that value is equal to 0, a label will be created. The final effect of this code is to cause a label to be created every third frame.

    清单 3-14。 控制标签创建的频率

    if (game.frame % 3 === 0) {
            var score = rand(100);
            var r = rand(256);
            var g = rand(256);
            var b = rand(256);
            var x = rand(300);
            var y = rand(300);
            game.addLabel(score + " Points", "rgb(" + r + "," + g + "," + b + ")", x, y);
    }
    
  2. 单击运行。标签的创建速度要慢得多。

指定时间后移除标签

现在创建标签的速度更慢了,但过不了多久,它们仍然会填满屏幕。要在标签创建后清除它,我们需要使用另一个事件监听器。

  1. Type in the code in Listing 3-15 under game.rootScene.addChild(label); (still inside the window.onload function curly braces). Here, we add an event listener to the labels we create, specifying to remove the label from the document object tree if the label’s age, or the number of frames the label has been part of the document object tree, exceeds 10 frames.

    清单 3-15。 去除标签

    label.addEventListener(Event.ENTER_FRAME, function() {
            if (label.age > 10) game.rootScene.removeChild(label);
    });
    

    你有没有注意到在if语句后面没有花括号?这是因为只有一个语句。如果没有花括号,if语句只触发后续的单行代码。

  2. 单击运行。标签是随机创建的,并在创建后不久从屏幕上删除。

使标签移动

作为标签的最后一个效果,我们希望它们在创建后向上移动。执行以下操作,指示它们每帧向上移动一个像素:

  1. Directly above the if statement you entered from Listing 3-15, but still within the event listener curly braces, enter the code from Listing 3-16. This simply reduces the y position of the label by one.

    清单 3-16。 移动标签

    label.y --;
    
  2. Because the ENTER_FRAME event listener executes its code every frame, the label moves up the screen at a rate of 30 frames every second, as the frames per second (fps) setting of the Core object is set to 30 fps by default.

    image 注意你可以通过修改Core对象的fps属性来改变每秒帧数的设置(换句话说就是game.fps = 16;)。

  3. 单击运行。标签会显示出来,并在消失之前在屏幕上向上移动。

标签用于显示游戏中的分数等,前面的练习简要介绍了如何创建标签。随着我们的继续,我们将更多地使用它们。如果你遇到任何问题,你可以在http://code.9leap.net/codes/show/27211找到一个完整的工作代码样本。

创建精灵

虽然也有例外,但大多数游戏都有一个主角,或者敌人,或者盟友,或者某种角色。这些角色由图像表示,通常以某种身份四处活动。在 enchant.js 中,角色由精灵表示。创建精灵时,它配备了许多属性和方法,使其更容易操作。

为精灵设置游戏

要为精灵设置游戏,您需要为精灵预加载图像并创建一个game.onload语句。执行以下操作,查看如何创建一个在屏幕上行走的熊精灵:

  1. http://code.9leap.net/codes/show/27204分叉空白的 enchant.js 模板。

  2. Type in the code in Listing 3-17 to create the basic structure of the game. Here, we use the preload function to load in the image of a bear to be used for the sprite. This image is included in the project on code.9leap.net and was included when you forked it in the preceding step.

    清单 3-17。 熊游戏的基本结构

    enchant();
            window.onload = function() {
            var game = new Core(320, 320);
            game.preload('chara1.gif');
    
            game.onload = function() {
    
            };
    
            game.start();
    };
    

这个代码示例还包含了game.onload函数,当加载图像或者不仅仅是添加和删除节点和事件监听器时,这个函数是必需的。这个函数类似于window.onload,在游戏完全加载后执行它的代码。

创建精灵

要创建 sprite,必须声明一个代表 sprite 的变量,必须给它分配一个图像,然后必须将其添加到文档对象树中。以下步骤向您展示了如何完成此过程:

  • 1.  Create a variable to represent a 32-pixel-wide by 32-pixel-tall sprite by typing in the code in Listing 3-18 inside the game.onload function. You can assume that all future references to placing code inside a function mean to place that code inside the curly braces at the end of that function.

    清单 3-18。 创建精灵变量

    var bear = new Sprite(32, 32);
    
  • 2.  Assign the image you preloaded in the last section by typing in the code in Listing 3-19 on the next line.

    清单 3-19。 将图像分配给图像属性

    bear.image = game.assets['chara1.gif'];
    
  • 3.  On the next line, add the bear to the rootScene to make it visible by typing in the code in Listing 3-20.

    清单 3-20。 使熊可见

    game.rootScene.addChild(bear);
    
  • 4.单击运行。这只熊出现在屏幕的左上角。

指定精灵的框架

如果您在这个项目中使用code.9leap.net,您将会看到chara1.gif在屏幕右侧以缩略图的形式出现。这张图片包含了几只熊的图片,那么为什么目前只有一只熊出现在我们的游戏中呢?答案与框架有关。

当你用Sprite(32,32)创建一个精灵时,程序会创建一个尺寸为 32×32 像素的精灵。当您将图像指定给精灵时,程序会将该图像分割成相同大小的帧,并从 0 开始用数字标识它们。这个概念被称为帧索引,如图 3-3 中的chara1.gif所示。

9781430247432_Fig03-03.jpg

图 3-3 。帧索引

通过将所需的帧数设置为 frame 属性,可以为 sprite 选择帧。为此,请执行以下操作:

  • 5.  Select the white standing bear as the frame by inserting the code in Listing 3-21 above the game.rootScene.addChild(bear); statement. Inserting the code after the addChild function carries the risk of the brown bear appearing onscreen and then switching to the image of the white bear.

    清单 3-21。 分配白熊帧

    bear.frame = 5;
    
  • 6.单击运行。精灵看起来像一只白熊。

制作精灵动画

我们有图像是很好的,但是一个画面的真正意义是创造运动的幻觉。最后,我们将指示熊在屏幕上来回移动,因为我们希望熊看起来像是在走路。在我们的 chara1.gif 图像中,有一些白熊的图像,我们可以快速连续显示来实现这一点。请执行以下操作来了解如何操作:

  1. Replace the bear.frame line you just added with the code shown in Listing 3-22. This notation is an array of numbers and acts as a shortcut for animation. For each frame, the frame will move to the next indicated frame in the sequence.

    清单 3-22。 动画熊

    bear.frame = [5, 6, 5, 7];
    
  2. 单击运行。熊精灵在帧中快速循环。这是我们的目标,但现在看来,这只熊似乎有点神经质。让我们让他慢一点。

  3. Rewrite your bear.frame statement to match the code in Listing 3-23. This slows down the rate of the frame change by assigning the same image for two frames instead of just one.

    清单 3-23。 减缓熊的定格

    bear.frame = [5,5,6,6,5,5,7,7];
    

    image 注意不要使用清单 3-23 ,你可以在游戏开始前写一个game.fps = 16;语句,让游戏每秒处理 16 帧,而不是默认的 30 帧,来减缓熊改变帧的速度。请记住,这将降低游戏中所有实体的帧速率。

  4. 单击运行。为了行走,熊以更正常的速率改变帧。

移动精灵

精灵现在有适当的动画行走,但不移动。执行以下操作,在屏幕上移动小熊:

  1. Create an event listener on the bear sprite registered to the ENTER_FRAME event by typing in the code in Listing 3-24 under game.rootScene.addChild(bear);. Enter the code inside the curly braces of the game.onload function.

    清单 3-24。 在 Bear 上创建事件监听器

    bear.addEventListener(Event.ENTER_FRAME, function() {
    });
    
  2. Create periodic processing inside the event listener to move the bear to the right by increasing the x position by 3, as shown in Listing 3-25.

    清单 3-25。 动人的熊

    bear.x += 3;
    
  3. 单击运行。熊跑过屏幕。

  4. Create an if statement to move the bear only to the right if the bear’s location is less than the edge of the screen by modifying Listing 3-25 to match Listing 3-26. We use 320 – 32 because, while 320 is the edge of the right side of the game screen, the position of the sprite is measured from the top-left corner. When the sprite is at x = 320, the bear will not be visible. Since 32 is the width of the sprite, putting the limit of the bear at 320 – 32 ensures the bear stays onscreen.

    清单 3-26。 用 If 语句约束运动

    if (bear.x < 320 - 32) bear.x += 3;
    

确定精灵的方向以匹配其运动

让我们让熊精灵在屏幕上来回行走,面向它行走的任何方向。为此,我们使用了scaleX属性。请执行以下操作来了解如何操作:

  1. Insert the code in Listing 3-27 above the game.rootScene.addChild(bear); statement. The scaleX property defaults to the value of 1 when the sprite is created and can be modified to change the size of the bear (in other words, scaleX = 2; will scale the bear to 200%). By making scaleX a negative value, the image of the bear is flipped across the x-axis, making the bear face the left side of the screen.

    清单 3-27。 反转熊横过 X 轴

    bear.scaleX = -1;
    

    image 注意使用sprite.scaleY = -1;可以在 y 轴上反转精灵图像。

  2. 单击运行。这只熊似乎在屏幕上向后走。这不是我们想要的最终结果,但是它让您了解了scaleX属性是如何工作的。我们将使用scaleX属性来指示熊在接下来的几个步骤中如何移动。

  3. 删除您在清单 3-26 中输入的代码。

  4. Replace the if statement (if (bear.x < 320 - 32) bear.x += 3;) in the bear’s event listener with Listing 3-28. This code sample specifies that if the bear is facing right, it should move to the right by 3 pixels for each frame and should be flipped to face left if the bear reaches the right side of the screen. If the bear faces left, the bear should move to the left by 3 pixels for each frame and should be turned around to face right again when it reaches the left side of the screen.

    清单 3-28。 定向熊

    if (bear.scaleX === 1) {
            bear.x += 3;
            if (bear.x > 320 - 32) bear.scaleX = -1;
    } else {
            bear.x -= 3;
            if (bear.x < 0) bear.scaleX = 1;
    }
    
  5. 单击运行。熊按照描述移动。

到目前为止,您应该已经熟悉了使用事件侦听器来处理每一帧上的代码。这是 enchant.js 中很多游戏的核心,如果你遇到任何 Sprite 代码的问题,可以在http://code.9leap.net/codes/show/27365找到完全工作的代码样例。

用曲面绘制地图

enchant.js 中的地图是由几个叫做 tiles 的小图像组成的。假设你正在创建一个游戏,背景是一片片绿草,一个角色在屏幕上来回移动。要在 enchant.js 中创建一个充满绿草瓷砖的地图,您需要首先将这些瓷砖添加到一个Surface对象中。

Surface对象是 enchant.js 中用于绘图的对象。绘画是指在一个物体上创造形状(这里不讨论)或图像的行为。一旦绘制了表面,就将其指定为 sprite 的 image 属性,以将其加入到显示对象树中。使用一个Surface对象允许在一个对象上绘制多个图像,这使得它成为显示地图的主要候选对象。在下面的代码示例中,我们创建了一个完全由绿草平铺组成的地图。

设置游戏

执行以下操作来设置一个使用Surface对象的示例游戏:

  1. http://code.9leap.net/codes/show/27204分叉空白的 enchant.js 模板。

  2. Set the groundwork for your game by typing in the code in Listing 3-29. The preloaded map0.gif file, shown in Figure 3-4, contains the component tile images that we will use to create the map.

    清单 3-29。 设置游戏

    enchant();
    window.onload = function() {
            var game = new Core(320, 320);
            game.fps = 16;
    
            game.preload('http://enchantjs.com/assets/img/map0.gif');
    
            game.start();
    };
    

    9781430247432_Fig03-04.jpg

    图 3-4 。map0.gif

为地图创建容器

在我们用切片填充地图之前,地图必须有几个容器可以使用。执行以下操作来创建它们:

  1. Below the preload statement, type in the code in Listing 3-30. Here, we create a variable to act as the sprite, which will display the map. We assign our preloaded map image to the variable maptip, which makes it easier to reference later. Finally, we create a new Surface, which is what we will draw our map tiles on.

    清单 3-30。 创建容器

    game.onload = function() {
            var bg = new Sprite(320, 320);
            var maptip = game.assets['http://enchantjs.com/assets/img/map0.gif'];
            var image = new Surface(320, 320);
    };
    

用图块推广地图

draw()命令绘制一个Surface对象。在本节中,我们认为draw()命令是用来复制和粘贴图像到一个表面上的。由于我们的 16x16 瓷砖图像必须一次一个地粘贴到我们的空白表面上,我们可以编写一个很长的这些语句的列表来绘制瓷砖,但是使用循环来这样做可以节省我们的时间。执行以下操作来创建循环:

  1. Create two for loops to represent x and y coordinates by typing in the code in Listing 3-31 below var image = new Surface(320,320); but still inside the game.onload function.

    清单 3-31。 平铺循环

    for (var j = 0; j < 320; j += 16) {
            for (var i = 0; i < 320; i += 16) {
    
            }
    }
    

这一开始可能会令人困惑,但是将两个for循环放在一起是一个非常有用的工具,可以在一个Surface上增量移动,这正是我们在地图上以特定间隔放置图块所需要做的。

将变量ji分别视为空白背景上的虚拟光标的 y 和 x 坐标。想象一下这个空白背景被切割成 16 像素乘 16 像素的正方形,做成一个巨大的桌子。变量j代表我们假想的光标的行位置,变量i代表列位置。这个假想的光标总是指向一个 16 x 16 方块的左上角,因为那是所有使用draw()功能的操作的起点。

当进入第一个for循环时,j的值为0。然后进入第二个for循环,其中i也具有初始值0。我们假想的光标当前在(0,0),在表格的左上角。我们将在一个draw()命令中使用这个位置来复制一个图块,它还没有显示。在draw()命令之后,第二个for循环重复,这次i值为 16。我们的draw()命令现在将在(16,0)运行。如此重复,直到i变为 320。这时,第一排瓷砖将被绿色瓷砖填满。然后,重复第一个for循环,该过程从第二行开始,依此类推。

但是我们不要想太多。我们必须首先创建draw()命令,这样才能工作。

  • 2.   Inside the second for loop, create the draw() command to actually copy the tiles throughout the for loop by copying Listing 3-32 into the second for loop (for (var i = 0; i < 320; i += 16) {}).

    清单 3-32。 绘制命令复制图块

    image.draw(maptip, 0, 0, 16, 16, i, j, 16, 16);
    

是的,这个命令有很多参数。他们是这样做的:

  • maptip:这是要用作源图像的预加载图像资产(要在地图上使用的图块的图像)。
  • 0, 0:这是一个假想矩形左上角的 x 和 y 坐标,将用于从原始图像中捕捉一个区域。如果你想象在一幅图像上点击并拖动一个选择框来复制它的一部分,这将是点击的起点。
  • 16, 16:这是假想矩形右下角的 x 和 y 坐标,用来从源图像中捕捉一个区域。如果您单击并拖动来创建选择框,这些将是您放开单击的坐标。
  • i, j:目的地Surface上的 x 和 y 坐标,表示所拍摄图像粘贴位置的左上角。在调用几个draw()语句的过程中,通过for循环来改变这些值,以完美地分隔瓦片。
  • 16, 16:最后两个值表示要粘贴的图像的宽度和高度。如果这些从捕获区域的大小改变,它将在粘贴之前缩放图像。

将地图分配给背景并显示它

既然我们已经将图块复制到目的地Surface,我们需要将它添加到bg sprite 中,这样我们就可以将它添加到显示对象树中。

  1. Under the for loops, but still inside the game.onload function, type in the code in Listing 3-33.

    清单 3-33。 添加地图

    bg.image = image;
    game.rootScene.addChild(bg);
    
  2. 单击运行。地图出现,充满了绿色的草地瓷砖。

在 enchant.js 中,地图构成了许多游戏的背景。尽管创建地图的技术可能很难掌握,但熟悉地图对于创建更具沉浸感的游戏来说是无价的。

使用触摸进行交互

创建一个在草地上移动玩家的游戏的下一步是创建一个角色,当玩家点击屏幕时,他移动到屏幕上的那个点。这是通过用户点击游戏屏幕时发生的触摸事件来实现的。

创建角色

记住 enchant.js 中的游戏主要由每一帧运行的循环控制,这一点很重要。如果我们有一个角色在屏幕上走来走去,它将不得不为每个移动方向设置精灵,并进行相应的处理。请执行以下操作来查看这是如何实现的:

  1. At the very top of your code, before the enchant() command, add constants (values that will not change) by typing in the code in Listing 3-34. These values are used for specifying sprites for movement.

    清单 3-34。 创建方向常数

    var DIR_LEFT  = 0;
    var DIR_RIGHT = 1;
    var DIR_UP    = 2;
    var DIR_DOWN  = 3;
    
  2. Add chara0.gif (Figure 3-5) to the list of images to be preloaded by modifying the game.preload statement to the one shown in Listing 3-35.

    9781430247432_Fig03-05.jpg

    图 3-5 。chara0.gif

    清单 3-35。 给要加载的图片添加 chara0.gif

    game.preload('http://enchantjs.com/assets/img/chara0.gif',
            'http://enchantjs.com/assets/img/map0.gif');
    
  3. Create a variable to represent our character, the girl from chara0.gif, and set the basic properties of that character by typing in the code in Listing 3-36 directly under game.rootScene.addChild(bg);. Setting the x and y coordinates to 160 – 16 causes the character to appear directly in the center of the screen. The frame is set to 7 to show the girl facing down first.

    清单 3-36。 塑造基础少女角色

    var girl = new Sprite(32, 32);
    girl.image = game.assets['http://enchantjs.com/assets/img/chara0.gif'];
    girl.x     = 160 - 16;
    girl.y     = 160 - 16;
    girl.frame = 7;
    
  4. Create secondary properties for the girl and add her to the document object tree by typing in the code in Listing 3-37 directly below the preceding code. The toX and toY properties are created to represent the location the girl will head toward when a player clicks the screen. The girl.anim array represents frame numbers used for animating the walking motion of the girl for all four directions.

    清单 3-37。 为女孩创造次级属性

    girl.toX   = girl.x;
    girl.toY   = girl.y;
    girl.dir   = DIR_DOWN;
    girl.anim  = [
            15, 16, 17, 16, //Left
            24, 25, 26, 24, //Right
            33, 34, 35, 34, //Up
            6,  7,  8,  7]; //Down
    game.rootScene.addChild(girl);
    
  5. 单击运行。女孩出现在草砖前的屏幕上。

处理触摸动作

最后一步是建立一个事件监听器,如果toXtoY与女孩的当前位置实质上不同,则移动女孩,并处理用户做出的触摸事件。请执行以下操作来了解如何操作:

  1. Create an event listener for the girl character to process movement by typing in the code in Listing 3-38 under game.rootScene.addChild(girl);. Like all ENTER_FRAME event listeners, this will be processed on the girl sprite for every frame.

    清单 3-38。 为女孩创建事件监听器

    girl.addEventListener(Event.ENTER_FRAME, function() {
    };
    
  2. Within the event listener you just added, create an if statement to process the upward movement of the girl by typing in the code in Listing 3-39. This code sample states that if the current Y position of the girl is lower (greater) than the girl’s destination (toY), the girl’s direction should be set as DIR_UP and the girl should be moved up by 3 pixels each frame, unless the girl’s current Y position is within 3 pixels of the destination. This is done by checking to see if the absolute value (Math.abs()) of the girl’s Y position minus the girl’s destination Y position is less than 3 pixels.

    清单 3-39。 加工向上运动

    if (girl.y > girl.toY) {
            girl.dir = DIR_UP;
            if (Math.abs(girl.y - girl.toY) < 3) {
                    girl.y=girl.toY;
            } else {
                    girl.y -= 3;
            }
    }
    
  3. Add the code to process the movement for the remaining directions (down, left, and right) by typing in the code in Listing 3-40 under the code you just added, but still within the girl’s event listener. This code uses the exact same approach as the preceding code to move the girl in the appropriate direction.

    清单 3-40。 加工其他方向的运动

    else if (girl.y < girl.toY) {
            girl.dir = DIR_DOWN;
            if (Math.abs(girl.y - girl.toY) < 3) {
                    girl.y = girl.toY;
            } else {
                    girl.y += 3;
            }
    }
    
    if (girl.x > girl.toX) {
            girl.dir = DIR_LEFT;
            if (Math.abs(girl.x - girl.toX) < 3) {
                    girl.x = girl.toX;
            } else {
                    girl.x -= 3;
            }
    }
    
    else if (girl.x < girl.toX) {
            girl.dir = DIR_RIGHT;
            if (Math.abs(girl.x- girl.toX) < 3) {
                    girl.x = girl.toX;
            } else {
                    girl.x += 3;
            }
    }
    
  4. Specify how the girl should be animated by adding Listing 3-41 directly under the last code you entered, still inside the girl’s event listener. This code sample states that if the girl is not moving, her age should be made to equal 1. Every frame, the girl’s age will increase; however, if she is standing still, her age will be reset to 1. This is used in the frame assignment on the next line to keep the girl from being animated if she is not moving. In the next line, the frame is assigned as a number from the array of values we specified earlier. The code means that the frame of the girl should cycle through the four frames of a given direction the girl is traveling in or facing.

    清单 3-41。 给女孩做动画

    if (girl.x == girl.toX && girl.y == girl.toY) girl.age = 1;
    girl.frame = girl.anim[girl.dir * 4 + (girl.age % 4)];
    
  5. Create the TOUCH_START and TOUCH_MOVE event listeners to capture clicks from the player and save those values to the girl’s toX and toY properties by modifying the last part of your code to match Listing 3-42. The two event listeners should be added inside the game.onload function, but outside the girl’s ENTER_FRAME event listener. Here, the TOUCH events are being passed into the functions as an argument (function(e)) and include the X and Y coordinates of where the touch event happens. Assigning those coordinates to the girl’s toX and toY properties (with -16 to center the girl) move the girl to the location being touched by the player.

    清单 3-42。 包括事件监听器进行触摸

                            ...if (girl.x == girl.toX && girl.y == girl.toY) girl.age = 1;
                            girl.frame = girl.anim[girl.dir * 4 + (girl.age % 4)];
    
                    });
    
                    bg.addEventListener(Event.TOUCH_START, function(e){
                            girl.toX = e.x - 16;
                            girl.toY = e.y - 16;
                    });
    
                    bg.addEventListener(Event.TOUCH_MOVE, function(e){
                            girl.toX = e.x - 16;
                            girl.toY = e.y - 16;
                    });
            };
            game.start();
    };
    
  6. 单击运行。女孩现在跟着屏幕上的鼠标点击,向他们走去。

本节介绍了一些用触摸事件控制精灵的复杂技巧。为你的精灵们启用这个能力将会为你的玩家们创造更流畅更有趣的游戏。

使用数字键盘进行交互

到目前为止,我们看到的玩家和游戏之间的唯一交互是触摸事件。然而,这并不是玩家与游戏互动的唯一方式。为了支持触摸事件之外的交互,enchant.js 附带了一个插件,用于将数字和模拟方向键(d-pads)放入游戏中。数字 d-pad(图 3-6 )只包含四个方向按钮(上、下、左、右),而模拟 pad 是由用户拇指操纵的控制杆。Playstation 2 和 3 控制器上的控制杆是现实生活中模拟垫的好例子。模拟板传递操纵杆相对于其中心的当前位置的 X 和 Y 值。本书不涉及模拟焊盘;相反,我们专注于数字键盘。

9781430247432_Fig03-06.jpg

图 3-6 。数字键盘

在本节的代码示例中,我们创建了一个数字 d-pad,为您提供了一个具有熊角色和地板的侧滚游戏的基础。按下 d-pad,使轴承向指定方向移动。

创建数字键盘

执行以下操作创建数字键盘:

  1. Fork the code at http://code.9leap.net/codes/show/27476 to create the basis of the game. If you are not using code.9leap.net, copy the code from Listing 3-43 into a new code file.

    清单 3-43。D-Pad 游戏基础

    var STATUS_WAIT = 0;
    var STATUS_WALK = 1;
    var STATUS_JUMP = 2;
    
    enchant();
    window.onload = function() {
        //create game object
        var game = new Core(320, 320);
        game.fps = 16;
    
        //load images
        game.preload('http://enchantjs.com/assets/img/chara1.gif',
            'http://enchantjs.com/assets/img/map0.gif');
    
        //called when the loading is complete
        game.onload = function() {
            //create the background
            var bg = new Sprite(320, 320);
            bg.backgroundColor = "rgb(0, 200, 255)";
            var maptip = game.assets['http://enchantjs.com/assets/img/map0.gif'];
            var image = new Surface(320, 320);
            for (var i = 0; i < 320; i += 16) {
                image.draw(maptip, 3 * 16, 0, 16, 16, i, 320 - 16, 16, 16);
            }
            bg.image = image;
            game.rootScene.addChild(bg);
    
            //The d-pad should be created below this line
    
            //create bear
            var bear = new Sprite(32, 32);
            bear.image  = game.assets['http://enchantjs.com/assets/img/chara1.gif'];
            bear.x      = 160 - 16;
            bear.y      = 320 - 16 - 32;
            bear.status = STATUS_WAIT;
            bear.anim   = [10, 11, 10, 12];
            bear.frame  = 10;
            game.rootScene.addChild(bear);
    
            //frame loop for the bear
            bear.addEventListener(Event.ENTER_FRAME, function() {
    
                //frame setting
                if (bear.status == STATUS_WAIT) {
                    bear.frame = bear.anim[0];
                } else if (bear.status == STATUS_WALK) {
                    bear.frame = bear.anim[bear.age % 4];
                } else if (bear.status == STATUS_JUMP) {
                    bear.frame = bear.anim[1];
                }
            });
        };
    
        //start game
        game.start();
    };
    

    image 注意D-pad 需要加载ui.enchant.js 插件。这显示在 code.9leap 示例中的 index.html 文件中,包括在调用 enchant.js 游戏脚本的 index.html 文件的文件头中添加<script src='/static/enchant.js-latest/plugins/ui.enchant.js'></script>或类似的指针。

  2. Create the D-pad by typing in the code in Listing 3-44 under the comment line that says //The d-pad should be created below this line. This creates the D-pad at (0,220).

    清单 3-44。 创建一个数字键盘

    var pad = new Pad();
    pad.x   = 0;
    pad.y   = 220;
    game.rootScene.addChild(pad);
    
  3. 单击运行。D-pad 出现在屏幕的左下角。

用 D-Pad 处理移动

您已经创建了 D-pad,但是您仍然需要对它进行编程来移动熊。请执行以下操作:

  1. Tie the D-pad in with the movement of the bear by adding Listing 3-45 to the inside of the Bear’s ENTER_FRAME event listener (directly under bear.addEventListener(Event.ENTER_FRAME, function() { }. This ties in the input from the d-pad with commands to move the bear.

    清单 3-45。 用 D-Pad 加工运动

    //up
    if (bear.status != STATUS_JUMP) {
            bear.status = STATUS_WAIT;
            if (game.input.up)  {
                    bear.status = STATUS_JUMP;
                    bear.age = 0;
            }
    }
    
    //left
    if (game.input.left)  {
            bear.x -= 3;
            bear.scaleX = -1;
            if (bear.status != STATUS_JUMP) bear.status = STATUS_WALK;
    }
    
    //right
    else if (game.input.right) {
            bear.x += 3;
            bear.scaleX =  1;
            if (bear.status != STATUS_JUMP) bear.status = STATUS_WALK;
    }
    
    //when jumping
    if (bear.status == STATUS_JUMP) {
            if (bear.age < 8) {
                    bear.y -= 8;
            } else if (bear.age < 16) {
                    bear.y += 8;
            } else {
                    bear.status = STATUS_WAIT;
            }
    }
    
  2. 单击运行。D-Pad 现在控制熊的移动。

与触摸事件一样,d-pad 可用于移动字符,如果您不想通过触摸事件来定位,它是一个有用的元素。如果你想给你的游戏一种复古的感觉,它们特别有用。

结论

关于 enchant.js 基础的章节到此结束。我们已经使用代码示例创建了标签、精灵、表面、触摸事件和虚拟面板。你现在已经为在 enchant.js 中创建自己的基本游戏打下了坚实的基础。当你创建自己的游戏时,你可能不会使用我们检查过的所有元素,但很有可能你会至少使用其中的一些,因为大多数 enchant.js 游戏都使用标签和精灵。

在下一章中,我们将介绍 enchant.js 的一些更高级的功能,例如在场景之间导航,添加开始屏幕和游戏结束屏幕,创建高级地图,以及建立声音播放。这些工具允许你把你的游戏带到一个更高的复杂和创造性的水平。

四、enchant.js 的高级功能

enchant.js 的基础允许简单的游戏创建。更高级的功能,如在单个游戏中的场景之间导航,需要使用 enchant.js 更复杂的功能。在本章中,我们将演示如何创建场景过渡,以包括多个级别或环境,在屏幕上开始和游戏以美化游戏外观,以及地图和声音以增加交互性。

汇总列表

  1. 场景之间的转换
  2. 创建开始、游戏结束和分数共享屏幕
  3. 使用碰撞检测
  4. 创建交互式地图

场景之间的过渡

虽然单个场景对于简单的游戏来说可能已经足够了,但是任何类型的冒险或基于对话的游戏都需要多个场景来保持适合类型的感觉。

本节的示例代码展示了如何创建一个简单的程序,该程序使用场景转换使玩家能够在三个不同的场景之间切换。图 4-1 说明了玩家如何通过点击橙色导航文本在三个场景之间导航。

9781430247432_Fig04-01.jpg

图 4-1 。场景转换

解剖一个场景

场景可能是一个挑战,因为它们有自己的结构,不同于 enchant.js 中的其他实体。因此,我们在本节中为您提供一些关于场景如何工作的基本信息,然后是示例代码,以显示场景如何在实际的 enchant.js 游戏中使用。

场景创建

场景是一个屏幕单元,可以在其中添加显示对象,如精灵、标签、贴图和组。一个游戏可以有多个场景,通过切换场景,可以完全改变画面的内容。常见的场景类型包括标题屏幕、播放屏幕和游戏结束屏幕。为了创建场景,使用了Scene对象构造函数(var scene1 = new Scene();)。

场景堆栈

创建场景后,可以将背景、带文本的标签和精灵添加到场景中。一旦完成,用addChild()函数将场景添加到场景堆栈中。

场景以堆栈结构组织。正如术语“堆叠”所暗示的,多个场景堆叠在彼此之上。最上面的场景是可见场景。你用push()方法在栈顶添加一个场景,用pop()方法移除最顶层的场景,如图图 4-2 所示。

9781430247432_Fig04-02.jpg

图 4-2 。烟囱建造

场景属性和方法

在一个游戏内部,可以用currentScene引用当前场景,用rootScene引用根场景。这两个名字使得跟踪游戏场景堆栈中发生的事情变得容易。

表 4-1 显示了Scene对象的方法。

表 4-1。场景对象方法

代码(参数) 描述
pushScene(Scene) 切换到新场景,将其添加到场景堆栈的顶部。
popScene() 通过将当前场景从屏幕堆栈中移除来结束当前场景。底层场景(如果有)将成为新的当前场景。
removeScene(scene) 从堆栈中移除指定的场景。
replaceScene(scene) 用另一个场景替换当前场景。

进行场景转换

要创建允许玩家在三个场景之间切换的代码,请执行以下操作:

  1. Fork the code from http://code.9leap.net/codes/show/27650. If you cannot access the code, copy it from Listing 4-1.

    清单 4-1。 场景设置

    enchant();
    window.onload = function() {
        //Core object creation
        var game = new Core(320, 320);
        game.fps = 16;
    
        //Image loading
        game.preload('http://enchantjs.com/assets/img/bg/bg01.jpg',
            'http://enchantjs.com/assets/img/bg/bg02.jpg',
            'http://enchantjs.com/assets/img/bg/bg03.jpg');
    
        //Called when pre-loading is complete
        game.onload = function() {
    
            //Background creation
            var bg = makeBackground(game.assets        ['http://enchantjs.com/assets/img/bg/bg01.jpg'])
            game.rootScene.addChild(bg);
    
            //Message creation
            game.rootScene.addChild(makeMessage("This is the root scene."));
    
            //Choice button creation
            var select=makeSelect("[Move to Scene 1]", 320 - 32 * 2);
            select.addEventListener(Event.TOUCH_START, function(e) {
                game.pushScene(game.makeScene1());
            });
            game.rootScene.addChild(select);
        };
    
        //Scene 1 creation
        game.makeScene1 = function() {
            var scene = new Scene();
    
            //Background creation
            var bg = makeBackground(game.assets        ['http://enchantjs.com/assets/img/bg/bg02.jpg'])
            scene.addChild(bg);
    
            //Message creation
            scene.addChild(makeMessage("This is Scene 1."));
    
            //Choice button creation
            var select = makeSelect("[Move to Scene 2]", 320 - 32 * 2);
            select.addEventListener(Event.TOUCH_START, function(e) {
                game.pushScene(game.makeScene2());
            });
            scene.addChild(select);
            scene.addChild(makeReturn(1));
            return scene;
        };
    
        //Scene 2 creation
        game.makeScene2 = function() {
            var scene = new Scene();
    
            //Background creation
            var bg = makeBackground(game.assets        ['http://enchantjs.com/assets/img/bg/bg03.jpg'])
            scene.addChild(bg);
    
            //Label creation
            scene.addChild(makeMessage("This is Scene 2."));
            scene.addChild(makeReturn(0));
            return scene;
        };
    
        //Start game
        game.start();
    };
    
    //Background creation
    function makeBackground(image) {
        var bg = new Sprite(320, 320);
        bg.image = image;
        return bg;
    }
    //Message creation
    function makeMessage(text) {
        var label = new Label(text);
        label.font  = "16px monospace";
        label.color = "rgb(255,255,255)";
        label.backgroundColor = "rgba(0,0,0,0.6)";
        label.y     = 320 - 32 * 3;
        label.width = 320;
        label.height = 32 * 3;
        return label;
    }
    
    //Choice button creation
    function makeSelect(text, y) {
        var label = new Label(text);
        label.font  = "16px monospace";
        label.color = "rgb(255,200,0)";
        label.y     = y;
        label.width = 320;
        return label;
    }
    
    //Return button creation
    function makeReturn(lineNumber) {
        var game = enchant.Game.instance;
        var returnLabel = makeSelect("[Return]", 320 - 32 * (2-lineNumber));
        returnLabel.addEventListener(Event.TOUCH_START, function(e) {
            game.popScene();
        });
        return returnLabel;
    }
    

在这个程序中,当玩家点击移动到场景 1 按钮时,游戏显示场景 1。方法 pushScene()应该有道理,但是game.pushScene(game.makeScene1());呢?

在前面的示例代码中,我们为我们的Core对象类显式定义了makeScene1()makeScene2()方法。当调用这些方法时,它们会创建并返回一个场景。

该函数中所发生的只是创建一个Scene对象,然后创建各种元素并添加到场景中。首先,代码创建一个背景并将其添加到场景中。接下来,代码创建一个标签并添加到场景中,特别是使用我们已经创建的makeMessage()函数。然后,代码用addChild().将它添加到场景中。之后,代码用同样的方法创建另一个标签,这允许导航到场景 2。

该方法的最后一行返回场景。请记住,向场景添加元素实际上并不会使场景活跃,这就是为什么这里有这个返回。这样做允许我们将它放在pushScene()函数中,该函数将调用makeScene1(),然后使用它创建的场景,将它推到堆栈的顶部。

当玩家在场景 1 或场景 2 中点击返回按钮时,场景堆栈顶部的场景将通过popScene()移除。

场景允许多个环境存在于同一个游戏中。您可以在基于对话框的游戏中使用它们来设置关卡或图像。然而,你不一定要把它们包含在你的游戏中。enchant.js 中很多流行的游戏都是在没有场景具体使用的情况下创建的。

创建一个有屏幕、时间限制和分数 的游戏

开始和游戏结束屏幕是向玩家传达游戏开始和结束的有用工具。幸运的是,enchant.js 附带了一个官方插件 nineleap.enchant.js,用于使这些屏幕易于实现。

在本节的示例代码中,我们创建了一个以开始屏幕开始的游戏。当玩家点击开始屏幕时,会切换到根场景。在根场景中,玩家可以使用 d-pad 来控制熊的左右移动,以接住从屏幕顶部落下的苹果。收集苹果会增加玩家的分数。达到 10 秒钟的时间限制后,游戏停止,并显示游戏结束屏幕。图 4-3 显示了屏幕转换的顺序。

9781430247432_Fig04-03.jpg

图 4-3 。nineleap 示例项目代码的屏幕序列

如果你上传你的游戏到 9leap.net,游戏结束后会出现另一个显示玩家分数的屏幕。

设置一个示例游戏

我们首先需要一个游戏来实现开始和结束屏幕。要创建此游戏,请执行以下操作:

  1. Fork the code from http://code.9leap.net/codes/show/23342 to your own project on code.9leap.net. If you are not using code.9leap.net, copy and paste the code from Listing 4-2. This code is for a simple apple-catching game.

    清单 4-2。 抓苹果游戏

    enchant();
    window.onload = function() {
        //Game object creation
        var game = new Core(320, 320);
        game.fps = 16;
        game.score = 0;
        var label;
        var bear;
    
        //Image loading
        game.preload('chara1.gif',
            'http://enchantjs.com/assets/img/map0.gif',
            'http://enchantjs.com/assets/img/icon0.gif');
    
        //Called when the loading is complete
        game.onload = function() {
            //Background creation
            var bg = new Sprite(320, 320);
            bg.backgroundColor = "rgb(0, 200, 255)";
            var maptip = game.assets['http://enchantjs.com/assets/img/map0.gif'];
            var image = new Surface(320, 320);
            for (var i = 0; i < 320; i += 16) {
                image.draw(maptip, 7 * 16, 0, 16, 16, i, 320 - 16, 16, 16);
            }
            bg.image = image;
            game.rootScene.addChild(bg);
    
            //Virtual pad creation
            var pad = new Pad();
            pad.x   = 0;
            pad.y   = 220;
            game.rootScene.addChild(pad);
    
            //Label creation
            label = new Label("");
            game.rootScene.addChild(label);
    
            //Bear creation
            bear = new Sprite(32, 32);
            bear.image  = game.assets['chara1.gif'];
            bear.x      = 160 - 16;
            bear.y      = 320 - 16 - 32;
            bear.anim   = [10, 11, 10, 12];
            bear.frame  = bear.anim[0];
            game.rootScene.addChild(bear);
    
            //Periodic processing of the bear sprite
            bear.addEventListener(Event.ENTER_FRAME, function() {
                //Left
                if (game.input.left)  {
                    bear.x -= 3;
                    bear.scaleX = -1;
                }
                //Right
                else if (game.input.right) {
                    bear.x += 3;
                    bear.scaleX =  1;
                }
    
                //Frame settings
                if (!game.input.left && !game.input.right) {
                    bear.frame = bear.anim[0];
                } else {
                    bear.frame = bear.anim[bear.age %  bear.anim.length];
                }
            });
        };
    
        //Adds an apple
        game.addApple = function(x, speed) {
            //Create apple
            var apple = new Sprite(16, 16);
            apple.image = game.assets['http://enchantjs.com/assets/img/icon0.gif'];
            apple.x = x;
            apple.y = -16;
            apple.frame = 15;
            apple.speed = speed;
            game.rootScene.addChild(apple);
    
            //Periodic processing of the sprite
            apple.addEventListener(Event.ENTER_FRAME, function() {
                apple.y += apple.speed;
                //Collision with the bear
    
                //Collision with the ground
                else if (apple.y > 320 - 32) {
                    game.rootScene.removeChild(apple);
                }
            });
        };
    
        //Periodic processing of the scene
        game.framesLeft = 10*game.fps; // 10 seconds
        game.rootScene.addEventListener(Event.ENTER_FRAME, function() {
            game.framesLeft--;
            if (game.framesLeft > 0) {
                //Apple creation
                if ((game.frame % 10) === 0) {
                    var x     = rand(300);
                    var speed = 3 + rand(6);
                    game.addApple(x,speed);
                }
                label.text = "Time left:" + Math.floor(game.framesLeft / game.fps)  +
                    "<BR />Score:" + game.score;
            } else {
                //Display Game Over
                game.end(game.score, "Your score is " + game.score);
            }
        });
    
        //Start game
        game.start();
    };
    
    //Generates a random number
    function rand(num){
        return Math.floor(Math.random() * num);
    }
    

如果你运行这段代码,你可以用 d-pad 在屏幕上移动一只熊,然后看到苹果从屏幕顶部落下。然而,开始和游戏结束屏幕还没有出现。在下一节中,我们将向您展示如何添加这些屏幕。

添加所需的插件

随着 nineleap.enchant.js 插件的添加,开始和游戏结束屏幕会自动出现。执行以下操作将其添加到项目中:

  1. 点击屏幕顶部的下拉菜单,选择index.html

  2. Type in the code in Listing 4-3 under <script src='/static/enchant.js-latest/enchant.js'></script>.

    清单 4-3。 添加 nine leap . ench . js 插件

    <script src='/static/enchant.js-latest/plugins/nineleap.enchant.js'></script>
    
  3. 单击运行。由于添加了插件,开始和游戏结束屏幕会自动出现。

创建分数共享屏幕

只有当你将游戏上传到 9leap.net 时,分数共享界面才会出现。这个屏幕允许玩家看到他们的最终分数,并让他们能够通过 9leap.net 在 Twitter 上分享分数。要设置游戏使用分数共享屏幕,请执行以下操作:

  1. Near the end of the code, change game.end(); to what is shown in Listing 4-4.

    清单 4-4。 使用计分屏

    game.end(game.score, "Your score is " + game.score);
    

接下来,把你的游戏上传到 9leap.net。当你玩游戏时,游戏结束后会出现一个屏幕,显示你的分数和你指定的信息(如“你的分数是…”)。

正如你所看到的,开始和游戏结束屏幕很容易在你的游戏中实现,只需要一个额外的插件。添加这个插件可以润色你的游戏,让你的玩家更容易理解确切的起点和终点。

使用碰撞检测

正如我们已经看到的,我们的游戏包括让一个熊角色收集从屏幕顶部落下的苹果。就苹果而言,我们知道如何创造它们,如何让它们随着时间的推移而掉落,甚至如何让它们消失。然而,我们需要一些方法来判断我们的熊角色何时接触到苹果。为此,我们使用特定的方法,这些方法是所有实体对象的一部分。

碰撞检测的方法

表 4-2 显示了用于碰撞检测的两种方法。这些方法是Entity对象的一部分,因此可以在场景中的任何地方调用。

表 4-2。碰撞检测方法

方法(参数) 描述
intersect(otherEntity) 根据实体(调用intersect()的实体和另一个实体)的边界矩形是否相交来执行碰撞检测。另一个实体必须具有属性xywidthheight
within(otherEntity, distance) 根据两个实体中心点之间的距离执行碰撞检测。

用内部方法检测碰撞

第一种碰撞检测方法是within()。在抓苹果游戏中执行以下操作:

  1. Beneath the line //Collision with the bear, type in the code in Listing 4-4.

    清单 4-4。 利用内()

    if (bear.within(apple, 16)) {
        game.score+=30;
        game.rootScene.removeChild(apple);
    }
    

这将计算每一帧苹果的中心点和熊的中心点,然后计算两者之间的距离。如果距离小于或等于 16 个像素,游戏分数增加 30,苹果从rootScene中移除。

用相交方法检测碰撞

存在另一种检测物体之间碰撞的方法。请执行以下操作,了解如何使用该方法:

  1. 将清单 4-4 中的bear.within(苹果,16)替换为bear.intersect(苹果)。

这导致程序查看熊和苹果的总面积。如果这些区域相交,分数增加 30,苹果从rootScene中移除。

碰撞检测是游戏的重要组成部分。抓苹果、击落船只或任何涉及一个精灵与另一个精灵接触的事情都需要碰撞检测。如果您在创建本节中的代码时遇到任何问题,您可以在http://code.9leap.net/codes/show/27891找到一个完整的工作副本。

创建交互式地图

我们在第三章的中看到了一个地图的基本示例,它在屏幕上复制了一个单幅图块,但需要进一步设置才能在 enchant.js 中创建交互式地图。然而,原理是相同的,仍然使用draw()方法用地图图块平铺给定的表面。

在本节的示例代码中,我们使用地图创建了一个简单的迷宫程序。我们的主角将只能穿过浅棕色的路面砖,而不能走过绿色的草地砖。一旦主角到达宝箱,游戏就结束了。图 4-4 和 4-5 说明了该程序。

9781430247432_Fig04-04.jpg

图 4-4 。地图示例项目的开始屏幕

9781430247432_Fig04-05.jpg

图 4-5 。地图示例项目的结束画面

创建地图对象

创建地图的第一步是创建一个Map对象。执行以下操作来设置游戏并创建一个Map对象:

  • 1.从http://code.9leap.net/codes/show/27204派生出空白模板,以获得一个项目来输入您的代码。

  • 2.  Type in the code in Listing 4-5 to set up the basics of the game.

    清单 4-5。 侧滚器基础知识

    var DIR_LEFT  = 0;
    var DIR_RIGHT = 1;
    var DIR_UP    = 2;
    var DIR_DOWN  = 3;
    
    enchant();
    window.onload = function() {
        var game = new Core(320, 320);
        game.fps = 16;
        game.preload(
            'http://enchantjs.com/assets/img/map0.gif',
            'http://enchantjs.com/assets/img/chara0.gif');
    
        game.onload = function() {
            var player = new Sprite(32, 32);
            player.image = game.assets['http://enchantjs.com/assets/img/chara0.gif'];
            player.x = 2 * 16;
            player.y = 16;
            player.dir   = DIR_DOWN;
            player.anim  = [
                 9, 10, 11, 10, //Left
                18, 19, 20, 19, //Right
                27, 28, 29, 28, //Up
                 0,  1,  2,  1];//Down
    
                //Frame setting
                if (!game.input.up && !game.input.down &&
                    !game.input.left && !game.input.right) player.age = 1;//Standing Still
                player.frame = player.anim[player.dir * 4 + (player.age % 4)];
    
            });
    
            var pad = new Pad();
            pad.y = 220;
            game.rootScene.addChild(pad);
    
        };
        game.start();
    };
    
  • 3.  Under game.onload = function() {, type in the code in Listing 4-6 to create the Map object and assign the tile set image to it.

    清单 4-6。 创建地图对象

    var map = new Map(16, 16);
    map.image = game.assets['http://enchantjs.com/assets/img/map0.gif'];
    

填充图块并设置碰撞和

现在您已经创建了地图,您需要用切片填充它。在前面的例子中,我们使用了一个循环来用相同的绿草瓦片填充地图中的所有瓦片。在此地图中,我们以特定的排列方式使用不同的图块,因此必须手动输入。执行以下操作来复制和粘贴图块数据:

  • 4.  Copy the code from http://code.9leap.net/codes/show/27905 and insert it into the line after Listing 4-6. If you are not using 9leap, copy the code from Listing 4-7.

    清单 4-7。 载入磁贴数据

     map.loadData([
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,0],
    [0,2,2,2,2,0,0,2,2,2,2,2,2,2,2,0,0,0,2,2,2,2,0],
    [0,2,2,2,2,0,0,2,2,2,2,2,2,2,2,0,0,0,2,2,2,2,0],
    [0,0,2,2,0,0,0,2,2,0,0,0,0,2,2,0,0,0,0,2,2,0,0],
    [0,0,2,2,0,0,0,2,2,0,0,0,0,2,2,0,0,0,0,2,2,0,0],
    [0,0,2,2,0,0,0,2,2,0,0,0,0,0,0,0,0,2,2,2,2,0,0],
    [0,0,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,2,2,2,2,0,0],
    [0,0,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,2,2,0,0,0,0],
    [0,0,0,0,0,2,2,0,0,0,0,0,2,2,2,2,2,2,2,0,0,0,0],
    [0,0,0,0,0,2,2,0,0,0,0,0,2,2,2,2,2,2,2,0,0,0,0],
    [0,0,0,2,2,2,2,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0],
    [0,0,2,2,2,2,2,0,0,0,0,0,2,2,2,2,2,2,2,2,2,0,0],
    [0,0,2,2,2,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,0,0],
    [0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,0,0],
    [0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,0,0],
    [0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,0],
    [0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,2,2,2,2,0],
    [0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,2,2,2,2,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
    ],[
    [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
    [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
    [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,18,-1,-1,-1],
    [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
    [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
    [-1,-1,-1,-1,-1,23,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
    [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
    [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,23,-1,-1,-1,-1,-1,-1,23],
    [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,23],
    [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
    [-1,-1,-1,-1,-1,-1,-1,-1, 1, 1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
    [-1,-1,-1,-1,-1,-1,-1,-1, 1, 1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
    [-1,-1,-1,-1,-1,-1,-1,-1, 1, 1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
    [-1,-1,-1,18,-1,-1,-1,-1,-1, 1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
    [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
    [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
    [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
    [-1,23,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,25,-1,-1],
    [-1,23,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
    [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1]
    ]);
    

这段代码展示了如何使用loadData()函数来填充地图。它由两部分数组组成,由一个右括号、一个逗号和一个左括号“],[”分隔。因为这是两个独立的组,所以让我们从第一组开始。

[0,0,0,0 . . .]开始的第一个数组组指定在第 1 个第 1 个位置(绿草瓷砖)开始布局瓷砖,作为瓷砖地图的第一行。这些图块来自图块集图像,该图像先前被分配给map.image。使用的图块大小(16x16)是在第一次用Map对象构造器(var map = new Map(16, 16);)创建地图时指定的。

第一行图块名称后的右括号和逗号(…2,2,2,0),)指定开始新的一行图块。这个平铺过程一直持续到我们到达清单 4-10 中的[大约一半的位置],

然后我们再次开始这个过程。但是,这一次,指定的图块将覆盖当前位于指定要平铺的位置的任何图块。值 1 表示不在指定位置放置任何单幅图块。这种分层技术允许将诸如花、树和宝箱之类的物体放在地图上,放在绿草地或褐色的路面砖上。

image 注意手动输入定义地图平铺顺序的二维数组或用于碰撞检测的二维数组是相当费力的。为了自动创建这些数据,enchant.js 支持地图编辑器,我们建议您在为游戏创建地图时使用该编辑器。你可以在http://enchantjs.com/resource/the-map-editor/找到地图编辑器和使用说明。

下一步是在地图上指定碰撞点。在地图上,既会有绿色的草地瓦片,也会有棕色的道路瓦片。主角应该只能在棕色的路面砖上行走,所以我们设置了碰撞来实现这一点。这是用另一个数组设置的,用0表示可以通行,1表示不可以通行,指定了角色是否可以在瓷砖上行走。

  • 5.  Create the collision data for the map by copying it from http://code.9leap.net/codes/show/27909 and pasting it beneath the loadData array you just entered. You can also copy it from Listing 4-8 if necessary.

    清单 4-8。 设置碰撞数据

    map.collisionData = [
        [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
        [1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1],
        [1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,1],
        [1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,1],
        [1,1,0,0,1,1,1,0,0,1,1,1,1,0,0,1,1,1,1,0,0,1,1],
        [1,1,0,0,1,1,1,0,0,1,1,1,1,0,0,1,1,1,1,0,0,1,1],
        [1,1,0,0,1,1,1,0,0,1,1,1,1,1,1,1,1,0,0,0,0,1,1],
        [1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,1,1],
        [1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,1,1,1,1],
        [1,1,1,1,1,0,0,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1],
        [1,1,1,1,1,0,0,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1],
        [1,1,1,0,0,0,0,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1],
        [1,1,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1],
        [1,1,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1],
        [1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1],
        [1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1],
        [1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1],
        [1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1],
        [1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1],
        [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
    ];
    

使用运动碰撞检测

现在我们游戏中的地图存在了,但是我们需要一种方法来根据数据移动我们的角色。enchant.js 中的地图支持查找地图上特定位置是否可以行走的方法。这用于确定角色是否可以在瓷砖上行走。执行以下操作来实现它:

  1. Under the line player.anim  = [...];//Down, type in the code in Listing 4-9.

    清单 4-9。 人物移动与地图碰撞检测

    player.addEventListener(Event.ENTER_FRAME, function() {
        //Move up
        if (game.input.up) {
            player.dir = DIR_UP;
            player.y -= 4;
            if (map.hitTest(player.x + 16, player.y + 32)) player.y += 4;
        }
        //Move down
        else if (game.input.down) {
            player.dir = DIR_DOWN;
            player.y += 4;
            if (map.hitTest(player.x + 16, player.y + 32)) player.y -= 4;
        }
        //Move left
        else if (game.input.left) {
            player.dir = DIR_LEFT;
            player.x -= 4;
            if (map.hitTest(player.x + 16, player.y + 32)) player.x += 4;
        }
        //Move right
        else if (game.input.right) {
            player.dir = DIR_RIGHT;
            player.x += 4;
            if (map.hitTest(player.x + 16, player.y + 32)) player.x -= 4;
        }
    
        //Frame setting
        if (!game.input.up && !game.input.down &&
            !game.input.left && !game.input.right) player.age = 1;//Standing Still
        player.frame = player.anim[player.dir * 4 + (player.age % 4)];
    
    });
    

hitTest()方法使用我们分配给collisionData的数据阵列来查找地图上给定 XY 坐标处是否存在障碍物。虽然这种方法可以告诉我们在地图上的给定位置是否存在障碍物,但是如果障碍物确实存在,我们必须手动指定应该做什么。

这些代码都在一个定期处理的事件侦听器中,所以它随着每个新帧的出现而执行。玩家角色在 d-pad 指定的方向上移动四个像素。

但是,如果角色的水平中点与包含障碍物的牌相邻,则角色不能在该障碍物的方向上继续移动。你有没有注意到清单 4-12 中嵌套的if语句是如何从移动的相反方向增加角色的位置的?通过使用相反的计算,角色位置的值不会改变,角色保持静止(如果map.hitTest()返回true)。

滚动地图

我们为我们的角色使用的地图比游戏屏幕大,这意味着地图的一部分在屏幕外被遮住了。当角色在屏幕上移动时,地图应该在某个点滚动,这样角色就可以探索整个地图。

要做到这一点,我们需要做的第一件事是将角色和地图合并成一个单元,这样两者可以同时滚动。为此,请执行以下操作:

  1. Above var pad = new Pad();, combine the map and the player into a single Group by typing in the code in Listing 4-10.

    清单 4-10。 将地图和玩家组合成一组

    var stage = new Group();
    stage.addChild(map);
    stage.addChild(player);
    game.rootScene.addChild(stage);
    
  2. Set the map to scroll with the player by typing in the code in Listing 4-11 after game.rootScene.addChild(pad);.

    清单 4-11。 滚动地图

    game.rootScene.addEventListener(Event.ENTER_FRAME, function(e) {
        //Set stage XY coordinates
        var x = Math.min((game.width  - 16) / 2 - player.x, 0);
        var y = Math.min((game.height - 16) / 2 - player.y, 0);
        x = Math.max(game.width,  x + map.width)  - map.width;
        y = Math.max(game.height, y + map.height) - map.height;
        stage.x = x;
        stage.y = y;
    });
    

如果我们反过来看,这可能是最容易理解的。

首先,我们知道我们正在重新定位上面创建的stage组,通过给舞台的 x 和 y 位置分配一个xy值。记住stage包含了地图和角色,并且会同时移动它们。

接下来,我们指定“从两个值中的一个减去地图的宽度,并将其赋给 x”(x = Math.max(game.width,  x + map.width)  - map.width;)。这里要注意的主要概念是,如果游戏的宽度大于某个其他值(x + map.width),x 会被赋一个值,等于游戏的宽度减去地图的宽度。如果队伍移动了这个数字(一个负数),地图的右边将与游戏屏幕的右边对齐。

最后,直接在事件监听器声明下,我们指定“要么使 x 的值为零,要么等于游戏宽度的一半减去玩家(在地图上)的当前 x 位置。”如果角色在地图上,但没有向右移动至少相当于游戏屏幕一半的量,则分配零分。

这有点复杂,但最终的结果是,地图随着角色的移动以一种总是充满游戏屏幕的方式滚动。在http://9leap.net/games/3004可以看到一个更简单的游戏《Heart_Runner》中滚动背景的例子,玩家快速点击屏幕,使角色跑过一片滚动到角色身后左侧的森林。

对目标使用碰撞检测

在游戏的这一点上,地图和角色应该会正确的出现。这个角色可以用 d-pad 控制,地图会滚动。然而,如果角色走过宝箱,什么也不会发生。要结束游戏,我们必须以不同的方式使用碰撞检测。

我们已经看到了如何在两个实体(intersect()within())之间执行碰撞检测,并且我们已经看到了如何执行基本的碰撞检测来查看角色是否可以在给定的图块上移动。但是,因为宝箱一旦平铺到地图上,就没有自己的实体容器,所以我们不能使用像intersectwithin()这样的实体方法。

无论从哪方面来看,宝箱都只是地图上的一组坐标。要确定角色是否与宝箱接触,我们需要确定角色所在位置与地图上宝箱位置之间的距离。图 4-6 显示了这个等式。

9781430247432_Fig04-06.jpg

图 4-6 。距离公式

请执行以下操作,了解如何创建和使用此公式:

  1. Add the function to calculate the distance between two points on the map by typing in the code in Listing 4-12 at the very end of your existing code, outside the game.onload function.

    清单 4-12。 创建函数计算长度

    function calcLen(x0, y0, x1, y1) {
        return Math.sqrt((x0 - x1) * (x0 - x1) + (y0 - y1) * (y0 - y1));
    }
    

    函数Math.sqrt()计算作为参数传递的任何值的平方根,它是Math对象的函数,可以在 JavaScript 中随时调用。

  2. Check if the character is touching the treasure chest panel by typing in the code in Listing 4-13 after player.frame = player.anim[player.dir * 4 + (player.age % 4)];.

    清单 4-13。 检查与宝箱的碰撞

    if (calcLen(player.x + 16, player.y + 16, 20 * 16 + 8, 17 * 16 + 8) <= 16) {
        game.end(0, "Goal");
    }
    

    在这里,我们正在测量玩家精灵的 XY 坐标与宝箱之间的距离。“+16”和“+8”是为了让程序根据这两个对象的中心而不是左上角来测量它们之间的距离。

    用于藏宝箱的 XY 位置(x1 和 y1)的乘法使用单个区块的大小(16 像素)乘以藏宝箱区块之前的区块数,来找到 x 和 y 位置的值。

  3. 单击运行。当角色接触到宝箱时,游戏结束。

在本节中,我们创建了一个游戏,使角色能够在地图上行走,但只能在特定的瓷砖上行走。我们还让地图随着角色的移动而滚动。最后,我们让游戏在角色到达地图上的宝箱后结束。所有这些功能和动作都涉及到交互式地图。互动地图非常适合二维游戏,尤其是二维角色扮演游戏。

如果您在设置本节中的地图时遇到任何问题,您可以在http://code.9leap.net/codes/show/23344找到完整的工作代码示例。

实现声音

本章的最后一个高级功能是声音。声音可以在 enchant.js 中实现,用于游戏中的背景音乐和事件,例如一艘船射击,一个特殊的能力被激活,等等,以使你的游戏身临其境,具有交互性。

本节的练习只是在屏幕上放几个香蕉,以及一个头骨。如果玩家点击一个香蕉,香蕉就会消失,并播放声音效果。如果玩家点击头骨,游戏将结束。游戏的目标是收集所有的香蕉。图 4-7 显示了这个简单游戏的开始状态。图 4-8 显示游戏画面。

9781430247432_Fig04-07.jpg

图 4-7 。香蕉游戏主屏幕

9781430247432_Fig04-08.jpg

图 4-8 。香蕉游戏结束画面

下载声音

由于声音文件很大,默认情况下它们不包含在 enchant.js 包中,但是可以从同一个页面下载。执行以下操作来下载它们:

  1. 转到http://enchantjs.com并点击下载。从页面底部的链接下载包含所有声音的 zip 文件。
  2. 播放一些声音,看看文件中包含了什么。

设置游戏

对于这个例子,我们将复制一个完整游戏的代码,然后在其中实现声音。执行以下操作来设置游戏:

  1. Fork the code at http://code.9leap.net/codes/show/27917 for the complete game. If you are not using 9leap.net, copy and paste the code from Listing 4-14 into a blank enchant.js game.

    清单 4-14。 香蕉游戏

    enchant();
    window.onload = function() {
        //Game object creation
        var game = new Core(320, 320);
        game.fps = 16;
        game.score = 0;
        game.bananaNum = 10;
        game.time = 0;
    
        //Sound and image loading
        game.preload(['se1.wav',
                      'http://enchantjs.com/assets/img/icon0.gif',
                      'http://enchantjs.com/assets/img/map0.gif']);
    
        //Called when the loading is complete
        game.onload = function() {
    
            //Background creation
            var bg = new Sprite(320, 320);
            var maptip = game.assets['http://enchantjs.com/assets/img/map0.gif'];
            var image = new Surface(320, 320);
            for (var j = 0; j < 320; j += 16) {
                for (var i = 0; i < 320; i += 16) {
                    image.draw(maptip, 16 * 2, 0, 16, 16, i, j, 16, 16);
                }
            }
            bg.image = image;
            game.rootScene.addChild(bg);
    
            //Add bananas
            for (var k = 0; k < 10; k++) game.addBanana();
    
            //Add skull
            game.addDokuro();
    
            //Periodic scene processing
            game.rootScene.addEventListener(Event.ENTER_FRAME, function(){
                game.time ++;
            });
        };
    
        //Adds a skull
        game.addDokuro = function(){
            var dokuro = new Sprite(16, 16);
            dokuro.x = rand(260) + 20;
            dokuro.y = rand(260) + 20;
            dokuro.image = game.assets['http://enchantjs.com/assets/img/icon0.gif'];
            dokuro.frame = 11;
            dokuro.addEventListener(Event.TOUCH_START, function(e) {
                game.end(0, "Game Over");
            });
            game.rootScene.addChild(dokuro);
        };
    
        //Adds a banana
        game.addBanana = function(){
            var banana = new Sprite(16, 16);
            banana.x = rand(260) + 20;
            banana.y = rand(260) + 20;
            banana.image = game.assets['http://enchantjs.com/assets/img/icon0.gif'];
            banana.frame = 16;
    
            //Event handling when the banana is touched
            banana.addEventListener(Event.TOUCH_START, function(e) {
                game.rootScene.removeChild(this);
    
                game.bananaNum--;
                if (game.bananaNum === 0){
                    game.end(1000000 - game.time,
                        (game.time / game.fps).toFixed(2) + " seconds to Clear!");
                }
            });
            game.rootScene.addChild(banana);
        };
    
        //Start game
        game.start();
    };
    
    //Generates a random number
    function rand(num){
        return Math.floor(Math.random() * num);
    }
    

装载声音

就像处理图像一样,你可以通过使用Core对象的preload()函数将声音加载到内存中。尽管Sound对象支持用load()方法直接加载,我们还是建议用preload()方法加载你的声音元素,因为这样可以避免玩家在游戏中等待元素加载的问题,如果他们的连接速度很慢的话。

一旦你的元素被预加载,它们可以通过使用Sound对象构造函数在你的游戏中被创建为声音对象。请执行以下操作来了解如何操作:

  1. After game.onLoad = function() {, type in the code in Listing 4-15.

    清单 4-15。 创建声音对象

    game.se = game.assets['se1.wav'];
    

播放声音

一旦创建了声音对象,就可以用特定的方法来播放它:

  1. After game.rootScene.removeChild(this);, type in the code in Listing 4-16.

    清单 4-16。 奏出声音

    game.se.play();
    
  2. 单击运行。当你点击香蕉时,会有声音播放。点击头骨结束游戏。

在本节中,我们创建了一个游戏,当玩家点击游戏中的香蕉时,游戏会发出声音。声音为游戏增加了互动元素,为玩家提供了额外的感官反馈元素。利用 enchant.js 主页上提供的免费声音库,很容易在游戏中加入声音。

结论

祝贺您通过了高级部分!您现在应该了解了 enchant.js 更高级的功能,包括如何在游戏中组织和导航场景,如何在屏幕上创建开始和游戏,如何实现地图和滚动地图,以及如何启用声音播放。这将允许你在游戏中增加更多的互动,让你的游戏更吸引玩家。

在下一章,我们从 enchant.js 的具体功能上退一步,看看游戏设计。制作自己的原创游戏需要创造力,因此我们向您介绍一个示例工作流,您可以使用它来创建自己的游戏,带您完成设计过程的每一步。

五、游戏设计

到目前为止,我们已经专注于 JavaScript 和 enchant.js 的基础知识,包括您必须用来编写代码以利用 enchant.js 的许多功能的特定格式。这些基础知识包括精灵、标签、场景、地图、如何在9leap.net上上传和共享您的游戏,等等。然而,这并不是一个好的游戏开发商所能做到的!

如果你一头扎进去,试图在没有任何参考资料的情况下,仅仅使用我们之前在本书中提到的内容,从头开始构建你自己的游戏,你将会遇到问题。很多问题。为了使整个过程对你来说更容易,我们在这一章中提供了一些关于从头开始设计和开发游戏的背景。

强大的能力(或者说功能)意味着巨大的责任,作为一名游戏开发者,你对你的玩家有责任。enchant.js 引擎是开发游戏的强大工具。但它的最终目的是什么?当然是为了给玩家带来快乐!好玩是游戏的重点。你的玩家是与你的游戏互动的人,如果他们不喜欢这种体验,他们就没有动力玩你花时间开发的游戏。

这一章特别介绍了构建原创游戏的创作过程。它充满了如何使这个过程变得简单的提示。它也给你一些提示,告诉你如何设计你的游戏来吸引和娱乐你的玩家。我们涵盖了一个游戏应该经历的开发周期:从构思,到编码,最后到改进游戏成品。

迷你游戏开发的牢不可破的规则

在用 enchant.js 制作游戏时,有几个“牢不可破”的规则是你应该遵守的,用该库开发的大多数游戏都是迷你游戏。迷你游戏是小规模的游戏,其特点是游戏时间短,关卡之间的进展简单。

通常,它们只需要一到三个人来开发,有时不到一个小时就可以完成。虽然有些是在几天内开发出来的,但是你不应该在一个游戏上花太多的钱。不断尝试,不要担心失败。你犯的错误越多,从中吸取的教训越多,你的技能就会提高得越多。

不要试图制作史诗游戏

如果你试图从一开始就制作一个史诗游戏,你肯定会失败。从制作一个适合你技能的小而简单的游戏开始,然后逐渐扩展它的功能。事实上,之前大部分财务上成功的游戏,并不是以打造一款史诗级游戏为目标开始的。如果你仔细看看那些游戏,你会发现它们都是由极其简单的组件组成的,尽管数量很多。

在一个完整的简单游戏中,存在许多阶段,每个阶段都通过一个故事连接到另一个阶段。从一开始规则就很复杂的游戏很少见。

不要试图做一个完全原创的游戏

重要的是,当你制定游戏规则时,不要试图实现 100%的原创性。游戏已经以这样或那样的形式发展了很长时间。许多初学者开始渴望做一些没有人见过的东西,但他们冒着创造一些事实上没有人喜欢的东西的风险。试着想象一种没人尝过或听说过的食物。现在想象一下吃这样一种食物,由一个从来没有做过饭的人准备的。一个可怕的想法,不是吗?

它可能完全是一种幸运的美味,但是美食通常不会偏离我们习惯听到的描述它们的形容词:甜、辣、多汁、丰盛等等。当然,一个大师级的厨师会用新的和创新的方式来处理这些味道,但是你不会看到那些刚刚开始学习烹饪艺术的人一头扎进烹饪科学的实验中。

创新很重要,但是如果味道很糟糕,那就不值得了。考虑花生酱和果冻。将两种熟悉的味道结合成新的东西比尝试烹饪完全闻所未闻的东西要好。游戏也一样。不要一开始就以原创为目标。结合熟悉的主题和规则,然后逐渐开始创造你自己独特的转折。

玩家要在十秒钟内掌握游戏

人们很容易被有许多规则的复杂游戏吓倒。在一个完美的游戏中,玩家本能地掌握游戏如何运作,并在开始玩的那一刻就被吸引住了。你要让玩家觉得“这很好玩!”在十秒或更短的时间内。不要指望你的球员有耐心。从他们开始玩游戏的那一刻起,他们就应该尽可能多地了解这个游戏及其吸引力。

如果你的游戏有多个关卡或者比一个标准的迷你游戏更复杂,可以考虑在游戏的开头增加一个可选的教程关卡。教程等级是教你的玩家如何玩的好方法,同时保持动手的感觉。

不要太沉迷于编程

编程本身就很有趣,一些游戏拥有迷人的结构。也就是说,人们很容易过度关注编程,而忽略了游戏的本质。请记住,你的最终目标是创造一些很多人会玩的东西。把你的注意力放在让游戏有趣上,而不是复杂的编程上。

遇到麻烦时,就去“时间攻击”

在大多数游戏中,你可以简单地通过增加时间限制(时间攻击)来增加赌注和乐趣。例如,在 9leap ( http://9leap.net/games/4)发布的“高速黑白棋”中,一个简单的黑白棋(奥赛罗)游戏通过获得时间限制获得了新的生命。这是一场慢节奏的战略游戏,极大地增加了赌注和难度。

不要纠结于失去了什么

对于游戏开发者来说,时间和资源短缺是很典型的,即使一个大型游戏是由一家大公司开发的。无限的开发时间和资源几乎是不可能的。当你开始为缺乏时间和资源而烦恼的时候,你的游戏进程就停止了。相反,专注于在时间和资源允许的情况下做出最好的游戏。

让别人开心

创造一个游戏的全部意义在于为他人创造一些乐趣。如果你费尽心思做了一个游戏,不要纠结于有多少人下载它或者它有多精英。试着用你的游戏带给你的朋友和家人一个微笑。

游戏开发流程

游戏无疑有多种形式,当你计划在 enchant.js 中制作自己的游戏时,研究一些不同类型的游戏总是一个好主意,例如 Fruits Panic(在9leap.net/games/90的一种动作益智游戏)或 Golf Solitaire(在9leap.net/games/2994的一种纸牌游戏)来获得灵感。

然而,一旦你决定了你感兴趣的游戏类型,如果你没有一个路线图来指引你正确的方向,你如何着手实际创建它会是一个混乱的过程。下面的列表显示了开发你自己的游戏的六个步骤。请注意,游戏设计是一个非常大的话题,有许多不同的观点。因为用 enchant.js 创建的大多数游戏都很简单,所以这里列出的开发过程可能不同于更复杂的方法。

  1. 设计游戏规则。
    • a.当你刚开始玩你的第一个游戏时,独自从头开始创建所有的规则可能会有点混乱。例如,如果你正在设计一个像“方块断路器”这样的游戏,你需要决定球在撞击方块和撞击球拍时将如何改变方向,决定所有的电源、球的速度、球加速的速率等等。在这种情况下,看看别人是如何为自己的游戏实现规则并模仿他们的技术是一个非常好的主意。在这个过程中,你应该作为模型使用的游戏是你觉得自己能够制作的游戏。把你的想法写在纸上,如果可以的话,甚至模拟一些动作。如果你还不知道所有的细节,不要担心。不要过度。
  2. 选择一个主题并准备精灵。
    • a.选择一个你想做的游戏的大致想法。花一分钟想象你的游戏。会是赛车吗?宇宙飞船与外星人战斗?之后,玩家会对什么念念不忘?你已经决定了你的游戏规则,所以想一个能最大化玩家潜力的主题可能是个好主意。你还应该评估你的艺术,声音和故事资产,以配合主题。你将如何创造或获得它们?
  3. 给游戏编程。
    • a.你已经选好了你的主题和规则,现在是时候根据它们来编写游戏代码了。根据你的主题,在此之前你可能需要准备一些基本的图片、声音或故事。
  4. 自己玩吧。
    • a.一旦你的游戏版本被编程,你必须亲自尝试一下。无论你多么仔细地制定游戏规则,如果你不真正测试游戏,你就会错过规则中的问题。你不仅仅是测试你的游戏是否遵循你已经建立的规则。你也在反复测试平衡,看看游戏是太难,太容易,还是太难控制。如果可以的话,寻求他人的帮助。玩这个游戏的人越多,给你的反馈越多,你就越能学会如何让它变得更有趣。
  5. 完善您的规则并返回到步骤 3。
    • a.在对你的游戏做了一点测试后,重新检查你的规则,看看如何改进它们,修改你的代码来实现改进,然后再玩你的游戏。根据需要重复这个公式。游戏的最终趣味水平将会因你的细心程度而大相径庭。如果你对自己提出的内容感到满意,增加更多的关卡可能是个好主意。
  6. 完成游戏。
    • a.这里是您添加最后润色的地方。比如制作标题画面,添加高分排名表,细化游戏外观等。

如果你已经有了一个你想做的游戏的想法,请随意交换第一步和第二步。首先选择一个主题,然后制定相应的规则,这可能会很有趣。

在实际的游戏开发中,这个过程要复杂得多,并且根据制作过程的不同而不同。然而,由于 enchant.js 提供的功能,开发周期缩短到这样一个程度:与不使用该库相比,您可以更快地重复这个游戏开发周期。

试着想出一个你想开发的游戏。在接下来的部分中,我们将一步一步地介绍开发过程,从头到尾制作一个完整的示例游戏。当我们经历这个过程时,试着找到利用这个过程来开发你自己的游戏的方法。

设计游戏规则

实际上,你可以通过决定游戏规则或者游戏主题来开始设计你的游戏。然而,如果你是游戏开发的初学者,我们建议你首先规划规则,因为执行游戏规则是游戏编程新手的最大障碍。

你可能会想,“你是怎么想出游戏规则的?”好吧,说白了,你先看看外面已经有什么了。挑出与你的概念相似,与你的技能水平相匹配的东西。在短暂但丰富的电脑游戏历史中,你会发现游戏中的每一条规则都被反复实验、润色,并转化为现有的游戏。就像在武术中一样,游戏有一些预先定义好的风格和技巧。在这一点上不要担心创新。

考虑从9leap.net下载一个开源游戏,然后重组源代码,或者检查源代码,并将其作为在自己的游戏中实现类似规则的灵感。不要因为从现有游戏中借用游戏的核心规则而感到难过。即使你一开始就这样做,通过一点一点地给游戏添加新元素,你作为游戏设计师的个性和个性就会开始显现。

打地鼠、射击游戏、基于对话的角色扮演游戏(RPG)和解谜游戏是初学者模仿的一些流行类型。如果你打算专门为智能手机制作一款游戏,我们建议你从拼图或打地鼠游戏开始。

在下面的章节中,我们使用打地鼠作为创建新游戏的基础。如果你碰巧不熟悉,打地鼠是一种游戏,目标是当鼹鼠出现在他们的洞里时击中它们。这是一个简单的游戏,适合初学者。

首先,请访问http://code.9leap.net/codes/show/23694并玩游戏。简要地看一下代码来熟悉它,但是您现在还不需要复制任何东西。

挑选一个主题

一个真正的打地鼠游戏使用一只鼹鼠的图像。然而,让我们使用一个机器人(Android)角色的图像来代替鼹鼠。Droid 图像是在知识共享许可下授权的,所以我们可以随意使用它。

因此,对于这个游戏,我们将制作一个重击机器人游戏,而不是重击鼹鼠游戏。正如你所看到的,在选择了你的规则之后,选择一个主题是绝对没有错的。重要的是从简单的事情开始。

当然,如果你喜欢画画,你可以画一颗痣或者别的什么东西(甚至是某个人)来让自己变得疲惫不堪。仅仅通过改变你的主题,游戏本身的本质就可以发生巨大的变化。即使游戏的逻辑保持不变,通过改变一个小的视觉方面,整个游戏体验可以发生巨大的变化。

选择主题后,在纸上画出游戏的样子。不一定要画得好。当你把游戏的元素写在纸上时,它可以帮助你理解创建游戏所需的步骤。图 5-1 显示了我们的重击机器人游戏的草图。

9781430247432_Fig05-01.jpg

图 5-1 。素描你的游戏

草图显示了一个 3×3 的网格,机器人将从这些网格中出现。随着时间的推移,多个机器人会从洞里出现,当玩家点击它们时,它们会做鬼脸,然后又掉回洞里。每成功击中一次,玩家的分数就会增加。在特定时间或特定数量的机器人出现后,游戏将结束。

当你进入编码阶段时,偏离这个草图并提供一个解释是非常好的,但是把一些东西写在纸上给你一个工作的起点。在此之后,如果有必要,您可以继续创建您的图像。

为了制作 Droid 的图像文件(图 5-2 ),我们使用了免费的开源图像编辑程序 GIMP for Mac,可以从www.gimp.org下载。但是,您可以使用任何图像编辑程序来创建 sprite 工作表。

9781430247432_Fig05-02.jpg

图 5-2 。机器人角色【mogura.png】精灵表

在这个 sprite 工作表中,六帧 48x48 像素的图像从左到右排列在一起(每个洞的图像构成一帧),然后在我们用Sprite(48,48)创建 sprite 时使用。这与该图像中一帧的大小(48x48 像素)相一致。在第一帧中,机器人的脸还没有出现,在接下来的四帧动画中,他慢慢地出现,直到最后一帧显示他被重击。

如果你正在使用 GIMP,你可以做以下事情来创建一个非常简单的 sprite 工作表,如图 5-2 所示:

  1. 单独下载或绘制 sprite 工作表的组件。
  2. 创建一帧大小的新图像。
  3. 将 sprite 工作表的组件粘贴到图像中。
  4. 将图像另存为 PNG 格式的单独文件,以保持透明度。
  5. 编辑图像以创建下一帧,并将其另存为新图像。
  6. 重复此过程,直到您创建了所有需要的框架。
  7. 使用 GIMP 附带的融合图层脚本合并所有图像。你可以在http://imagine.kicbak.com/blog/?p=114找到使用融合层脚本的说明。
  8. 将图像保存为新的 PNG 文件。

每帧之间的差异很重要。如果你的帧完全不同,那么动画会看起来像机器人或者不存在。在我们的 Droid 游戏中,如果一帧中有一个空洞,而下一帧中的 Droid 完全在洞外,那么当游戏运行并在两帧之间快速切换时,将不会有动画。机器人会突然出现。然而,如果有 30 帧,Droid 每次向上移动很小的量,这将不会有效地使用您的文件,并且需要更多的工作来编程。这里没有你应该制作的具体帧数。最好使用它来找出什么看起来最好,同时不要使用太多的框架。

给游戏编程

一旦你决定了游戏的规则和主题,就该进入编程阶段了。当你到了这一步,开始编程最简单的方法就是尽可能地创建你的游戏的最简单的版本。这个版本不一定需要是工作版,也绝对不需要具备前瞻游戏的所有特性。

创建最简单的版本

打地鼠游戏最简单的版本是一个显示一个洞的程序。因为最终会有不止一个洞,所以我们应该首先为洞创建一个类。执行以下操作来创建该类:

  1. http://code.9leap.net/codes/show/28286处分叉空白项目模板。这个模板不包含游戏代码,但是包含了你需要的精灵图片。

  2. Initialize the enchant library and define a class for holes by typing in the code in Listing 5-1 into your project. This will run all the code inside it whenever a Pit object (hole) is created.

    清单 5-1。 定义坑类

    enchant();
    
    Pit = Class.create(Sprite,{
         initialize:function(x,y){
              //Call the Sprite class (super class) constructor
              enchant.Sprite.call(this,48,48);
              this.image = game.assets[’mogura.png’];
              this.x = x;
              this.y = y;
         }
    });
    
  3. Create a single hole on the screen by adding the code in Listing 5-2 under the code you just entered.

    清单 5-2。 在屏幕上创建一个孔

    window.onload = function(){
         game = new Game(320, 320);
         //Load Droid image
         game.preload(’mogura.png’);
         game.onload = function(){
              var pit = new Pit(100,100);
              game.rootScene.addChild(pit);
        }
        game.start();
    }
    
  4. Click Run. A hole is displayed, as shown in Figure 5-3. The Pit class we created extends the enchant.js Sprite class, and therefore can be used in the same way as Sprite. If you encounter problems, you can see the result of this step on code.9leap.net (http://code.9leap.net/codes/show/23728).

    9781430247432_Fig05-03.jpg

    图 5-3 。简单打地鼠程序的结果

让机器人出现

下一步是修改我们的Pit类,使 Droid 出现。代码中唯一改变的部分是Pit类,所以我们将只展示这一部分。执行以下操作来更改该类:

  1. Modify the Pit class to change to the next frame of the Droid if the current frame is one of the first four by modifying the Pit class to match Listing 5-3. This loops the animation of the Droid appearing over and over again, but it is the groundwork for the next few steps. In this and all future code listings in the chapter, changes from the previous code are in boldface.

    清单 5-3。 更新了坑类

    Pit = Class.create(Sprite,{
         initialize:function(x,y){
              //Call the Sprite class (super class) constructor
              enchant.Sprite.call(this,48,48);
              this.image = game.assets[’mogura.png’];
              this.x = x;
              this.y = y;
              //Defines an event listener to run every frame
              this.addEventListener(’enterframe’,this.tick);
         },
         tick:function(){
              this.frame++;
              //Loop the animation once complete
              if(this.frame>=4)this.frame=0;
         }
    });
    
  2. Click Run. The Droid repeatedly appears out of the hole, as shown in Figure 5-4. If you encounter problems, you can check the result of this step on code.9leap.net (http://code.9leap.net/codes/edit/23729).

    9781430247432_Fig05-04.jpg

    图 5-4 。打地鼠更新了动画

调整动画速度

我们制作了一个动画,展示我们的小机器人从他的洞里快速出现。然而,他移动得太快了以至于不能击中他,所以我们需要让他慢一点。目前,在机器人精灵的第四帧之后,我们立即返回到第 0 帧。这突然回到他的洞感觉不自然。让我们重写它,这样一旦我们进行到第四帧,我们就返回到 0。执行以下操作进行这些更改:

  1. Add a variable mode to the Pit class. Use it to define how the Droid should behave with respect to animation by modifying the Pit class to match Listing 5-4.

    清单 5-4。 制作坑类动画

    Pit = Class.create(Sprite,{
         initialize:function(x,y){
              //Call the Sprite class (super class) constructor
              enchant.Sprite.call(this,48,48);
              this.image = game.assets[’mogura.png’];
              this.x = x;
              this.y = y;
              //Defines an event listener to run every frame
              this.addEventListener(’enterframe’,this.tick);
              //Keeps track of the state of the Droid
              this.mode = 0;
         },
         tick:function(){
              //only change the frame every other frame
              //the return call ends the function
              if(game.frame%2!=0)return;
              switch(this.mode){
                   //Droid is appearing from the hole
                   case 0:
                        this.frame++;
                        //change mode after completely appearing
                        if(this.frame>=4) this.mode=1;
                        break;
                   //Droid is hiding in the hole
                   case 1:
                        this.frame--;
                        //change mode after completely hiding
                        if(this.frame<=0) this.mode=0;
                        break;
              }
         }
    });
    

如果模式设置为0,这将代表机器人正在出现,但尚未完全出现,那么机器人将前进通过前四帧,然后将其模式更改为1。模式1代表躲在洞里的机器人,并使机器人颠倒它出现时使用的帧的顺序。最终的结果是,机器人会反复出现,然后消失。

我们的动画也变得更干净了。我们已经更改了代码,因此基于一个if语句,帧处理每两帧发生一次,而不是每一帧,这使它变得更好更平滑。此外,我们还添加了一个名为mode的新属性,具有模式0(出现)和模式1(隐藏中),并且我们已经基于该属性执行了一个切换来执行相应的处理。如果遇到问题,可以在code.9leap.net ( http://code.9leap.net/codes/show/23730)上查看这一步的结果。

让机器人随机出现

用我们现在的代码,如果我们复制这个洞,所有的机器人会同时出现。我们需要让机器人出现的时间随机。目前我们只有模式0(显示)和模式1(隐藏),所以我们需要添加一个模式,在两者之间等待一段随机的时间,方法如下:

  1. Upgrade the Pit class to cause random time to elapse between appearing and disappearing by replaying the Pit class with the code shown in Listing 5-5.

    清单 5-5。 升级坑类

    //function to generate random numbers
     rand = function(n){
         return Math.floor(Math.random()*n);
    }
    
    //Define a class for holes
    Pit = Class.create(Sprite,{
         initialize:function(x,y){
              //Call the Sprite class (super class) constructor
              enchant.Sprite.call(this,48,48);
              this.image = game.assets[’mogura.png’];
              this.x = x;
              this.y = y;
              //Defines an event listener to run every frame
              this.addEventListener(’enterframe’,this.tick);
              //Set the Droid mode to 2 (waiting) in the beginning.
              this.mode = 2;
              //Set the next mode as 0 (appearing)
              this.nextMode = 0;
              //wait for a random number (0-99) of frames
              this.waitFor =  game.frame+rand(100);
         },
         tick:function(){
              //only change the frame every other frame
              //the return call ends the function
              if(game.frame%2!=0)return;
              switch(this.mode){
                   //Droid is appearing from the hole
                   case 0:
                        this.frame++;
                        if(this.frame>=4) {
                        //switch to Mode 2 (waiting) after appearing
                            this.mode=2;
                        //the mode to go to after Mode 2 is Mode 1 (hide)
                        this.nextMode=1;
                        //Set a random waiting time for 0 ∼ 99 frames
                        this.waitFor = game.frame+rand(100);
                                    }
                        break;
                   //Droid is going to hide in the hole
                   case 1:
                        this.frame--;
                        //if Droid is hidden...
                        if(this.frame<=0){
                             //Switch to Mode 2 (waiting)
                             this.mode=2;
                             //The next mode should be Mode 0 (appear)
                             this.nextMode=0;
                             //Set a random waiting time for 0 ∼ 99 frames
                             this.waitFor = game.frame+rand(100);
                        }
                        break;
                   //Droid is waiting
                   case 2:
                        //if the game’s current frame is greater than
                        //the set frame to wait for...
                        if(game.frame>this.waitFor){
                             //Make a transition to the next mode
                             this.mode = this.nextMode;
                        }
                        break;
              }
         }
    });
    

我们增加了一个名为rand()的函数来生成随机数,还创建了一个新的mode,模式2(等待)。

游戏开始后经过的帧数存储在game.frame中。当waitFor中设置的帧数过去后,我们转换到下一个模式。通过在模式0(出现)和模式1(隐藏)之间插入这个,机器人出现和消失之间的时间可以被随机化。

如果有任何问题,请检查http://code.9leap.net/codes/show/23731处的代码。

实现机器人重击

到目前为止,我们已经将我们的机器人朋友设置为随机出现和消失。现在让我们创建当玩家点击他时的处理。对于所谓的“疲惫的”事件,我们使用一个touchstart事件监听器(事件监听器在第二章中介绍)。执行以下操作来实现它:

  1. Modify the Pit class by adding the bold sections of code shown in Listing 5-6.

    清单 5-6。 实现“疲惫不堪”的状态

    //Define a class for holes
    Pit = Class.create(Sprite,{
         initialize:function(x,y){
              //Call the Sprite class (super class) constructor
              enchant.Sprite.call(this,48,48);
              this.image = game.assets[’mogura.png’];
              this.x = x;
              this.y = y;
              //Defines an event listener to run every frame
              this.addEventListener(’enterframe’,this.tick);
              //Defines an event listener for when the Droid gets whacked
              this.addEventListener(’touchstart’,this.hit);
              //Set the Droid mode to 2 (waiting) in the beginning.
              this.mode = 2;
              //Set the next mode as 0 (appearing)
              this.nextMode = 0;
              //wait for a random number (0-99) of frames
              this.waitFor =  game.frame+rand(100);
         },
         tick:function(){
              //only change the frame every other frame
              //the return call ends the function
              if(game.frame%2!=0)return;
              switch(this.mode){
                   //Droid is appearing from the hole
                   case 0:
                        this.frame++;
                        if(this.frame>=4) {
                        //switch to Mode 2 (waiting) after appearing
                            this.mode=2;
                        //the mode to go to after Mode 2 is Mode 1 (hide)
                        this.nextMode=1;
                        //Set a random waiting time for 0 ∼ 99 frames
                        this.waitFor = game.frame+rand(100);
                                    }
                        break;
                   //Droid is going to hide in the hole
                   case 1:
                        this.frame--;
                        //if Droid is hidden...
                        if(this.frame<=0){
                             //Switch to Mode 2 (waiting)
                             this.mode=2;
                             //The next mode should be Mode 0 (appear)
                             this.nextMode=0;
                             //Set a random waiting time for 0 ∼ 99 frames
                             this.waitFor = game.frame+rand(100);
                        }
                        break;
                   //Droid is waiting
                   case 2:
                        //if the game’s current frame is greater than
                        //the set frame to wait for...
                        if(game.frame>this.waitFor){
                             //Make a transition to the next mode
                             this.mode = this.nextMode;
                        }
                        break;
              }
         },
         //Whack Droid
         hit:function(){
              //only when Droid has appeared at least half-way
              if(this.frame>=2){
                   //Droid after being whacked
                   this.frame=5;
                   //Switch to waiting mode
                   this.mode=2;
                   this.nextMode=1;
                   //Number of frames to wait is fixed at 10
                   this.waitFor = game.frame+10;
              }
         }
    });
    

请注意我们刚刚添加的新功能hit。如果你在机器人至少半张脸出现时点击它,重击动画将会播放。

疲惫不堪的 Droid 帧显示了十帧后,Droid 再次转换到模式1(隐藏)。以这种方式实现其他功能时切换状态的程序被称为状态机

如果您在自己的代码中遇到意外行为,您可以在http://code.9leap.net/codes/show/23739检查这一步的结果。

我们现在已经创造了打地鼠游戏的基本要素,在这个游戏中,你要对抗我们的机器人朋友。然而,我们仍然有一个问题。如果你持续不断地击打机器人,他的脸就会卡在疲劳状态。

防止机器人连续重击

我们需要确保一旦机器人被击垮,我们不能再打他,让他保持被击垮的状态。为此,让我们通过执行以下操作向我们的Pit类添加一个新属性:

  1. Update the Pit class by adding the bold sections from Listing 5-7.

    清单 5-7。 防打圈动画

    //Define a class for holes
    Pit = Class.create(Sprite,{
         initialize:function(x,y){
              //Call the Sprite class (super class) constructor
              enchant.Sprite.call(this,48,48);
              this.image = game.assets[’mogura.png’];
              this.x = x;
              this.y = y;
              //Defines an event listener to run every frame
              this.addEventListener(’enterframe’,this.tick);
              //Defines an event listener for when the Droid gets whacked
              this.addEventListener(’touchstart’,this.hit);
              //Set the Droid mode to 2 (waiting) in the beginning.
              this.mode = 2;
              //Set the next mode as 0 (appearing)
              this.nextMode = 0;
              //wait for a random number (0-99) of frames
              this.waitFor =  game.frame+rand(100);
              //stores info on whether or not the Droid
              //has already been whacked
              this.currentlyWhacked = false;
         },
         tick:function(){
              //only change the frame every other frame
              //the return call ends the function
              if(game.frame%2!=0)return;
              switch(this.mode){
                   //Droid is appearing from the hole
                   case 0:
                        this.frame++;
                        if(this.frame>=4) {
                        //switch to Mode 2 (waiting) after appearing
                        this.mode=2;
                        //the mode to go to after Mode 2 is Mode 1 (hide)
                        this.nextMode=1;
                        //Set a random waiting time for 0 ∼ 99 frames
                        this.waitFor = game.frame+rand(100);
                             }
                        break;
                   //Droid is going to hide in the hole
                   case 1:
                        this.frame--;
                        //if Droid is hidden...
                        if(this.frame<=0){
                             //Switch to Mode 2 (waiting)
                             this.mode=2;
                             //The next mode should be Mode 0 (appear)
                             this.nextMode=0;
                             //Set a random waiting time for 0 ∼ 99 frames
                             this.waitFor = game.frame+rand(100);
                             //reset flag as the whacked Droid disappears
                             this.currentlyWhacked = false;
                        }
                        break;
                   //Droid is waiting
                   case 2:
                        //if the game’s current frame is greater than
                        //the set frame to wait for...
                        if(game.frame>this.waitFor){
                             //Make a transition to the next mode
                             this.mode = this.nextMode;
                        }
                        break;
              }
         },
         //Whack Droid
         hit:function(){
              //Do nothing if the Droid has already been whacked
              if(this.currentlyWhacked)return;
              //only when Droid has appeared at least half-way
              if(this.frame>=2){
                   //Set the flag so we know he’s been whacked
                   this.currentlyWhacked = true;
                   //Droid after being whacked
                   this.frame=5;
                   //Switch to waiting mode
                   this.mode=2;
                   this.nextMode=1;
                   //Number of frames to wait is fixed at 10
                   this.waitFor = game.frame+10;
              }
         }
    });
    

在这里,我们添加了currentlyWhacked属性,这是一个标志,表示机器人是否被攻击。当 Droid 被创建时,这个属性被设置为false。每当一个机器人被重击,这个属性被设置为true,并开始隐藏动画序列。机器人消失后,这个标志被设置为false,因为一个新的机器人将从洞里出来。这样,我们就避免了机器人被连续攻击的情况。

如果您在编写与这个示例匹配的代码时遇到问题,您可以在http://code.9leap.net/codes/show/23740检查代码。

复制孔

我们几乎已经完成了只有一个洞的重击机器人游戏的基础。你可能会想,“为什么我们要大费周章只挖一个洞?”通过像我们所做的那样创建一个类,我们可以在眨眼之间完成我们的游戏。执行以下操作复制孔:

  1. Modify the game.onload function by editing it to match Listing 5-8.

    清单 5-8。 复制机器人孔

    game.onload = function(){
              //Line up holes in a 4x4 matrix
              for(var y=0;y<4;y++){
                   for(var x=0;x<4;x++){
                        var pit = new Pit(x*48+20,y*48+20);
                        game.rootScene.addChild(pit);
                   }
              }
    }
    
  2. Click Run. A line of Droids appears, as shown in Figure 5-5.

    9781430247432_Fig05-05.jpg

    图 5-5 。创建 Pit 类的多个实例

就像这样,我们有一个打地鼠游戏!如您所见,使用类可以节省大量时间。

因为我们将在接下来的步骤中进一步完善我们的游戏,请确保到目前为止您所做的一切都已成功完成。到目前为止的代码可以从http://code.9leap.net/codes/show/23741开始检查和分叉。

随机排列孔洞

开发游戏时,尝试不同的方法会很有帮助。例如,为了检查它将如何影响游戏,您可能想尝试让我们的重击机器人游戏中的洞随机出现在屏幕上,而不是排成 4x4 的网格。你可以在清单 5-9 和图 5-6 中看到实现这一点的变化。

清单 5-9。 随意放置孔洞

game.onload = function(){
     for(var i=0;i<7;i++){
          //Place pits randomly
          var pit = new Pit(rand(300),rand(300));
          game.rootScene.addChild(pit);
     }
}

9781430247432_Fig05-06.jpg

图 5-6 。随机放置孔

因为洞的位置是随机的,所以每次你重新载入游戏时它们都会改变。不管这些孔在哪里结束,它们的工作方式完全相同,因为它们是由Pit类定义的。

现在是有趣的部分了。根据球洞的最终位置,比赛的本质将会改变。发挥你的创造力,尝试创造自己的原创球洞布置。你现在已经完成了你的第一个游戏原型!

播放、重复、完成

一旦你有了一个工作原型,下一步就是玩这个原型,并注意游戏中任何需要改进或添加的部分。

如果你玩几次我们完成的“重击机器人”游戏,你会发现少了两样重要的东西:一个分数和机器人出现次数的限制。

如果你试着让游戏的球洞变得随机,对于下面的例子,重置你的game.onload函数回到清单 5-8 中显示的。

显示分数

让我们从显示分数开始:

  1. Create the ScoreLabel class by adding the code shown in Listing 5-10 above the line of code starting with window.onload.

    清单 5-10。 ScoreLabel Class

    //ScoreLabel class definition, extending Label class
    ScoreLabel = Class.create(Label,{
         initialize:function(x,y){
              //Call the Label class constructor
              enchant.Label.call(this,"SCORE:0");
              this.x=x;
              this.y=y;
              this.score = 0;
         },
         //Adds points to the score
         add:function(pts){
              this.score+=pts;
              //Change the displayed score
              this.text="SCORE:"+this.score;
         }
    });
    
  2. Create a new instance of the ScoreLabel class by adding a new instance of the ScoreLabel class, as shown in Listing 5-11.

    清单 5-11。 创建 ScoreLabel

    game.onload = function(){
    
         //Display ScoreLabel
         scoreLabel=new ScoreLabel(5,5);
         game.rootScene.addChild(scoreLabel);
    
         //Line up holes in a 4x4 matrix
         for(var y=0;y<4;y++){
              for(var x=0;x<4;x++){
                   var pit = new Pit(x*48+20,y*48+20);
                   game.rootScene.addChild(pit);
              }
         }
    }
    
  3. Add code to increase the score whenever a Droid gets hit by adding the bold sections from Listing 5-12 to the hit function.

    清单 5-12。 增加命中分数

    //Whack Droid
    hit:function(){
         //Do nothing if the Droid has already been whacked
         if(this.currentlyWhacked)return;
         //only when Droid has appeared at least half-way
         if(this.frame>=2){
              //Set the flag so we know he’s been whacked
              this.currentlyWhacked = true;
              //Droid after being whacked
              this.frame=5;
              //Switch to waiting mode
              this.mode=2;
              this.nextMode=1;
              //Number of frames to wait is fixed at 10
              this.waitFor = game.frame+10;
              //Add score
              scoreLabel.add(1);
         }
    }
    
  4. Click Run. A score appears in the upper-left side of the screen and increases by one whenever a player whacks a Droid.

    如果您在添加这段代码时遇到问题,请查看位于http://code.9leap.net/codes/show/23777的示例代码。

限制机器人的出现

既然我们已经增加了一个分数,事情看起来就要结束了。然而,我们的机器人出现得太频繁了,以至于只要快速击中同一个点就可以获得高分。有取之不尽的机器人供应,剥夺了玩家富有挑战性的游戏体验。

让我们改变游戏,让我们的机器人只出现 30 次。以这种方式改变会迫使玩家在机器人出现的 30 次中获得尽可能多的命中。为此,请执行以下操作:

  1. Create a variable for the total number of Droids and the maximum number of appearances for a Droid, and then implement them by copying in the bold sections from Listing 5-13.

    清单 5-13。 限制机器人外观

    enchant();
    //function to generate random numbers
    rand = function(n){
         return Math.floor(Math.random()*n);
    }
    
    //Number of appearances of the Droid
    
    maxDroid = 30;
    
    //Total number of Droids
    
    totalDroid = 16;
    
    //Define a class for holes
    Pit = Class.create(Sprite,{
         initialize:function(x,y){
              //Call the Sprite class (super class) constructor
              enchant.Sprite.call(this,48,48);
              this.image = game.assets[’mogura.png’];
              this.x = x;
              this.y = y;
              //Defines an event listener to run every frame
              this.addEventListener(’enterframe’,this.tick);
              //Defines an event listener for when the Droid gets whacked
              this.addEventListener(’touchstart’,this.hit);
              //Set the Droid mode to 2 (waiting) in the beginning.
              this.mode = 2;
              //Set the next mode as 0 (appearing)
              this.nextMode = 0;
              //wait for a random number (0-99) of frames
              this.waitFor =  game.frame+rand(100);
              //stores info on whether or not the Droid
              //has already been whacked
              this.currentlyWhacked = false;
         },
         tick:function(){
              //only change the frame every other frame
              //the return call ends the function
              if(game.frame%2!=0)return;
              switch(this.mode){
                   //Droid is appearing from the hole
                   case 0:
                        this.frame++;
                        if(this.frame>=4) {
                        //switch to Mode 2 (waiting) after appearing
                        this.mode=2;
                        //the mode to go to after Mode 2 is Mode 1 (hide)
                        this.nextMode=1;
                        //Set a random waiting time for 0 ∼ 99 frames
                        this.waitFor = game.frame+rand(100);
                             }
                        break;
                   //Droid is going to hide in the hole
                   case 1:
                        this.frame--;
                        //if Droid is hidden...
                        if(this.frame<=0){
                             //Switch to Mode 2 (waiting)
                             this.mode=2;
                             //The next mode should be Mode 0 (appear)
                             this.nextMode=0;
                             //Set a random waiting time for 0 ∼ 99 frames
                             this.waitFor = game.frame+rand(100);
                             //reset flag as the whacked Droid disappears
                             this.currentlyWhacked = false;
    
                             //Reduce maximum amount of Droids
                             maxDroid--;
                             //If the amount is exceeded the Droid should not appear
                            if(maxDroid<=0) {
                                this.mode=3;
                                if(maxDroid <= -1*totalDroid + 1) {
                                    game.end(scoreLabel.score, scoreLabel.text);
                                }
                            }
                        }
                        break;
                   //Droid is waiting
                   case 2:
                        //if the game’s current frame is greater than
                        //the set frame to wait for...
                        if(game.frame>this.waitFor){
                             //Make a transition to the next mode
                             this.mode = this.nextMode;
                        }
                        break;
              }
         },
         //Whack Droid
         hit:function(){
              //Do nothing if the Droid has already been whacked
              if(this.currentlyWhacked)return;
              //only when Droid has appeared at least half-way
              if(this.frame>=2){
                   //Set the flag so we know he’s been whacked
                   this.currentlyWhacked = true;
                   //Droid after being whacked
                   this.frame=5;
                   //Switch to waiting mode
                   this.mode=2;
                   this.nextMode=1;
                   //Number of frames to wait is fixed at 10
                   this.waitFor = game.frame+10;
                   //Add score
                   scoreLabel.add(1);
              }
         }
    });
    
    //ScoreLabel class definition, extending Label class
    ScoreLabel = Class.create(Label,{
         initialize:function(x,y){
              //Call the Label class constructor
              enchant.Label.call(this,"SCORE:0");
              this.x=x;
              this.y=y;
              this.score = 0;
         },
         //Adds points to the score
         add:function(pts){
              this.score+=pts;
              //Change the displayed score
              this.text="SCORE:"+this.score;
         }
    });
    
    //Initialization
    window.onload = function(){
         game = new Game(320, 320);
         //Load Droid image
         game.preload(’mogura.png’);
         game.onload = function(){
    
              //Display ScoreLabel
              scoreLabel=new ScoreLabel(5,5);
              game.rootScene.addChild(scoreLabel);
    
              //Line up holes in a 4x4 matrix
              for(var y=0;y<4;y++){
                   for(var x=0;x<4;x++){
                        var pit = new Pit(x*48+20,y*48+20);
                        game.rootScene.addChild(pit);
                   }
              }
         }
    
         game.start();
    };
    

你可以在code.9leap.net ( http://code.9leap.net/codes/show/23778)找到这个改动的源代码。

目前,我们的重击机器人游戏结束了。然而,有很多其他的方法来制作打地鼠类型的游戏。你可以重击移动的鼹鼠或者增加等级,在等级中鼹鼠的速度和洞的位置是变化的,当然你也可以和机器人以外的其他东西战斗或者让他们从洞以外的地方出现(比如门)。通过改变规则和主题,无限的变化是可能的。

现在轮到你了。释放你的创造力,尝试创造一件属于你自己的杰作。完成游戏后,尝试将其上传到http://9leap.net。如果遇到问题,请参见第一章了解关于上传游戏的信息。

结论

在这一章中,我们重点介绍了使用 enchant.js 设计游戏的过程。我们看了游戏设计的牢不可破的规则,回顾了游戏开发过程,甚至设计了一个完全可用的打地鼠游戏。在下一章中,我们将研究游戏中最经典的原型之一,街机射击游戏,并一步一步地解释如何用 enchant.js 创建一个这样的游戏。

六、创建街机射击游戏

在第五章中,我们研究了游戏设计并创造了我们自己的打地鼠(或者我应该说是打地鼠机器人?)游戏。在这一章中,我们来看另一个游戏,一个比打地鼠更复杂的街机射击游戏。

在街机射击游戏中,我们包括几个有助于有趣游戏的功能:背景音乐,不可玩的角色(如好人和坏人),爆炸,滚动背景,等等。将所有这些元素整合在一起需要在游戏设计方面进行仔细的规划,因为我们创建的所有类都必须相互交互。例如,从玩家的船上发射的子弹应该会让敌人消失。除了介绍街机射击游戏是如何设计和编码的,我们还介绍了几个新的主题,包括指示器、关卡和旋转。

汇总列表

  1. 探索游戏和设置
  2. 为游戏奠定基础
  3. 创建玩家的船
  4. 创建拍摄类
  5. 创建 PlayerShoot 类并使船射击
  6. 制造敌人
  7. 创建敌人射击类并让敌人射击
  8. 让敌人呈弧形移动
  9. 爆炸
  10. 添加滚动背景
  11. 添加生活量表

建造街机射击游戏

让我们从我们能制作的最简单的街机射击游戏开始,然后逐步增加复杂度。本节我们编写的游戏是一个简单的射击游戏。当玩家触摸屏幕时,飞船会移动到触摸的位置,只要用户触摸它,飞船就会发射子弹穿过屏幕。敌人的船出现在屏幕的对面,向玩家的船射击。如果玩家的船被击中,游戏结束。玩家每消灭一艘敌舰,分数就会增加。游戏的目标是在被击中之前获得尽可能高的分数。

在第五章中,我们的方法是写一小段简单的代码,然后在开发过程中不断改进,在每一次迭代中玩游戏。我们在这里也是这样做的,但是因为街机射击游戏比打地鼠游戏更复杂,所以有更长的步骤。

探索游戏,设置

如果你在我们开始编码之前玩了我们将要编码的游戏,你会对游戏的工作原理有更好的了解。执行以下操作来熟悉游戏,并设置您将在其中编码的环境:

  1. http://9leap.net/games/1034玩几次游戏,感受一下我们将要制作的游戏。特别注意敌人是如何随机出现的,当屏幕上有很多敌人子弹时,游戏会变得多么困难。这款游戏因其重玩价值而在9leap.net 上广受欢迎。由于随机产生的敌人,每次的体验都不一样。
    * 在http://code.9leap.net/codes/show/29839叉模板。该模板包含您需要的必要图像文件。

`为游戏打基础

和往常一样,最好先从一个游戏最简单的元素开始,然后再加入更复杂的元素。在这里,我们设置了Core对象,设置了背景,并创建了一个乐谱。执行以下操作进行设置:

  1. Initialize the enchant.js library and create the Core entity by copying the code in Listing 6-1 into the blank template. Because of the complexity of this section, we label sections of code with comments so we can refer to them later in the chapter.

    清单 6-1。 街机射手的基础

    enchant();
    //Class Definitions
    
    window.onload = function() {
        game = new Core(320, 320);
        //Game Properties
        game.fps = 24;
    
        game.onload = function() {
        };
    
        game.start();
    };
    
  2. Create a black background by typing the code in Listing 6-2 into the game.onload function. This specifies the backgroundColor of rootScene to be black.

    清单 6-2。 创建黑色背景

    //In-Game Variables and Properties
    game.rootScene.backgroundColor = 'black';
    

    rootScenebackgroundColor属性可以接受所有标准颜色的名称和颜色的十六进制值(例如,#FF0000代表红色)。

  3. 单击运行。屏幕应该会变黑。

  4. Create a game variable for the player’s score by adding the code in Listing 6-3 to the //Game Properties section. Later, we'll make this variable increase in value whenever enemies are hit by bullets from the player’s ship.

    清单 6-3。 创建分数变量

    game.score = 0;
    
  5. Create a ScoreLabel and add it to rootScene by adding the code in Listing 6-4 to the //In-game Variables and Properties section. The (8,8) specifies the top-left corner of the label to be placed 8 pixels to the right and 8 pixels down from the top-left corner of the game. Later, we’ll update the value of the ScoreLabel with the value of game.score every frame.

    清单 6-4。 为 rootScene 创建并添加分数标签

    scoreLabel = new ScoreLabel(8, 8);
    game.rootScene.addChild(scoreLabel);
    

    ScoreLabel类是名为ui.enchant.js的插件的一部分,该插件包含在enchantjs.com的下载包中。如果你早点从 code.9leap.net 接手这个项目的话,它也包括在内。如果没有,在继续之前,您需要确保行<script src='/static/enchant.js-latest/plugins/ui.enchant.js'></script>被添加到您的index.html文件中。

  6. 单击运行。ScoreLabel应出现在屏幕顶部,并带有单词“SCORE:”。

如果您的代码遇到任何问题,您可以在http://code.9leap.net/codes/show/29841找到一个完整的工作示例。

创建玩家的船

下一步是创建玩家的船,我们将创建一个类。执行以下操作来创建它:

  1. Create a class definition for the player by entering the code in Listing 6-5 to the //Class Definitions section. For a refresher, the enchant.Sprite declaration creates the new class as an extension of the Sprite class, which means all properties and methods of the Sprite class will work on the Player class as well. Everything in the initialize function will be run when a Player object is created.

    清单 6-5。 玩家类

    // Player class
    var Player = enchant.Class.create(enchant.Sprite, {
        initialize: function(x, y){
    
        }
    });
    
  2. Preload graphic.png, which contains all the images used in this game, by adding the code in Listing 6-6 to the //Game Properties section. Figure 6-1 shows the graphic.

    清单 6-6。 预加载图像

    game.preload('graphic.png');
    

    9781430247432_Fig06-01.jpg

    图 6-1 。Graphic.png

  3. Define the Player class as a 16x16 instance of the Sprite class and specify graphic.png as its image by entering the code in Listing 6-7 into the initialize function of the Player class.

    清单 6-7。 指定尺寸和图像

    enchant.Sprite.call(this, 16, 16);
    this.image = game.assets['graphic.png'];
    
  4. Make the location of Player to be whatever was specified when it was created and set the frame by adding the code in Listing 6-8 directly under what you just added.

    清单 6-8。 设置位置和帧

    this.x = x;
    this.y = y;
    this.frame = 0;
    
  5. Create a variable to keep track of when the screen is being touched by adding the code in Listing 6-9 to //Game Properties. This will set in the touch event listeners we’ll create next, and will be used to determine if bullets should be fired from the ship later.

    清单 6-9。??【跟踪触摸】

    game.touched = false;
    
  6. Back in the initialize function, add an event listener to move the Player to Y position of the touch event when a touchstart event occurs by entering the code in Listing 6-10. When the touchstart event occurs, we also set the game.touched variable to true. Notice how we don’t need to put a line break between player.y = e.y; and game.touched = true;. The semicolons delineate the commands.

    清单 6-10。 添加 Touchstart 事件监听器

    game.rootScene.addEventListener('touchstart',
            function(e){ player.y = e.y; game.touched = true; });
    
  7. Below that, add event listeners for both touchend and touchmove events to take care of all possible interaction from the player by adding the code in Listing 6-11.

    清单 6-11。 附加事件监听器

    game.rootScene.addEventListener('touchend',
            function(e){ player.y = e.y; game.touched = false; });
    game.rootScene.addEventListener('touchmove',
            function(e){ player.y = e.y; });
    
  8. Create an instance of the Player class and add it to rootScene by entering the code in Listing 6-12 directly below what you just added, still inside the initialize function.

    清单 6-12。Player添加到rootScene

    game.rootScene.addChild(this);
    
  9. Create an instance of Player by adding the code in Listing 6-13 to the //In-game Variables and Properties section. The instance of Player is automatically added to rootScene because of the last step.

    清单 6-13。 创建Player 的实例

    player = new Player(0, 152);
    
  10. Click Run. The ship appears on the screen. If you click and hold, the ship will follow your cursor up and down on the screen.

如果您在本节中遇到问题,可以在`http://code.9leap.net/codes/show/29845`找到一个工作代码示例。

创建拍摄类

这个游戏中会出现两种弹药:玩家船射的弹药和敌人射的弹药。这两种类型使用相同的图像,所以我们将从创建一个通用的Shoot类开始。执行以下操作来创建它:

  1. Create the basic Shoot class by entering the code in Listing 6-14 beneath the //Player Class definition. It should have two functions: initialize and remove. It should also extend the Sprite class. When an instance of the Shoot class is created, we will pass three values to it: an X coordinate, a Y coordinate, and a direction.

    清单 6-14。创建射击类

    // Shoot class
    var Shoot = enchant.Class.create(enchant.Sprite, {
        initialize: function(x, y, direction){
        },
        remove: function(){
        }
    });
    
  2. Create the Shoot class as a 16x16 instance of the Sprite class, and create and assign necessary variables by entering the code in Listing 6-15 into the initialize function of the Shoot class. We will use the moveSpeed variable next to control the speed of movement and allow for easy modification of ammunition speed later on.

    清单 6-15。Shoot类的实例变量

    enchant.Sprite.call(this, 16, 16);
    this.image = game.assets['graphic.png'];
    this.x = x;
    this.y = y;
    this.frame = 1;
    this.direction = direction;
    this.moveSpeed = 10;
    
  3. Create an enterframe event listener to control movement by entering the code in Listing 6-16 directly beneath this.movespeed = 10;. Code entered here will be run on instances of Shoot every frame.

    清单 6-16。 创建一个enterframe事件监听器

    this.addEventListener('enterframe', function(){
            });
    

我们将在下一节中向这个事件监听器添加代码。

用 Cos 和 Sin 控制方向

我们创建了Shoot类来接受方向,但是我们还不知道传递什么样的值来指示方向。我们也有一个事件监听器来处理运动,但是我们还不知道如何处理偏离方向的运动。我们如何做到这一点?具有功能cosinesine,通常简称为cossin

要理解这些功能,首先需要理解单位圆的方向,如图图 6-2 所示。

9781430247432_Fig06-02.jpg

图 6-2 。单位圆

单位圆用弧度表示方向。一弧度等于一个圆的半径的长度,在它的圆周上排成一行。圆圈中的pi符号是一个数学常数,等于一个圆的周长除以其直径(约 3.14)。这有什么关系?因为 cos 和 sin 只接受以弧度表示的值,而左右主方向分别等于pi0(或2 * pi)。

假设你想从圆心画一条长度为 1 的线。Cossin将分别给出该线端点的 x 和 y 坐标,根据pi给出一个方向。例如,如果我们想向点的右边移动 1,我们可以将pi传递给cos来找出我们需要沿着 x 轴移动多少才能到达那里(-1)。我们还将通过pisin来找出我们需要沿着 y 轴(0)移动多少。

代码就是这样处理运动的。我们根据pi选择一个方向,然后使用cossin来计算每一帧精灵移动多少。

  • 4.  Inside the event listener you just created, specify how instances of the Shoot class should move based off cos and sin by entering the code in Listing 6-17. Multiplying the results of the calculations by moveSpeed allows the ammunition to be manipulated in terms of speed later, if needed.

    清单 6-17。CosSin 控制移动

    this.x += this.moveSpeed * Math.cos(this.direction);
    this.y += this.moveSpeed * Math.sin(this.direction);
    
  • 5.  Designate instances of the Shoot class to call the remove function if the shots stray far outside the bounds of the game screen by entering the code in Listing 6-18 inside the enterframe event listener, below what you just added. We could use 0 for the minimum allowed values of X and Y before the remove function is called, but using –this.width and –this.height ensures the shots don’t disappear off the screen unnaturally. Do not worry about defining what the remove function does just yet.

    清单 6-18。 调用remove函数

    if(this.y > 320 || this.x > 320 || this.x < -this.width || this.y < -this.height){
                this.remove();
    }
    
  • 6.  Inside the definition of the remove function, under the definition of the initialize function, specify what should happen when the remove function is called by entering the code in Listing 6-19. The delete command removes a given instance of the Shoot class from memory. In a very long game, if this is not specified it could bog down the system.

    清单 6-19。remove功能

    game.rootScene.removeChild(this);
    delete this;
    
  • 7.  Add instances of the Shoot class to rootScene on creation by adding the code in Listing 6-20 to the initialize function, after the event listener.

    清单 6-20。Shoot添加到rootScene

    game.rootScene.addChild(this);
    

创建 PlayerShoot 类并让船射击

我们创建了通用的Shoot类,但是现在我们需要为玩家的船发射的弹药创建一个类,并在船发射时创建它的实例。执行以下操作来创建该类:

  1. In the //Class Definitions section, create the PlayerShoot class and its initialize function by entering the code in Listing 6-21.

    清单 6-21。 创建 PlayerShoot 类

    // PlayerShoot class
    var PlayerShoot = enchant.Class.create(Shoot, { // Succeeds bullet class
        initialize: function(x, y){
        }
    });
    
  2. Create the PlayerShoot class as an instance of the Shoot class, specifying 0 as the direction, by inserting the code in Listing 6-22 into the initialize function. Remember the unit circle? The value of 0 is equal to a direction facing the right side of the screen. Because the ship is on the left side of the screen, bullets fired will head toward the right.

    清单 6-22。 创建Shoot类的实例

    Shoot.call(this, x, y, 0);
    
  3. We now need make the ship fire instances of the PlayerShoot class. Go back to the Player class definition and create an enterframe event listener inside the initialize function by entering the code in Listing 6-23 right above the line game.rootScene.addChild(this);.

    清单 6-23。 Enterframe 事件监听器

    this.addEventListener('enterframe', function(){
    });
    
  4. Inside this event listener, add the code in Listing 6-24 to create an if statement to be executed once every three frames and to be executed if the game is being touched.

    清单 6-24。 If 语句控制出手

    if(game.touched && game.frame % 3 === 0){
    }
    
  5. Inside the if statement, create a new instance of the PlayerShoot class by entering the code in Listing 6-25.

    清单 6-25。 创建一个PlayerShoot实例

    var s = new PlayerShoot(this.x, this.y);
    
  6. 单击运行。当你点击屏幕时,你的船会发射弹药穿过屏幕。

如果您在本节中遇到问题,您可以在http://code.9leap.net/codes/show/29907找到一个完整的工作代码示例。

创造敌人

我们的船现在可以发射子弹,但现在我们需要为船创造一些可以射击的东西。执行以下操作为敌人创建一个职业并将他们添加到游戏中:

  1. Create the basic Enemy class definition with an initialize and remove function by adding the code in Listing 6-26 to the //Class Definitions section, below the Player class definition.

    清单 6-26。 基础Enemy

    //Enemy class
    var Enemy = enchant.Class.create(enchant.Sprite, {
        initialize: function(x, y){
        },
        remove: function(){
        }
    });
    
  2. Make the Enemy class a 16x16 instance of the Sprite class and assign the frame, x, and y variables by entering Listing 6-27 into the initialize function.

    清单 6-27。 制作 Sprite 实例并赋值变量

    enchant.Sprite.call(this, 16, 16);
    this.image = game.assets['graphic.png'];
    this.x = x;
    this.y = y;
    this.frame = 3;
    
  3. Specify the direction of movement for enemies to be to the left in terms of the unit circle (Math.PI), and create a variable for movement speed by entering the code in Listing 6-28 below what you just entered.

    清单 6-28。 为方向和运动创建变量

    this.direction = 0;
    this.moveSpeed = 3;
    
  4. Under what you just entered, create an event listener to move the enemy using the variables you just created by entering the code in Listing 6-29.

    清单 6-29。 移动敌人

    // Define enemy movement
    this.addEventListener('enterframe', function(){
            this.x -= this.moveSpeed * Math.cos(this.direction);
            this.y += this.moveSpeed * Math.sin(this.direction);
    });
    
  5. Make the Enemy call the remove function (which we’ll define soon) if it is outside the dimensions of the screen by entering the code in Listing 6-30 directly under the line this.y += this.moveSpeed * Math.sin(this.direction);.

    清单 6-30。 除去屏幕外的敌人若

    // Disappear when outside of screen
    if(this.y > 320 || this.x > 320 || this.x < -this.width || this.y < -this.height){
            this.remove();
    }
    
  6. Finally, have the Enemy add itself to rootScene when it is created by entering the code in Listing 6-31 into the initialize function, under the event listener you just added.

    清单 6-31。 给 rootScene 添加敌人

    game.rootScene.addChild(this);
    
  7. Define the remove function by entering Listing 6-32 into the remove function. This will remove the enemy from rootScene and delete it from memory. Also, it will remove the enemy from an array we’re going to create to keep track of enemies. (Hence the delete enemies[this.key];, which will be explained soon.)

    清单 6-32。 清除功能

    game.rootScene.removeChild(this);
    delete enemies[this.key];
    delete this;
    
  8. In the //In-Game Variables and Properties section, create an array to keep track of enemies by entering the code in Listing 6-33.

    清单 6-33。 制造敌阵

    enemies = [];
    
  9. Under the new array, create an enterframe event listener for the game to create enemies randomly by entering the code in Listing 6-34. We create a variable inside the Enemy called enemy.key and assign the game’s current frame to it because this gives us something to keep track of the enemies with. If we do not do this, we would not be able to reference a specific enemy later on, which is needed when the enemies stray off screen or are hit with ammunition from the ship. Enemies have approximately a 1 in 10 chance of being created because of if(Math.random()*100 < 10) and, if created, are placed randomly on the y-axis.

    清单 6-34。 在游戏中制造敌人

    game.rootScene.addEventListener('enterframe', function(){
        if(Math.random()*100 < 10){
            var y = Math.random() * 320;
            var enemy = new Enemy(320, y);
            enemy.key = game.frame;
            enemies[game.frame] = enemy;
        }
    });
    
  10. Update the game’s scoreLabel every frame by entering the code in Listing 6-35 underneath the if statement, but still inside the event listener.

***清单 6-35。*** 每帧更新游戏分数

```js
scoreLabel.score = game.score;
```
  1. 点击运行。敌人被创造出来并飞过屏幕。然而,当船上的子弹击中他们时,什么也没有发生。
  2. Make the ship’s ammunition destroy enemies with an event listener by entering the code in Listing 6-36 into the PlayerShoot class definition, under Shoot.call(this, x, y, 0);. The way this for loop is constructed causes the program to go through every single member of the enemies array, checking to see if the given bullet is in contact with it. If so, both the bullet and the enemy in the array are removed, and the player’s score is increased by 100.
***清单 6-36。*** 制伏敌人

```js
this.addEventListener('enterframe', function(){
    // Judges whether or not player's bullets have hit enemy
    for(var i in enemies){
        if(enemies[i].intersect(this)){
            // Eliminates enemy if hit
            this.remove();
            enemies[i].remove();
            //Adds to score
            game.score += 100;
        }
    }
});
```
  1. 单击运行。你现在可以通过射击来消灭敌人。如果您在本节中遇到问题,可以在http://code.9leap.net/codes/show/29929找到一个工作代码示例。

创建敌人射击类并让敌人射击

在这一点上,没有办法输掉比赛,这不是很引人注目。让我们创建一个敌人弹药的职业,让敌人向飞船开火,通过以下方式增加难度:

  1. Create the enemyShoot class by adding the code in Listing 6-37 in the //Class Definitions section. Create it as an instance of the Shoot class, with the direction set as Math.PI, as that faces left in the unit circle.

    清单 6-37。 排敌班

    // Class for enemy bullets
    var EnemyShoot = enchant.Class.create(Shoot, { // Succeeds bullet class
        initialize: function(x, y){
            Shoot.call(this, x, y, Math.PI);
        }
    });
    
  2. Add an enterframe event listener inside the initialize function by adding the code in Listing 6-38 under the line that begins with Shoot.call. This event listener should contain an if statement specifying that the game should end if the center of player and the center of a given enemy bullet is 8 pixels or less at any given time.

    清单 6-38。 指定playerShootplayer 之间的点击次数

    this.addEventListener('enterframe', function(){
        if(player.within(this, 8)){
            game.end(game.score, "SCORE: " + game.score);
        }
    });
    
  3. Make the Enemy class create instances of the enemyShoot class every 10 frames by changing the if statement inside the Enemy enterframe event listener to match what is shown in Listing 6-39. The variable age can be called on any Entity and gives the number of frames the Entity has been alive.

    清单 6-39。 制敌射击

    // Disappear when outside of screen
    if(this.y > 320 || this.x > 320 || this.x < -this.width || this.y < -this.height){
        this.remove();
    }else if(this.age % 10 === 0){ // Fire every 10 frames
        var s = new EnemyShoot(this.x, this.y);
    }
    
  4. 点击运行。敌人现在开始反击。

如果您在本节中遇到问题,可以在http://code.9leap.net/codes/show/30105找到一个工作代码示例。

使敌人沿弧线移动

敌人现在沿直线移动。这使得玩游戏相当直接,但有点简单。让我们让敌人以弧线移动,让游戏更有趣。我们将通过创建一个变量来指定方向应该向上运动还是向下运动(theta ),这取决于敌人在哪里被创建,然后在每一帧稍微改变方向。为此,请执行以下操作:

  1. Each enemy needs to have a variable that will be used to change its direction. Create a variable (theta) that is passed as an argument when an Enemy is created by changing the opening line of the initialize function to match the code in Listing 6-40.

    清单 6-40。 添加了theta变量

    initialize: function(x, y, theta){
    
  2. Although direction is specified in terms of the unit circle, it’s easier to work in degrees for specific amounts other than 0 or Math.PI. We’ll be passing a value in degrees to theta, so convert it to radians by entering the code in Listing 6-41 right before the line that reads this.direction = 0;.

    清单 6-41。theta转换成弧度

    this.theta = theta * Math.PI / 180;
    
  3. Inside the enterframe event listener of the Enemy class, make theta change the direction of the Enemy every frame by entering the code in Listing 6-42 directly under the line that reads this.addEventListener('enterframe', function(){.

    清单 6-42。 递增Enemy方向

    this.direction += this.theta;
    
  4. Now enemies can accept a value for theta, and this will change the direction of the Enemy every frame, but we need to specify how the enemies are created to really use this. To accomplish this, change the if statement inside the game’s rootScene enterframe event listener to match the code in Listing 6-43. If the Enemy is created in the upper half of the screen, the enemy will arc upwards (direction angle will increase by 1 each frame). If it is created in the lower half of the screen, it will arc downwards (direction angle will decrease by 1 each frame).

    清单 6-43。 制造敌人那道弧线

    if(rand(100) < 10){
            // Make enemies appear randomly
            var y = rand(320);
            if (y < 160) {
                    theta = 1;
            } else {
                    theta = -1;
            }
            var enemy = new Enemy(320, y, theta);
            enemy.key = game.frame;
            enemies[game.frame] = enemy;
    }
    
  5. Click Run. Enemies move in an arc, making the game more compelling. Your game should appear as it does in Figure 6-3.

    9781430247432_Fig06-03.jpg

    图 6-3 。简单的射击游戏

如果您在本节中遇到问题,可以在http://code.9leap.net/codes/show/30564找到一个工作代码示例。

强化游戏

目前,我们的游戏在目前的状态下是一个工作游戏。我们可以把它留在这里,转到另一个游戏,但让我们研究一些方法,通过添加一些功能来使游戏更引人注目。

我们将对原来的游戏做一些补充,并在接下来的部分解释如何做。

爆炸

先来加点爆款。目前,当敌舰被击落时,它们就消失了。让我们通过在敌舰被击中时引起爆炸来增加一点刺激。

  1. Download the explosion (Figure 6-4) sprite sheet from http://enchantjs.com/assets/img/effect0.gif and add it to your project. We’ll use this sprite sheet for our explosion.

    9781430247432_Fig06-04.jpg

    图 6-4 。爆炸效果图

  2. Create a basic class, called Blast, as an extension of the Sprite class by adding the code in Listing 6-44 into the //Class Definitions section. The class should have an initialize and remove function.

    清单 6-44。 爆炸类

    // Class for explosions
    var Blast = enchant.Class.create(enchant.Sprite, {
        initialize: function(x, y){
        },
        remove: function(){
        }
    });
    
  3. Inside the initialize function, create the Blast class as a 16x16 instance of the Sprite class, and pass the x and y arguments to the local variables x and y by entering the code in Listing 6-45.

    清单 6-45。 在爆炸中分配变量

    enchant.Sprite.call(this,16,16);
    this.x = x;
    this.y = y;
    
  4. Jump down to the window.onload function and add a preload statement under game.preload('graphic.png'); to add the sprite sheet of the explosion by entering the code in Listing 6-46.

    清单 6-46。 预载爆炸图像

    game.preload('effect0.gif');
    
  5. Back in the initialize definition of the Blast class, under this.y = y;, add a statement to use effect0.gif as the image for the explosion by adding the code in Listing 6-47.

    清单 6-47。 指定effect0.gifBlast图像

    this.image = game.assets['effect0.gif'];
    
  6. Specify the frame to start at 0, and specify a duration of 20 frames by entering Listing 6-48 on the next line. We will use the duration soon to draw out the animation over a specific amount of time.

    清单 6-48。 指定起始帧和持续时间

    this.frame = 0;
    this.duration = 20;
    
  7. Below that, create an enterframe event listener by entering the code in Listing 6-49. We’ll use this event listener to control the frame of the explosion.

    清单 6-49。 事件监听器为Blast

    this.addEventListener('enterframe', function(){
    }
    
  8. Inside the event listener, create a statement to set the frame of the explosion to go from 0 to 4 over a period of 20 frames by entering the code in Listing 6-50. This is accomplished with an algorithm. This algorithm first takes the current number of frames the explosion has been alive for (this.age) and divides it by the desired duration of the animation (this.duration) to get a fraction representing how far through the animation sequence the explosion should be. This is multiplied by 5 because the total sequence, shown in the sprite sheet, is 5 frames long. At this point, the result most likely has a decimal value (such as 4.42), so it is rounded down with Math.floor, and the result is what is assigned to the current frame of the explosion. The result is that the explosion progresses smoothly over 20 frames, and this value can be changed simply by editing the value of duration from 20 to something else.

    清单 6-50。frame 赋值

    // Explosion animation
    this.frame = Math.floor(this.age/this.duration * 5);
    
  9. Beneath that, but still in the event listener, enter the code in Listing 6-51 to create an if statement that calls the remove function if the explosion has been alive for the desired duration. Note that if there is only one statement after the if statement, curly braces ({}) are not required.

    清单 6-51。 调用remove函数

    if(this.age == this.duration) this.remove();
    
  10. Under the if statement, outside of the event listener, but still inside the initialize function, add the blast to rootScene by entering the code in Listing 6-52.

***清单 6-52。*** 给`rootScene` 添加冲击波

```js
game.rootScene.addChild(this);
```
  1. Make the remove function remove the blast from rootScene by entering the code in Listing 6-53 into the definition of the remove function.
***清单 6-53。*** 清除根景爆炸

```js
game.rootScene.removeChild(this);
```
  1. Make playerShoot create an instance of the Blast class if it hits an enemy by rewriting the definition of playerShoot to match the code in Listing 6-54. The line you should add is in bold type.
***清单 6-54。***`playerShoot`创建`Blast` 的实例

```js
// PlayerShoot class
var PlayerShoot = enchant.Class.create(Shoot, { // Succeeds bullet class
    initialize: function(x, y){
        Shoot.call(this, x, y, 0);
        this.addEventListener('enterframe', function(){
                // Judges whether or not player's bullets have hit enemy
                for(var i in enemies){
                    if(enemies[i].intersect(this)){
                    //Start Explosion
                    var blast = new Blast(enemies[i].x,enemies[i].y);
                        // Eliminates enemy if hit
                        this.remove();
                        enemies[i].remove();
                        //Adds to score
                        game.score += 100;
                        }
                }
                });
    }
});
```
  1. 单击运行。尝试玩游戏,看看当敌人被玩家船上的子弹击中时,爆炸是如何出现的。如果你做的一切都正确,游戏应该会出现在图 6-5 中。

9781430247432_Fig06-05.jpg

图 6-5 。具有爆炸功能的街机射击游戏

如果您在本节中遇到问题,您可以在http://code.9leap.net/codes/show/30633找到一个完整的工作代码示例。

添加滚动背景

现在,我们已经加入了爆炸,事情开始看起来有点好,但我们仍然有一段路要走。为了增加准现实主义的元素,让我们为我们的游戏创建一个滚动背景。

首先,我们需要一个正好是屏幕两倍宽的背景图像。图像的左右两边应该完全一样,最左边或最右边都没有星星。我们一会儿就知道为什么了。

我们一次向左滚动一个像素,当我们到达终点时,我们循环回到起点,在整个游戏中不断重复这个过程。这就是为什么我们希望背景图片的左右两边是一样的。如果玩家注意到背景中的突然变化,看起来会很不自然。我们将使用图 6-6 中所示的图像。

9781430247432_Fig06-06.jpg

图 6-6 。png 图形

通过创建Background类并加入这个循环动作,我们创造了一个无休止滚动的背景效果。执行以下操作来创建Background类并实现它:

  • 14.  In the //Class Definitions section, create a class for the background as an extension of the Sprite class by entering the code in Listing 6-55. The only method needed is the initialize method, as we never remove it from rootScene.

    清单 6-55。 Background

    // Background class
    var Background = enchant.Class.create(enchant.Sprite, {
        initialize: function(){
        }
    });
    
  • 15.  Inside the initialize function, create Background as a 640x320 Sprite by entering in Listing 6-56. This dimension is just as tall and twice as wide as the game screen.

    清单 6-56。 创建为精灵的实例

    enchant.Sprite.call(this,640,320);
    
  • 16.转到http://enchantjs.com/?p=731并下载图像文件bg.png,用作背景。

  • 17.将文件上传到您在code.9leap.net中的项目。

  • 18.  Inside the window.onload function, under the //Game Properties section, add Listing 6-57 to preload bg.png.

    清单 6-57。 预加载背景图像

    game.preload('bg.png');
    
  • 19.  Back inside the initialize function of the Background class, add Listing 6-58 to the variable declarations to position the background and assign bg.png to be used as the background image.

    清单 6-58。 定位和图像变量

    this.x = 0;
    this.y = 0;
    this.image = game.assets['bg.png'];
    
  • 20.  Below that, but still inside the initialize function, add an enterframe event listener to move the background to the left by one pixel every frame by entering in the code in Listing 6-59.

    清单 6-59。 移动Background每一帧

    this.addEventListener('enterframe', function(){
            this.x--;
    });
    
  • 21.  Inside the event listener, underneath this.x--;, write a statement to reset the position of Background if it has been scrolled all the way over by entering the code in Listing 6-60. We know if the image has been scrolled all the way over if its x position is -320 or less.

    清单 6-60。 重置背景位置

    if(this.x<=-320) this.x=0;
    
  • 22.  Under the event listener, but still inside the initialize function, add Background to rootScene by entering the code in Listing 6-61.

    清单 6-61。 给 rootScene 添加背景

    game.rootScene.addChild(this);
    
  • 23.  Finally, in the game.onload function, replace the line that reads game.rootScene.backgroundColor = 'black'; with the code in Listing 6-62 to create an instance of Background.

    清单 6-62。 创建Background 的实例

    background = new Background();
    
  • 24.单击运行。游戏应该出现在图 6-7 中。

9781430247432_Fig06-07.jpg

图 6-7 。背景滚动的街机射击游戏

如果您在本节中遇到问题,可以在http://code.9leap.net/codes/show/30704找到一个工作代码示例。

添加生命量表

目前,玩家只需一击就会死亡。如果他们在至少三次击中后倒下,似乎会公平得多。考虑到这一点,让我们添加一些东西,让玩家有多条命,并允许他们在死亡前承受几次打击。为此,请执行以下操作:

  1. In the game.onloadfunction, under the //In-Game Variables and Properties section, add a variable as part of the Core object (game) to keep track of a player’s life by entering the code in Listing 6-63.

    清单 6-63。 创建一个生命变量

    game.life = 3;
    

    我们现在已经初始化了一个代表玩家剩余生命数的变量。因为我们在game.onload内部这样做,并使用game.life作为变量名(使用game.前缀使其成为这里称为gameCore对象的一部分),所以它可以在游戏内部的任何地方被引用。

  2. Rewrite the EnemyShoot class to reduce the amount of life the player has by 1 if hit by an enemy bullet by making the class match the code in Listing 6-64. You should erase the line that reads game.end(game.score, "SCORE: " + game.score); and replace it with the code in bold type.

    清单 6-64。 命中时减少生命

    // Class for enemy bullets
    var EnemyShoot = enchant.Class.create(Shoot, { // Succeeds bullet class
        initialize: function(x, y){
            Shoot.call(this, x, y, Math.PI);
            this.addEventListener('enterframe', function(){
                if(player.within(this, 8)){     // Bullet has hit player
                        game.life--;
                    }
            });
        }
    });
    
  3. Under game.life--;, but still inside the if statement, write another if statement to end the game if the player’s life is less than or equal to 0 by entering the code in Listing 6-65.

    清单 6-65。 结束游戏如果没有生命

    if(game.life<=0)
    game.end(game.score, "SCORE: " + game.score);
    

    我们已经重写了游戏,当玩家被击中时生命值减少 1,如果生命值为 0,游戏就结束了。我们可以创建一个类来显示生活,但是不需要在屏幕上添加太多内容,所以在这种情况下,简单地在game.onload中创建指示器会更有效。

    我们将使用作为ui.enchant.js插件的一部分提供的MutableText类来制作指示器。MutableText类与Label类非常相似,但是它不使用计算机中已经安装的字体在屏幕上创建文本,而是使用 sprite 表中文本字符的图像,很像Sprite

    image 注意ScoreLabel类是MutableText类的扩展,行为类似。

    如果我们在这里使用常规的Label类,结果看起来会很便宜,因为将使用系统字体。通过使用MutableText,你可以使用更好看的字体,并且因为使用图像而不是系统字体,你可以确保字母看起来总是一样的,不管游戏运行在什么样的浏览器或操作系统上。

  4. Within the game.onload function, below the line that reads player = new Player(0, 152);, create a new instance of MutableText by entering the code in bold type from Listing 6-66. The first argument (8), specifies the X position of the upper-left corner of the new instance, the second argument (320 – 32) specifies the Y position, the third (game.width) specifies the width of the label, and the fourth specifies the text that should be shown.

    清单 6-66。 创造了lifeLabel

    game.onload = function() {
        //In-Game Variables and Properties
        background = new Background();
        game.life = 3;
    
        scoreLabel = new ScoreLabel(8, 8);
        game.rootScene.addChild(scoreLabel);
        player = new Player(0, 152);
        enemies = [];
        // Display life
        lifeLabel = new MutableText(8, 320 - 32, game.width, "");
    
  5. Beneath the line that reads lifeLabel = new MutableText(8, 320 – 32, game.width, "");, but still inside the game.onload function, create an event listener to update the text of lifeLabel with a number of 0s equal to however many lives the player has left by entering the code in Listing 6-67.

    清单 6-67。 显示生命数量

    lifeLabel.addEventListener('enterframe', function(){
        this.text = "LIFE " + "OOOOOOOOO".substring(0, game.life);
    });
    

    因为我们在一个enterframe事件侦听器中定义了lifeLabel的文本,所以生命量表将在每一帧中更新。

    我们来看看"OOOOOOOOO".substring(0, game.life);部分。这个“O”代表游戏中的单身生活。"OOOOOOOOO"是一个 JavaScript 字符串对象,所以我们可以直接调用应用于该字符串的方法。我们可以通过使用substring()方法从一个字符串的开头到指定的点提取文本。

    应该注意的是,substring()方法的第二个参数表示子字符串应该从中提取字符的点。请记住,字符串和数组中的位置引用以 0 开头。这意味着如果game.life等于 3,子串用"OOOOOOOOO".substring(0, game.life);处理,子串将在位置 0、位置 1 和位置 2 返回 0,但在到达第三个位置之前会停止。

    这也是为什么游戏以三条命开头,在游戏中用三个 0 来代表("LIFE 000")。随着生命的减少,它会显示"LIFE OO"然后是"LIFE O."

    正如你所看到的,enchant.js 允许你轻松地创建游戏指示器,而不必创建一个全新的类。

  6. Finally, under the event listener, add the lifeLabel to rootScene by entering in the code in Listing 6-68.

    清单 6-68。 给 rootScene 添加 life label

    game.rootScene.addChild(lifeLabel);
    
  7. 单击运行。寿命计将出现在图 6-8 的中。

9781430247432_Fig06-08.jpg

图 6-8 。带生命计的街机射击游戏

如果您在本节中遇到问题,您可以在http://code.9leap.net/codes/show/30801找到一个完整的工作代码示例。

结论

希望你能看到最终拍摄程序的总体流程与我们在本章前面看到的简单原型没有太大的不同。事实上,Background类和许多其他类与我们在本章开始时检查的原型完全相同。

正如我们看到的第五章一样,在重击机器人的例子中,通过一点一点地重写和改进原型,一个成熟而复杂的游戏可以被开发出来。

在这一章中,我们探索了射击游戏的开发,看看如何为子弹、敌人和玩家创建类,以及如何处理子弹和敌人或玩家飞船之间的碰撞检测。然后,我们研究了可以用来改进基本游戏的更高级的概念,包括爆炸、生命标尺等等。

在下一章,我们将看看如何在code.9leap.net之外创建独立游戏,并研究如何使用 3D 来创建带有gl.enchant.js插件的游戏。`

七、创建一个独立的三维游戏

我们在第六章中创造的街机射击游戏就是一个二维游戏的例子。标准 enchant.js 游戏中的精灵、标签甚至场景都存在于一个平面上,就像游戏中只存在 x 和 y 轴一样。尽管我们可以将实体堆叠在彼此之上,但这只是为了操纵可见性。没有可以控制的“深度”。

Enchant.js 附带了一个名为 gl.enchant.js 的插件,可以创建三维游戏。在本章中,我们将探索如何使用 gl.enchant.js 来创建我们之前创建的打地鼠游戏的 3d 版本。在路上,我们讨论了 gl.enchant.js 的基本主题,即 3-D 场景、3-D 相机和 3-D 精灵。

然而,在我们进入 3d 游戏之前,让我们再来看看如何在code.9leap.net之外创建自己的 enchant.js 游戏,并简单探索一下从头开始创建一个 enchant.js 游戏需要什么。在撰写本书时,code.9leap.net支持 enchant.js 插件的大部分功能,但不是全部。我们需要在 3d 版的打地鼠游戏中使用其中一个函数。

单机游戏

到目前为止,为了简单起见,实际上我们所有的代码示例和指令都推荐使用code.9leap.net。这是一种跟踪代码和动态开发的简单方法。也就是说,为了让你的游戏开发更上一层楼,如果需要的话,了解如何从头开始组装一个 enchant.js 游戏是很重要的。

你可能需要或想要创建一个单机游戏的原因有很多。首先,因为code.9leap.net处于测试阶段,某些文件格式不支持上传到项目中。目前,3d 模型文件不能上传到code.9leap.net项目中。这就是为什么当你制作一个 3D 游戏时,它不能写在code.9leap.net里面。创建一个单机游戏而不是在code.9leap.net上运行的另一个原因是在你自己的网站上运行它。

从头开始创建游戏包括在您的计算机上本地准备游戏所需的所有文件。虽然游戏创作和测试可以在标准的本地文件上完成(换句话说,双击 index.html 文件在浏览器中打开它),但一些功能,如从文件中加载 3d 数据,需要浏览器有证据表明该文件正被 web 服务器访问。这是由于被称为同源策略 的安全限制。

同源策略

同源策略是一种安全措施,用于限制脚本加载某些文件的能力。在 enchant.js 游戏中,通常受策略影响的文件是那些包含声音或 3d 数据的文件。但是,最好将除图像之外的所有文件都视为受此策略限制。根据您的浏览器,可以使用特定的命令或设置来覆盖同源策略。

在 Chrome 中,你可以用一个特定的参数打开 Chrome 来覆盖同源策略。打开终端(Mac)或命令提示符(Windows)并键入以下内容:

  • Mac : open /applications/Google\ Chrome.app --args --allow-file-access-from-files
  • Windows??:

火狐浏览器

在 Firefox 中,您可以通过设置来覆盖同源策略:

  1. 在地址栏中输入about:config。如果 Firefox 警告您更改这些设置可能会影响浏览器的性能,请单击以接受警告消息。
  2. 在地址栏下出现的搜索框中,键入origin
  3. 双击 security . file uri . strict _ origin _ policy 将值设置为false

旅行队

Safari 还支持禁用同源策略:

  1. 进入 Safari 偏好设置(Safari image偏好设置(Mac),文件image偏好设置(Windows)),点击高级选项卡,勾选菜单栏中显示开发菜单的复选框。
  2. 关闭“设置”窗口,点按“开发”菜单,然后选取“停用本地文件限制”。

微软公司出品的 web 浏览器

我们强烈建议不要在 Internet Explorer 中开发游戏,因为 HTML5 的一些功能不受支持。

虽然禁用同源策略是一种快速解决方案,但我们建议为较大的开发项目安装 web 服务器。这将有助于更容易地过渡到最终版本。

本地 web 服务器

本地网络服务器 允许组成一个网站的文件以类似于从互联网上的标准服务器访问文件的方式被访问。网上有各种免费的服务器模拟器。仿真器是硬件的虚拟版本。您可以下载并运行带有仿真软件的虚拟服务器,而不是让物理服务器运行网站。这使得在网站上工作更容易,因为你不需要上传你的文件到互联网上的服务器来测试它。我们在http://sourceforge.net/projects/xampp/向 Windows、Mac 或 Linux 推荐 XAMPP,在http://www.mamp.info/推荐 MAMP,后者仅适用于 Mac。这些模拟器是免费的,很容易获得。

在这个练习中,我们使用 XAMPP,但是如果你熟悉的话,也可以使用其他的网络服务器。

设置

首先,从 SourceForge 的项目页面下载并安装 XAMPP,如图 7-1 所示。

9781430247432_Fig07-01.jpg

图 7-1 。SourceForge 项目 XAMPP 官方页面(【http://sourceforge.net/projects/xampp/】??

如果您在安装时遇到任何问题,或者如果您想了解更多关于 XAMPP 的技术细节,您可以在http://www.apachefriends.org/en/xampp-macosx.html(Mac)或http://www.apachefriends.org/en/xampp-windows.html(Windows)找到官方文档和安装说明。安装后打开 XAMPP,确保 Apache 旁边的指示灯是绿色的,或者 Apache 服务已经启动,然后再继续。

一旦安装了 XAMPP(或另一个 web 服务器包)并且它当前正在运行,导航到 web 服务器的根文件夹。如果你在 Mac 上使用 XAMPP,你可以在/Applications/XAMPP/htdocs/ (Mac)或C:\xampp\htdocs (Windows)访问它。这是你放置游戏所有文件的地方。例如,如果你把一个名为index.html的文件放入这个htdocs文件夹,你就可以通过浏览器访问http://localhost/index.html来查看它。

3d 游戏

2d 游戏基于在平面空间移动的实体,而 3d 游戏基于深度。对于玩家来说,这意味着有更多的东西可以看,有更多的角度可以看。游戏空间存在于三维空间中:长度、宽度和高度(x、y 和 z)。这种环境支持从任何角度查看其中的对象。然而,对于程序员来说,这意味着游戏创建过程更加复杂。

虽然 3-D 游戏的好处包括创造一个更加身临其境的环境的可能性(换句话说,让玩家通过一个可以移动和环视游戏世界的虚拟角色的眼睛来看),3-D 也可以用来创建简单的游戏,并通过视觉升级来增强。具体来说,通过在游戏过程中改变相机的位置,你可以创建一个深度级别,与在 enchant.js 中创建的大多数游戏相比,这在视觉上令人印象深刻。

当你创建一个三维的游戏时,你需要考虑比二维游戏更多的变量。许多发生在 3d 游戏中的事件不能使用与 2d 游戏中相同的函数来创建。因此,在 enchant.js 中有一套完全独立的插件来创建 3d 游戏。

创建一个 3d 的重击机器人游戏

在本章的游戏制作练习中,我们制作了一个之前制作的 3d 版的重击机器人游戏。为此,我们使用 gl.enchant.js 并学习如何实现 GL . enchant . js 3d 游戏的各种元素,包括 3d 场景、3d 摄像机和 3d 精灵。图 7-2 显示了游戏完成后的样子。

9781430247432_Fig07-02.jpg

图 7-2 。3d 重击机器人

我们一步一步地完成这个游戏的创作。我们开始吧!

  1. 转到http://enchantjs.com/?p=731并下载第七章的的 WhackADroid3D-initial zip 文件,并将其解压缩到您的本地 web 服务器正在使用的目录中。另一个文件 WhackADroid3D-Steps 位于 WhackADroid3D-initial zip 文件下的页面中,该文件包含本章中每个主要步骤之后显示的全部源代码。与命名文件夹相对应的步骤(换句话说,步骤 1->/步骤 1/)在本章的代码列表中有标记。

  2. Open index.html in a text editor and make sure the code matches the code in Listing 7-1.

    清单 7-1。【index.html】包含插件引用

    <html>
    <head>
    <title>Whack-A-Mole 3D</title>
    <meta http-equiv="cache-control" content="no-cache">
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <script type="text/javascript" src="lib/glMatrix-1.3.7.min.js"></script>
    <script type="text/javascript" src="lib/enchant.js"></script>
    <script type="text/javascript" src="lib/nineleap.enchant.js"></script>
    <script type="text/javascript" src="lib/gl.enchant.js"></script>
    <script type="text/javascript" src="lib/collada.gl.enchant.js"></script>
    <script type="text/javascript" src="lib/primitive.gl.enchant.js"></script>
    <script type="text/javascript" src="main.js"></script>
    <style type="text/css">
        body {
            margin: 0;
        }
    </style>
    </head>
    
    <body>
    </body>
    
    </html>
    

下面的列表显示了加载的不同插件,并对每个插件进行了解释:

  • glMatrix-1.3.7.min.js :一个开源的 JavaScript 库,包含了用 gl.enchant.js 执行各种操作所必需的数学计算。

  • nine leap . enchant . js:enchant . js 的 9leap 插件,我们在这里用它来显示游戏开始和结束画面。

  • GL . enchant . js:enchant . js 的核心 WebGL 使能插件*

  • collada.gl.enchant.js :允许在 collada(.dae)格式加载到 enchant.js 游戏中。

  • primitive.gl.enchant.js :包含几个预先制作的 3d 对象,可以在 3d 游戏中使用。

  • WebGL 是一个 JavaScript API(或接口),它允许网页中的元素,特别是 canvas 元素——它是用于绘制图形的 HTML5 网页的一部分——与计算机的显卡通信,以通过硬件加速来渲染这些图形。enchant.js 插件,允许在 enchant.js 游戏中使用 WebGL,gl.enchant.js,附随 enchant.js 包,主要用于用 enchant . js 制作 3d 游戏。

如果这些插件中的任何一个缺失,你将会在游戏中遇到问题,所以在继续之前确保它们都在代码中。

创建场景 3D 和灯光

就像普通的 enchant.js 游戏一样,3d enchant . js 游戏也需要一个场景,在这个场景中所有的事情都会发生。然而,这些场景被归类为“场景 3D ”,以表明它们在代码中的 3d 性质。当你创建一个新的场景 3D 时,你是在告诉 enchant.js 初始化并创建一个三维空间,该空间将支持向其中添加对象。

  • 1.  Open main.js, which should be empty, and type in the code in Listing 7-2 to set up the basics of the game. Notice how there is nothing new here just yet.

    清单 7-2。 三维游戏的框架

    enchant();
    var game;
    
    window.onload = function(){
        game = new Core(320, 320);
        game.fps = 60;
    
        game.onload = function(){
        };
    
        game.start();
    };
    
  • 2.  Inside the game.onload function, create a new Scene3D by typing in the code in Listing 7-3.

    清单 7-3。 创建场景 3D

    scene = new Scene3D();
    

    Scene3D创建一个维度空间,如图图 7-3 所示。

    9781430247432_Fig07-03.jpg

    图 7-3 。场景 3D

三维空间中的点被指定在一组括号(x,y,z)内的 x,y 和 z 轴上。如果从 x = 0,y = 0,z = 10 的某一点看三维空间中的原点(x = 0,y = 0,z = 0),x 轴将从左(-x)到右(+x),y 轴将从下(-y)到上(+y),z 轴将从屏幕内部(-z)到屏幕外部,朝向您(+z)。

这就是所谓的右手系统。如果你伸出拇指和前两个手指,每个手指与其他两个手指成 90 度角,想象你的拇指代表 x 轴,你的食指代表 y 轴,你的中指将形成 z 轴。每个手指代表每个轴的正方向。在左手系统中,z 轴的正方向会指向远离你的方向。

需要注意的是,这个坐标系不同于标准 enchant . js 2d 游戏中确定位置的方式。在正常的二维环境中,屏幕的左上角被指定为(0,0),值在该点的右侧和下方增加。在三维系统中,原点或(0,0,0)被认为是环境的中心,如图图 7-3 所示。正因为如此,正值和负值都是常见的。

  • 3.  Under the creation of the new Scene3d, but still inside the game.onload function, type in the code in Listing 7-4 to create a new directional light.

    清单 7-4。 创建平行光

    scene.setDirectionalLight(new DirectionalLight());
    

平行光只是创建一个光波阵列,从场景中的物体反射回来。这些光波中的一部分被物体反射,可以被其他物体(如相机)拾取,从而让玩家可以看到它们。在写这本书的时候,没有参数的平行光是在默认点(0.5,0.5,1)创建的。从这个缺省点,画一条线到场景的原点(0,0,0)。从这条线的方向,产生了虚拟光波,在整个场景中平行于这条线传播,如图 7-4 中的所示。

9781430247432_Fig07-04.jpg

图 7-4 。从右上角开始的方向光源的二维表示

我们也可以通过写(var light = new DirectionalLight(); scene.setDirectionalLight(light);)而不是(scene.setDirectionalLight(new DirectionalLight());)来创建平行光。如果你使用这个var方法,你可以将光源的原点从默认位置(0.5, 0.5, 1)改变到另一个具有directionXdirectionYdirectionZ属性的位置。

创建照相机 3D

尽管我们现在有了场景和平行光,我们仍然需要一个观察场景的视角。如果我们在三维空间中有物体,物体看起来如何将取决于我们从哪里看它。这些视点在 gl.enchant.js 中被指定为Camera3D对象。它们从平行光拾取场景中对象反射的虚拟光波。

  • 1.  Create a Camera3D by typing in the code in Listing 7-5 under the creation of the DirectionalLight, but still inside game.onload.

    清单 7-5。 创建新的摄像机 3D

    camera = new Camera3D();
                           camera.y = 1.1;
                           camera.z = -1.65;
                           camera.centerZ = -10;
                           camera.upVectorZ = 10;
                           camera.upVectorY = 100;
    

摄像机在创建时有一个默认位置(0,0,10),所以我们在图 7-5 的中添加了,摄像机位于(0,1.1,-1.65)。相机对象还具有属性centerXcenterYcenterZ。这些值指定了相机应该“注视”或指向的点。

9781430247432_Fig07-05.jpg

图 7-5 。屏幕上显示一个机器人

如果相机位于一个点,并指向另一个点,相机可以在任何方向旋转,仍然指向指定的坐标。这样想:如果我正倒着看着你,我还在看着你。这就是为什么有“上向矢量”属性。这些属性指定了一个点,相机应该将该点视为“向上”通过改变上矢量,相机(以及相机的视图)可以轴向旋转。

你可以随心所欲地在三维空间中放置物体和摄像机。可以毫无问题地创建多个摄像机。只要相机指向你在三维场景中创建的实体,你就能看到它们。你可能需要调整你的相机来获得你想要的物体的视图,但是最好的方法是练习。

虽然我们现在已经创建了相机,指定了它应该存在的位置,应该指向什么,哪个方向是向上的,但我们仍然需要告诉Scene3D使用它。

  • 2.  Make the scene use our new camera as the view for the player by typing the code in Listing 7-6 beneath camera.upVectorY = 100;, but still inside the game.onload function.

    清单 7-6。 指定场景的摄像机

    scene.setCamera(camera);
    

如果Scene3D内有多个Camera3D对象,可以随时调用此函数(setCamera)在它们之间切换。

创建三维精灵

精灵也作为Sprite3D对象存在于 gl.enchant.js 中,并包含许多类似的属性和功能。在我们的游戏中,我们创建了多个机器人角色,每个角色都是Sprite3D的一个实例。为了避免我们每次需要另一个机器人时手动创建一个新的Sprite3D实例,让我们创建一个包含所有必要变量的Droid类,然后在需要机器人时调用它。为此,请执行以下操作:

  1. In the window.onload function, under game.fps=60;, add a line to preload droid.dae by typing in the code in Listing 7-7. This file was included in the download package earlier and is a 3-D model of the Droid image.

    清单 7-7。 预压 droid.dae

    game.preload('img/droid.dae');
    

    以结尾的文件。dae 采用一种称为 Collada 的格式。Collada 是一种用于保存三维数据的文件格式,大多数主流三维建模软件都支持它。Collada 文件甚至可以用 Adobe Photoshop 打开。

  2. Above the window.onload function, create the Droid class as an extension of the Sprite3D class by typing in the code in Listing 7-8. The initialize function should include arguments for x, y, and z, which represent the position of the Droid on the x, y, and z axes respectively.

    清单 7-8。 创建机器人类

    var Droid = Class.create(Sprite3D, {
        initialize: function(x, y, z) {
        }
    });
    
  3. Inside the initialize function, make Droid an instance of the Sprite3D class and set it to use the Collada file as its model by typing in the code in Listing 7-9. Notice how when working with Sprite3D, the image is not set through the image property, but rather through the set function.

    清单 7-9。 调用 Sprite3D 并设置 Collada 文件

    Sprite3D.call(this);
    this.set(game.assets["img/droid.dae"]);
    
  4. In its current form, the droid.dae file is too large, so scale it to 30 percent by typing in the code in Listing 7-10 on the next line.

    清单 7-10。 缩放机器人

    this.scaleX = 0.3;
    this.scaleY = 0.3;
    this.scaleZ = 0.3;
    
  5. Assign the arguments x, y, and z to their Sprite3D counterparts by typing in the code in Listing 7-11.

    清单 7-11。 分配 x、y、z

    this.x = x;
    this.y = y;
    this.z = z;
    
  6. Inside the game.onload function, under the line scene.setCamera(camera);, add a new Droid to the Scene3D by typing in the code in Listing 7-12.

    清单 7-12。 给场景 3D 添加一个新机器人

    scene.addChild(new Droid(0, 0, -8));
    
  7. Save the file you are working on (main.js), but do not close it. If you are using a virtual web server, open up a web browser other than Internet Explorer and go to the path where index.html is stored (most likely http://localhost/index.html). Your screen should appear as it does in Figure 7-5.

    如果您在本节的代码中遇到任何问题,您可以在本章前面提到的 zip 文件的 Step2 文件夹中找到一个工作代码示例。

    我们现在有了一个合法的、可见的 3d 精灵,但是为了让它更接近真实的游戏,我们需要添加一些交互性。

机器人互动

我们需要一些互动来使这成为一个真正的游戏。执行以下操作,让游戏进入下一步:

  • 1.  Near the top of the code, under var game;, type in the code in Listing 7-13 to create a function to generate random numbers and some variables we’ll use to keep track of the total number of Droids.

    清单 7-13。 创建随机数函数和变量

    function rand(n) {
        return Math.floor(Math.random() * n);
    }
    
    //Variables to keep track of droids
    var maxDroid = 100;
    var hitDroid = 0;
    var combo = 1;
    
  • 2.  Below that, create a ScoreLabel by typing in the code in Listing 7-14. This ScoreLabel contains an add function to easily add and update the score.

    清单 7-14。创建 ScoreLabel

    //Define ScoreLabel
    var ScoreLabel = Class.create(Label, { //Extends Label class
        initialize: function(x, y) {
            enchant.Label.call(this, "SCORE:0"); //Call the Label class constructor
            this.x = x;
            this.y = y;
            this.score = 0;
            this.color = "#ffffff";
        },
        add: function(pts) { //Add the score
            this.score += pts;
            this.text = "SCORE:" + this.score; //Update display
        }
    });
    
  • 3.  In the game.onload function, at the very end, create a new instance of ScoreLabel and add it to rootScene by typing in the code in Listing 7-15.

    清单 7-15。 创建和添加评分标签

    //Display score label
    scoreLabel = new ScoreLabel(5, 5);
    game.rootScene.addChild(scoreLabel);
    
  • 4.  Edit the contents of the Droid class to match Listing 7-16. New code is in boldface. This will prepare the class for some interactivity upgrades in the next section, which will use the a and mode variables shown in the code sample. The tick function will be called every frame, and the hit function will be called whenever the player clicks on the Droid.

    清单 7-16。 重组的机器人类

    //Define Droid class
    var Droid = Class.create(Sprite3D, { //Extend Sprite3D class
        initialize: function(x, y, z) {
            Sprite3D.call(this); //Call the Sprite3D constructor
            this.set(game.assets["img/droid.dae"]);
    
            this.scaleX = 0.3;
            this.scaleY = 0.3;
            this.scaleZ = 0.3;
            this.x = x;
            this.y = y;
            this.z = z;
            this.a = 0.01;
    
            this.addEventListener('enterframe', this.tick); //Define event listener
            this.addEventListener('touchstart', this.hit); //Define event listener for hit
            this.mode = 2; //Set initial Droid mode to wait, and then appear
            this.nextMode = 0;
            this.waitFor = game.rootScene.age + rand(100) + 75;
        },
        tick: function() { //Iterate Droid animation based on mode
        },
    
        hit: function() { //Hit Droid
        }
    });
    

机器人外观行为

在打机器人游戏中,机器人必须有特定的行为方式。首先,在游戏开始之前,它们是在一个洞里被创造出来的。然后,在一段随机的时间后,他们从那个洞里出现。(现在,不要太担心那个洞,而是关注机器人的移动。)它们等待另一段随机的时间,然后藏在它们第一次出现的洞里。在我们的游戏中,当他们在洞外被击中时,他们会飞起来,离开屏幕,一个新的机器人会出现在它的位置上。

从这个描述中,我们可以推断出机器人在任何给定的时刻都会处于五种状态中的一种。表 7-1 显示了这些状态的列表以及驱动该行为的逻辑。表格中显示的模式编号是我们在 Droid 处于该状态时分配给它的编号,以便跟踪它。

表 7-1 。机器人的状态

状态(模式编号) 描述
出现(1) 每帧增加机器人的 y 位置,直到某一点。切换到等待模式,并打算在一段随机时间后进入隐藏模式。
隐藏(2) 每帧减少机器人的 y 位置,直到某个点。切换到等待模式,并打算在一段随机时间后进入显示模式。
等待(3) 一旦规定的时间过去,就进入下一个模式。
停用(4) 什么都不做。这个洞不应该再出现机器人了。
飞走了(5) 每一帧增加机器人的 y 位置(在某一点上,机器人会飞出屏幕)。在一段随机的时间后,重置机器人在洞下的位置,并设置为出现模式。

这些状态将与我们接下来在Droid类声明中创建的switch语句相匹配。

  • 5.  There is already an event listener that calls the tick function inside the Droid class definition. Type in the code in Listing 7-17 inside the tick function to create a system for moving the Droids.

    清单 7-17。 勾选功能

    if (game.rootScene.age === 0) {
        this.waitFor++;
    }
    switch (this.mode) {
        case 0: //Appear from hole
            this.a *= 0.98;
            this.y += this.a;
            if (this.y >= -1.2) {
                this.mode = 2;     //Go to wait mode after fully appearing
                this.nextMode = 1; //After wait mode, go to mode 1 (hide)
                this.waitFor = game.rootScene.age + rand(100) + 10;
            }
            break;
        case 1: //Hide in hole
            this.y -= this.a;
            this.a *= 0.98;
            if (this.y <= -1.5) {
                this.mode = 2;    //After fully hiding, go to wait mode
                this.nextMode = 0; //After waiting, go to mode 0 (appear)
                this.waitFor = game.rootScene.age + rand(100);
                //Reduce number of max droids (to control length of game)
                maxDroid--;
                //If number of max droids has been reached, and the given
                //Droid is instructed to hide, block the hole
                if (maxDroid <= 0) this.mode = 3;
            }
            break;
        case 2: //Wait
            if (this.y < -1.5) this.y += 0.05;
            if (game.rootScene.age > this.waitFor) {
                this.mode = this.nextMode;
                this.a = 0.05;
            }
            break;
        case 3: //Deactivated (no more Droids will appear from this hole)
            break;
        case 4: //Fly
            this.y += this.a;
            this.a *= 1.1;
            if (game.rootScene.age > this.waitFor) {
                this.nextMode = 0;
                this.waitFor = game.rootScene.age+rand(50) + 30;
                this.mode = 2;
                this.y = -3;
                this.a = 0.05;
            }
            break;
    }
    

首先,我们告诉帧在继续增加一帧之前等待一帧,这种情况很少见,在rootScene的第 0 帧上创建了一个 Droid。这最有可能不会发生,但我们想保护我们的基地。

代码中的a变量用于动态移动。在每一帧中,a的值根据 Droid 所处的模式而变化。例如,如果机器人正在飞走,a的值将每帧增加 10%(this.a *= 1.1;),由于机器人的 y 位置每帧增加a ( this.y += this.a;)的值,随着时间的推移,机器人将越来越快地飞起来。这一切发生得非常快,但增加了一个很好的物理模拟效果,看起来更真实。

请注意,在案例 4 ( fly)中,在游戏达到waitFor值之后(换句话说,在机器人飞离屏幕之后),机器人被重新定位回(y = -3;),重新开始这个过程,表现得像一个“新”机器人。

  • 6.  Define the hit function by entering Listing 7-18 into the hit function in the Droid class definition. This function is called whenever the player clicks a Droid.

    清单 7-18。 点击功能

    if (this.y >= -1.4) { //If Droid is over halfway out of hole
        this.mode = 4;  //Enter fly mode
        this.a = 0.02;
        this.waitFor = game.rootScene.age + 30; //Wait for 30 frames (0.5s)
        scoreLabel.add(combo); //Add combo score
        combo++;
        hitDroid++;
    } else {
        //If Droid is whacked before appearing out of hole, reset combo
        combo = 1;
    }
    

如果在游戏过程中没有机器人被玩家击中,机器人会在出现、等待和隐藏模式之间循环。然而,这个hit函数,当在 Droid 类定义的前面从touchstart事件监听器调用时,将逐行导致下面的事情发生:

  • 如果机器人离洞超过一半:

  • 进入fly模式。

  • 设置修改器(a)将 y 值(在fly模式下)最初增加 0.02。

  • 告诉机器人在 30 帧内不要改变模式。

  • 将用户当前的组合(从 1 开始)添加到总得分中。

  • 将当前组合增加 1。

  • hitDroid的值增加 1。

  • 30 帧过去后,将模式更改为 2(等待),并将 Droid 从 Droid 定义中的情况 4 重新定位到(y = -3)。

  • 否则(换句话说,如果玩家试图击中尚未出洞一半的机器人):

  • 将玩家当前的连击重置为 1。

最后,我们将添加到Scene3D中的 Droid 的位置更改为(0,-1.5,-9.2)。这不是完全必要的,但我们这样做是为了感受一下机器人在屏幕上出现和消失的样子。

  • 7.  Finally, reposition the Droid created and added to rootScene by replacing the line that reads scene.addChild(new Droid(0, 0, -8)); in the game.onload function with the line shown in Listing 7-19.

    清单 7-19。 添加不同位置的机器人

    scene.addChild(new Droid(0, -1.5, -9.2));
    

    我们在这里改变机器人的位置,因为现在它四处移动,否则它最初不会出现在摄像机的视野中。

  • 8.保存代码并在浏览器中查看游戏。Droid 应该随着时间的推移而上下弹出,并且当它处于向上状态时应该是可点击的。当机器人被点击时,它应该会飞上屏幕,直到消失,另一个应该会取代它的位置。

如果您在本节中遇到问题,您可以在下载包的 Step3 文件夹中找到一个工作代码示例。

飞机:gl.enchant.js 的招牌和更多

enchant.js 包包含插件 primitive.gl.enchant.js 。这个插件包含游戏中使用的最常见的三维形状的预定义对象。我们将在本节中使用的对象是一个平面,,它只是一个可以加载图像或颜色的平面。因为所有这些对象都建立在Sprite3D之上,所以任何Sprite3D方法或属性对于一个平面也是可用或可访问的。在游戏中执行以下操作来实现位面:

  • 1.  Change the game.preload('img/droid.dae'); line in the window.onload function to match what is shown in Listing 7-20. This is just loading in pictures to be used by the planes coming up.

    清单 7-20。 预压图像

    game.preload('img/pit.png', 'img/sign.png', 'img/droid.dae');
    
  • 2.  Create a Plane to represent the pit (containing the holes the Droids come out of) by typing in the code in Listing 7-21 after the line scene.setCamera(camera); in the game.onload function.

    清单 7-21。 造坑

    var pit = new PlaneXZ();
    pit.scaleX = 3.0;
    pit.scaleY = 3.0;
    pit.scaleZ = 3.0;
    var texture = new Texture(game.assets['img/pit.png'], { flipY: false });
    texture.ambient = [1.0, 1.0, 1.0, 1.0];
    pit.mesh.texture = texture;
    pit.y = -1;
    pit.z = -10;
    scene.addChild(pit);
    

首先,我们创建 pit 变量作为新的PlaneXZ。这是 primitive.gl.enchant.js 中的一个预定义三维对象。由于平面是一个二维对象,它将只存在于三个三维轴中的两个轴上,这就是我们创建这个坑作为PlaneXZ的原因。这个指定告诉 enchant.js 应该在由 X 和 Z 轴创建的二维空间中创建平面。

接下来,我们用scaleXscaleYscaleZ将坑缩放 3 倍。使用等于-0.5 或 0.5 的坐标为平面的每个角或顶点(例如,-0.5,0,-0.5,(0.5,0,-0.5)等)来创建平面。).通过在所有三个轴上将它们缩放三倍,对于每个值,该平面的最终坐标要么是-1.5,要么是 1.5(例如,-1.5,0,-1.5),(1.5,0,-1.5),等等。).当你创建自己的游戏时,你会想尝试缩放设置,看看在你的场景摄像机的角度下什么看起来最好。

下一步是创建一个纹理,它最终可以应用到您刚刚创建的平面上。这里,我们加载 pit.png 作为图像,并传递一个参数(flipY: false)。我们为什么要这样做?当将纹理应用到平面时,enchant.js 会尽力找出如何应用纹理,使其正面朝上。但是,有时候 enchant.js 会出错。

在 WebGL 中,任何可以存储像素的对象都包含沿着垂直轴翻转图像的属性UNPACK_FLIP_Y_WEBGL,,并被赋给 gl.enchant.js 中的属性flipY,flipY属性默认为true,因此通过这种方式将其设置为false会沿着 Y 轴翻转图像。虽然从技术上来说,XZ 平面上没有 Y 轴,但是纹理是二维的,所以flipY的指定会像预期的那样工作,因为纹理没有 Z 轴。(如果你准备好迎接挑战,你可以通过搜索 OpenGL 中的纹理坐标信息,找到更多关于如何在 WebGL 中处理纹理的信息。WebGL 基于 OpenGL。)

移动到texture.ambient行,我们设置一个值[1.0,1.0,1.0,1.0]。前三个值对应红色、绿色和蓝色,最后一个值对应灯光的可见性或透明度。值 1.0 等于 100%,因此这转化为完全可见的白光。visibility 值仅在非常特殊的情况下使用,这里不做介绍。

最后,我们将新创建的纹理应用到坑的网格纹理属性,并调整坑的位置,将其放置在屏幕上适当的位置。最后一步是将其添加到Scene3D中,坑将出现在屏幕上。

  • 3.  Directly under the line scene.addChild(pit);, type in the code in Listing 7-22 to create a red base for our pit. This adds a nice touch to the finished product.

    清单 7-22。 创建基地

    var base = new PlaneXY();
    base.z = -8.6;
    base.y = -2.5;
    base.scaleX = 3.0;
    base.scaleY = 3.0;
    base.scaleZ = 3.0;
    base.mesh.setBaseColor('#ff0000');
    scene.addChild(base);
    

这段代码非常简单。我们简单地创建一个新的PlaneXY作为基础,定位它,缩放它,然后设置一个基础颜色。注意基色方法如何接受颜色的十六进制值。这里可以接受任何对 CSS 颜色属性有效的值,但是为了简单起见,我们喜欢使用十六进制值。

#ff0000转换为红色,因为前两个数字对应红色,后两个数字对应绿色,最后两个数字对应蓝色。因为这些值是十六进制的,所以这个值是十六进制的。ff的值对应于 256。因为只有红色占位符有值,并且值是 256,所以PlaneXY底座的颜色会是红色。

最后,我们给Scene3D添加碱基,使它出现在屏幕上。

  • 4.  Beneath that, create a sign to make our game even snazzier by typing in the code in Listing 7-23.

    清单 7-23。 创造标志

    var sign = new PlaneXY();
    sign.scaleX = 2.0;
    sign.scaleY = 2.0;
    sign.scaleZ = 2.0;
    var signTexture = new Texture(game.assets['img/sign.png']);
    signTexture.ambient = [1.0, 1.0, 1.0, 1.0];
    sign.mesh.texture = signTexture;
    sign.y = 0.5;
    sign.z = -8.6;
    scene.addChild(sign);
    

这里没有什么新东西。就像坑一样,我们创建一个新的Plane,这一次是一个PlaneXY,缩放它,创建一个新的纹理(这一次没有使用false flipY参数,因为 enchant.js 根据我们的需要正确定位图像),为纹理设置环境光,并将其分配给标志。用 y 和 z 属性定位标志后,我们将它添加到场景中。

  • 5.  Save the code and run it through a web browser. It should appear as it does in Figure 7-6.

    9781430247432_Fig07-06.jpg

    图 7-6 。用飞机重击机器人 3d

如果您遇到问题,您可以在本章开头提到的下载包的 Step4 文件夹中找到一个工作代码示例。

复制机器人并完成游戏

我们现在已经非常接近完成游戏了,但是我们的屏幕上仍然只有一个机器人。让我们复制那个 Droid 以得到一个完整的集合,填充我们的 pit 图像。

  • 1.  In your code, create a Droid in each one of the holes shown in the pit by replacing scene.addChild(new Droid(-1.1, -1.5, -9.2)); in the game.onload function with the code shown in Listing 7-24.

    清单 7-24。 创造多个机器人

    //Create all Droids
    for (var j = 0; j < 3; j++) {
        for (var i = 0; i < 4; i++) {
            scene.addChild(new Droid(i * 0.75 - 1.1, -1.5, -9.2 - j * 0.9));
        }
    }
    

    我们将Droid创建为一个可以调用的类,并指定该类的所有实例的行为,因此复制 Droid 就像使用for循环遍历我们想要创建的所有实例一样简单。

    j变量控制的for循环表示每行,由i变量控制的for循环表示给定行中的每列。请注意,在新的Droid声明中,我们如何将 x 位置乘以 0.75,以向右移动将创建一个新机器人的空间。类似地,我们将j的值乘以 0.9,然后从-9.2 中减去它,以将新机器人的位置向后移动一两行。在你的 enchant.js 游戏中创建任何东西的多个实例是一种非常有效的方法。

  • 2.  Directly underneath this, type in the code in Listing 7-25 to create an event listener that ends the game once the maximum number of Droids has reached zero or lower.

    清单 7-25。 结束游戏

    game.rootScene.addEventListener('enterframe', function() {
        if (maxDroid <= 0) {
            game.end(scoreLabel.score, "You whacked " + hitDroid + " Droids for a score of " + scoreLabel.score + " points!");
        }
    });
    

    因为重击机器人或允许他们退回到他们的洞里会导致变量maxDroid每次递减 1,所以我们还添加了一个enterframe事件监听器,以便在maxDroid达到 0 或更低时结束游戏,显示被玩家分数重击的机器人数量。

    image 注意请记住,因为game.end功能只有 nineleap.enchant.js 插件才完全支持,所以只有在9leap.net上传游戏时,才会出现带有玩家分数和机器人命中数的最终消息。

  • 3.  Save the code and run it from a web browser. You should have a working game that appears as it does in Figure 7-7.

    9781430247432_Fig07-07.jpg

    图 7-7 。复制机器人后

    如果您在本节中遇到问题,您可以在本章前面提到的下载包的 Step5 文件夹中找到一个完整的工作代码示例。

平移相机效果

虽然我们现在有一个工作的游戏,但让我们添加一个最后的点睛之笔:游戏开始时的平移相机效果。当游戏开始时,玩家将鸟瞰这个坑,然后摄像机将向下移动到我们到目前为止的视野。这可以让你看到你刚刚创建的游戏的 3d 特性。

执行以下操作来创建平移相机效果:

  • 1.  Inside the game.onload function, replace the lines that read camera.y = 1.1; and camera.z = -1.65; with the code in Listing 7-26 to change the initial camera position.

    清单 7-26。 改变摄像机位置

    camera.y = 14;
    camera.z = -8.1;
    

    这个摄像机位置离坑更远,但是仍然朝向坑。这使得声相效果更加强烈和明显。

  • 2.  After the line that reads if (maxDroid <= 0) {game.end(scoreLabel.score, "You whacked " + hitDroid + " Droids for a score of " + scoreLabel.score + " points!");}, but still inside the event listener, type in the code in Listing 7-27 to make the camera pan down at the beginning of the game.

    清单 7-27。 使摄像机开始平移

    //Pan camera down at beginning
    if (game.rootScene.age < 130) {
        camera.z += 0.05;
        camera.y -= 0.1;
    }
    

    实际的平移效果发生在这里。对于游戏的前 130 帧,相机会越来越靠近坑,停在完美的视点。这是通过每帧对 z 轴和 y 轴位置进行非常小的修改,以在经过 130 帧时到达特定点来实现的。当开发这样的东西时,它有助于计算你希望相机从哪里开始,你希望它在哪里结束,然后计算出相机需要移动每一帧多少才能实现这一点。

  • 3.  Save the code and run the game through a web browser. The game should start from the viewpoint shown in Figure 7-8 and quickly pan down to the view that is featured in preceding code samples.

    9781430247432_Fig07-08.jpg

    图 7-8 。具有全新视角的 3d 重击机器人

    如果您在本节中遇到问题,您可以在本章前面提到的 zip 文件的 Step6 文件夹中找到一个工作代码示例。

结论

在这最后一章,我们看了看如何开发一个独立的 3d enchant . js 游戏。我们简要介绍了 WebGL 和 enchant.js 的各种支持 WebGL 的插件:gl.enchant.js、primitive.gl.enchant.js 和 collada.gl.enchant.js。

我们创建了一个 3d 版本的重击机器人游戏,并看到了如何使用Scene3DCamera3DSprite3D的实例来构建一个 3d 环境并与玩家进行交互。通过创建平面实例和我们自己创作的Droid类,我们能够将我们的重击机器人游戏的 3d 版本放在一起,甚至在结尾添加了俯冲相机效果来展示我们游戏的 3d 本质。

我们希望这一章能启发你通过 enchant.js 自己尝试 3d 游戏,并最终尝试创作你自己的 3d 游戏。由于 9leap.net 支持任何用 enchant.js 创建的游戏,我们鼓励你在 9leap 上发布你的游戏并与世界分享!

与我们保持联系:

  • 官方网站 : enchantjs.com
  • Reddit:reddit.com/r/enchantjs–在这里提问!
  • Facebook 页面 : facebook.com/enchantjs
  • 推特 : @enchantjs_en
  • GitHub : github.com/wise9/enchant.js

祝你的游戏创作好运!

enchant.js 团队

八、附录 A:类

本附录包含 enchant.js 中最常见的类的信息。类首先根据它们来自 enchant.js 的哪一部分(核心库或插件)列出,然后按字母顺序组织。每个类页都包含对该类的总体功能的简要说明,列出从其继承属性和方法的类,提供该类最常见的属性和方法,对于某些类,还包括如何使用该类的示例。

类被组织在一个层次结构中,并从它们的父类继承方法和属性。从其继承属性和方法的类在每个类列表的继承部分中列出,并以一系列类的形式显示。最高级别的父类显示在左侧,列出的类显示在右侧。例如,标签类的继承树如下所示:

EventTarget-Node-Entity-Label

因此,除了能够使用在Label类中指定的所有属性和方法,作为标签创建的对象将能够使用来自Entity, NodeEventTarget的所有适用方法。addEventListener方法(来自EventTarget)可以在标签上运行,age 属性(来自Node)也可以在Label上调用。

关于 enchant.js 中类的完整列表,请参考http://enchantjs.com处参考部分下的官方 API。

附录内容:

  1. 核心类
    • a.核心
    • b.EventTarget(事件目标)
    • c.比赛
    • d.组
    • e.标签
    • f.地图
    • g.结节
    • h.事件
    • 一.声音
    • j.鬼怪;雪碧
  2. gl .很高兴见到你. js
    • a.故意的
    • b.相机 3D
    • c.方向灯
    • d.Light3D
    • e.场景 3D
    • f.Sprite3D
  3. ui.enchant.js
    • a.阿帕德
    • b.纽扣
    • c.生活标签
    • d .可变文本
    • e.Pad(垫)
    • f.分数标签

核心类

核心类包含在主 enchant.js 文件中。除了基本库(enchant.js)之外,不需要向一组文件中添加任何东西来访问核心类。

核心

Core类是主要的游戏容器。整个游戏必须驻留在一个Core对象中。一次只能存在一个,如果创建了另一个,原始的将被停用。

Core类还负责管理游戏中的Scene对象。改变活动场景、删除场景和添加Scene都是Core类对象的一部分。

延伸

EventTarget

image Core

公共属性

  • assets:存储预装图像的对象。
  • currentScene:当前显示的场景。
  • fps:游戏的帧率。
  • frame:自从Core对象被创建以来已经过去的当前帧数。
  • height:游戏画面的高度。
  • 游戏加载时显示的场景。
  • rootScene:一个游戏的默认场景。
  • width:游戏画面的宽度。

常用方法

所有的例子都假设变量game已经被创建为Core类的一个实例。

方法 说明 例子
popScene(); 结束当前场景并激活底层场景。 game.popScene();
preload(assets); 预装一个图像或声音文件,以便在游戏中使用。 game.preload("img/chara1.jpg");
pushScene(scene); 将场景推到场景堆栈的顶部,使其成为活动场景。 game.pushScene(Scene3);
removeScene(scene); 从场景堆栈中移除场景。 game.removeScene(Scene2);
replaceScene(scene); 用指定的场景替换当前场景。 game.replaceScene(Scene4);
start(); 开始游戏。 game.start();

例子

var game = new Core(320,320);

实体

Entity类包含显示为 DOM 元素的对象,或者在网页中创建为对象的元素。这个类不是直接使用的(也就是你从来没有指定过var bear = new Entity();),而是包含了 enchant.js 中大多数可视元素共有的属性和方法的集合,因为大多数可视元素(Sprite, Label等)。)是Entity类的子类,可视元素也可以访问这些属性和方法。

延伸

EventTarget-Node-Entity

公共属性

  • backgroundColor:指定背景的颜色。应以对 CSS 颜色属性有效的任何格式指定颜色。(如#ffffff、红色等。)
  • buttonMode:将此Entity指定为按钮。当随后点击Entity时,相应的按钮事件被调度。有效值包括左、右、上、下、a 和 b。
  • 如果此Entity被点击,则buttonPressed:设置为true。这仅在设置了buttonMode时有效。
  • height:以像素为单位的Entity的高度。
  • opacity:``Entity的不透明度。这是从 0(完全透明)到 1(完全不透明)测量的。
  • originX:用于旋转和缩放的原点的 X 位置。
  • origin:用于旋转和缩放的原点的 Y 位置。
  • rotation:旋转Entity的角度(单位为度)。
  • visible:表示是否显示Entity。默认为true。注意,一个Entity仍然必须被添加为一个Scene的子节点,以显示在屏幕上。

常用方法

所有的例子都假设变量game已经被创建为Sprite类的实例,它扩展了Entity类。这是因为Entity本身不能被创建。

方法 说明 例子
intersect(Entity); 如果Entity与指定的Entity接触或相交,则返回true bear.intersect(ball);
rotate(degree); Entity旋转指定的度数。 bear.rotate(15);
scale(x,y); 按照指定的因子在 x 和 y 方向缩放Entity bear.scale(2,2);(将熊缩放 200%)
within(Entity, distance); 如果Entity的中心点在指定Entity的中心点的指定距离内(以像素为单位),则返回true bear.within(ball, 50);

事件

事件不是由开发者直接创建的,而是通常由一个CoreSceneNode对象发出的。使用EventListener,可以监听这些事件,然后在事件发生时执行指定的代码。

延伸

事件是在 enchant.js 的最高层定义的,不从任何类继承。

常见事件

请注意,下表中描述的“A”和“B”按钮是指作为 ui.enchant.js 中的Button类的一部分创建的虚拟按钮,并被指定为“A”和“B”按钮。有关更多信息,请参见本附录末尾的 ui.enchant.js 部分中的Button类条目。

事件类型 描述 发布的对象
Event.A_BUTTON_DOWN 按下“A”按钮时发生的事件 Core, Scene
Event.A_BUTTON_UP 释放“A”按钮时发生的事件 Core, Scene
Event.ADDED 将节点添加到组时发生的事件 Node
Event.ADDED_TO_SCENE 将节点添加到场景中时发生的事件 Node
Event.B_BUTTON_DOWN 按下“B”按钮时发生的事件 Core, Scene
Event.B_BUTTON_UP 释放“B”按钮时发生的事件 Core, Scene
Event.DOWN_BUTTON_DOWN 按下向下按钮时发生的事件 Core, Scene
Event.DOWN_BUTTON_UP 释放向下按钮时发生的事件 Core, Scene
Event.ENTER 场景开始时发生的事件 Scene
Event.ENTER_FRAME 处理新帧时发生的事件 Core, Scene
Event.EXIT 场景结束时发生的事件 Scene
Event.EXIT_FRAME 帧处理即将结束时发生的事件 Core
Event.INPUT_CHANGE 按钮输入改变时发生的事件 Core, Scene
Event.INPUT_END 按钮输入结束时发生的事件 Core, Scene
Event.INPUT_START 按钮输入开始时发生的事件 Core, Scene
Event.LEFT_BUTTON_DOWN 按下左键时发生的事件 Core, Scene
Event.LEFT_BUTTON_UP 释放左键时发生的事件 Core, Scene
Event.LOAD 游戏加载完成时调度的事件 Core
Event.PROGRESS 游戏加载期间发生的事件 Core
Event.REMOVED 从组中删除节点时发生的事件 Node
Event.REMOVED_FROM_SCENE 从场景中移除节点时发生的事件 Node
Event.RENDER 呈现实体时发生的事件 Entity
Event.RIGHT_BUTTON_DOWN 按下右键时发生的事件 Core, Scene
Event.RIGHT_BUTTON_UP 释放右键时发生的事件 Core, Scene
Event.TOUCH_END 当与节点相关的触摸结束时发生的事件 Node
Event.TOUCH_MOVE 当与节点相关的触摸移动时发生的事件 Node
Event.TOUCH_START 与节点相关的触摸开始时发生的事件 Node
Event.UP_BUTTON_DOWN 按下向上按钮时发生的事件 Core, Scene
Event.UP_BUTTON_UP 释放向上按钮时发生的事件 Core, Scene

比赛

在 0.6 版本中,Game对象被重新命名为Core对象。请参见Core对象条目。高于 0.6 的 enchant.js 版本向后兼容Game对象,但是由于Game对象已经过时,我们强烈建议使用Core对象。

Group类用于将多个Node对象链接在一起,目的是将它们作为一个单元移动,或者作为一个单元以另一种方式对它们进行操作。因为 enchant.js 中所有可见的实体都属于扩展了Node类的Entity类,所以所有可见的实体都可以添加到一个Group对象中。

延伸

EventTarget-Node-Group

公共属性

  • childNodes:包含所有节点的对象数组,这些节点是Group的成员。
  • firstChild:Group中的第一个Node
  • lastChild:?? 中的最后一个Node

常用方法

所有的例子都假设变量set已经被创建为Group类的一个实例。因为Group扩展了Node类,任何对Node有效的方法都将作用于Group对象。(如moveTo, moveBy等。)

方法 说明 样品
addChild(node); Group的末尾添加一个Node set.addChild(bear);
insertBefore(node, reference); Node对象数组中的参考位置之前,将一个Node插入到一个Group中。 set.insertBefore(bear,3);
removeChild(node); Group中删除一个Node set.removeChild(bear);

示例使用

var stage = new Group();
stage.addChild(player);
stage.addChild(enemy);
stage.addChild(map);
stage.addEventListener('enterframe', function() {
   // Moves the entire frame based on the player's x coordinate.
   if (this.x > 64 - player.x) {
       this.x = 64 - player.x;
   }
});

标签

标签用于显示游戏中的文本。

延伸

EventTarget-Node-Entity-Label

公共属性

  • color:Label中文本的颜色。应以对 CSS 颜色属性有效的任何格式指定颜色。(如#ffffff、红色等。)
  • font:Label中文本的字体。字体应该以对 CSS 字体属性有效的任何格式指定。(如 16px 衬线;12px 无衬线字体;等等。)
  • text:由Label显示的文本。
  • textAlign:文本的水平对齐。应以对 CSS text-align 属性有效的任何格式指定对齐方式。(例如,左、右、中心等。)

常用方法

除了从Entity, NodeEventTarget继承的方法之外,Label没有其他通用的方法。

示例使用

var title = new Label();
title.color = "blue";
title.font = "16px serif";
score.text = "Example Title";

game.rootScene.addChild(title);

地图

地图用于从 tilesets 创建地图并显示它们。

延伸

EventTarget-Node-Entity-Map

公共属性

  • collisionData:二维数组,指定是否应该在Map中的特定图块上检测碰撞。
  • image:用于Map中图块的图块集合图像。
  • tileHeight:在Map中使用的瓷砖的高度。
  • tileWidth:在Map中使用的瓷砖的宽度。

常用方法

所有的例子都假设变量map1已经被创建为Map类的一个实例。

方法 说明 例子
checkTile(x, y); 检查在给定位置出现了什么牌。 map1.checkTile(50, 50);
hitTest(x, y); 如果指定点在collisionData数组中被指定为包含障碍物,则返回true map1.hitTest(50, 50);
loadData(data); 从图块集图像设置地图中的图块。 map1.loadData(array);

示例使用

var map = new Map(16, 16);
map.image = game.assets['http://enchantjs.com/assets/img/map0.gif'];
map.loadData([
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,0],
    [0,2,2,2,2,0,0,2,2,2,2,2,2,2,2,0,0,0,2,2,2,2,0],
    [0,2,2,2,2,0,0,2,2,2,2,2,2,2,2,0,0,0,2,2,2,2,0],
    [0,0,2,2,0,0,0,2,2,0,0,0,0,2,2,0,0,0,0,2,2,0,0],
    [0,0,2,2,0,0,0,2,2,0,0,0,0,2,2,0,0,0,0,2,2,0,0],
    [0,0,2,2,0,0,0,2,2,0,0,0,0,0,0,0,0,2,2,2,2,0,0],
    [0,0,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,2,2,2,2,0,0],
    [0,0,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,2,2,0,0,0,0],
    [0,0,0,0,0,2,2,0,0,0,0,0,2,2,2,2,2,2,2,0,0,0,0],
    [0,0,0,0,0,2,2,0,0,0,0,0,2,2,2,2,2,2,2,0,0,0,0],
    [0,0,0,2,2,2,2,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0],
    [0,0,2,2,2,2,2,0,0,0,0,0,2,2,2,2,2,2,2,2,2,0,0],
    [0,0,2,2,2,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,0,0],
    [0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,0,0],
    [0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,0,0],
    [0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,0],
    [0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,2,2,2,2,0],
    [0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,2,2,2,2,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
]);

结节

Node类用于 enchant.js 中通过显示树显示的对象。Node类的实例不是由开发人员直接创建的。

延伸

EventTarget-Node

公共属性

  • age:物体已经存活的帧数。
  • scene:Node所属的Scene
  • x:物体的 x 位置。
  • y:Node对象的 y 位置。

常用方法

所有的例子都假设变量bear已经被创建为Sprite类的实例,它扩展了Entity类,后者扩展了Node类。

方法 说明 例子
moveBy(x, y); 将节点对象在 x 和 y 轴上移动指定的量(以像素为单位)。 bear.moveBy(50, 50);
moveTo(x, y); 将节点对象移动到 x 和 y 轴上指定的位置。 bear.moveTo(60, 60);

事件

显示对象树的根。所有的Entity对象必须添加到一个Scene对象中才可见。

延伸

EventTarget-Node-Group-Scene

公共属性

除了从EventTarget, NodeGroup继承的属性之外,场景没有公共属性。

常用方法

除了从EventTarget, NodeGroup继承的方法之外,Scene没有其他通用的方法。

示例使用

var scene = new Scene();
scene.addChild(player);
scene.addChild(enemy);

core.pushScene(scene);

鬼怪;雪碧

精灵用于在游戏中显示图像,通常是角色或游戏元素的图像。

延伸

EventTarget-Node-Entity-Sprite

公共属性

  • frame:要显示的帧的索引号。
  • height:以像素为单位的Sprite的高度。
  • image:图像(sprite-sheet ),从中拉出一个帧来表示屏幕上的Sprite
  • width:以像素为单位的Sprite的宽度。

常用方法

除了从EventTarget, NodeEntity继承的属性之外,Scene没有共同的属性。

示例使用

var bear = new Sprite(16,16);
sprite.image = game.assets("img/chara1.gif");
sprite.frame = [0, 1, 0, 2];
        //shows frame 0, 1, 0, and 2 in sequence, incrementing up one each frame

game.rootScene.addChild(bear);

gl .很高兴见到你. js

该插件将 WebGL 支持扩展到 enchant.js,允许创建 3d 游戏。

环境光

AmbientLight类表示从光源的原点向所有方向产生光的光源。

延伸

EventTarget-Light3D-AmbientLight

公共属性

除了从EventTarget, NodeEntity继承的属性之外,AmbientLight没有共同的属性。

常用方法

除了从EventTargetLight3D继承的属性之外,AmbientLight没有共同的属性。

示例使用

var scene = new Scene3D();
var light = new AmbientLight();
light.color = [1.0, 1.0, 0.0];

scene.setAmbientLight(light);

相机 3D

类创建了一个视图,玩家可以从这个视图中看到一个Scene3D的内部。

延伸

这个类是在 gl.enchant.js 的最高层定义的,它没有父类。

公共属性

  • centerX:相机指向的 X 轴上的点。
  • centerY:相机指向的 Y 轴上的点。
  • centerZ:相机指向的 Z 轴上的点。
  • upVectorX:X 轴上的点,相机认为该点“向上”
  • upVectorY:Y 轴上的点,相机认为该点“向上”
  • upVectorZ:Z 轴上的点,相机认为该点“向上”
  • x:摄像机在 X 轴上的位置。
  • y:相机在 Y 轴上的位置。
  • z:摄像机在 Z 轴上的位置。

常用方法

所有的例子都假设变量camera已经被创建为Camera3D类的一个实例。

方法 说明 例子
altitude(amount); 沿 Y 轴移动摄像机 3D。 camera.altitude(2);
forward(amount); 沿 Z 轴移动摄像机 3D。 camera.forward(2);
lookAt(sprite); 将摄像机 3D 指向场景 3D 中的 Sprite3D。 camera.lookAt(droid);
rotatePitch(degrees); 沿 X 轴旋转摄像机 3D。 camera.rotatePitch(15);
rotateRoll(degrees); 沿 Z 轴旋转摄像机 3D。 camera.rotateRoll(15);
rotateYaw(degrees); 沿 Y 轴旋转摄像机 3D。 camera.rotateYaw(15);
sidestep(amount); 沿 X 轴移动相机。 camera.sidestep(2);

示例使用

camera = new Camera3D();
camera.y = 1.1;
camera.z = -1.65;
camera.centerZ = -10;
camera.upVectorZ = 10;

camera.upVectorY = 100;

方向灯

DirectionalLight类代表一种向一个方向而不是所有方向投射光线的灯光。

延伸

EventTarget-Light3D-DirectionalLight

公共属性

  • directionX:点的 X 位置,该点是DirectionalLight照射的方向。
  • directionY:点的 Y 位置,DirectionalLight将光线射向该点。
  • directionZ:点的 Z 位置,DirectionalLight将光线射向该点。

常用方法

除了从EventTargetLight3D继承的方法之外,DirectionalLight没有通用的方法。

示例使用

var scene = new Scene3D();
var light = new DirectionalLight();
light.color = [1.0, 1.0, 0.0];
light.directionY = 10;
light.directionX = 4;
light.directionZ = 1;

scene.setDirectionalLight(light);

Light3D

Light3D是三维灯光的基类。Light3D类的实例不是由开发人员直接创建的。

延伸

EventTarget-Light3D

公共属性

color:光源的颜色。

常用方法

除了从EventTarget继承的方法之外,Light3D没有通用的方法。

场景 3D

Scene3D类是Scene类的三维等价物。

遗产

EventTarget-Scene3D

公共属性

  • backgroundColor:Scene3D的背景颜色。
  • childNodes:一个Scene3D的子元素数组。
  • lights:在Scene3D中的 Light3D 元素的数组。

常用方法

所有的例子都假设变量scene已经被创建为Scene3D类的一个实例。

方法 说明 例子
addChild(sprite); 将 Sprite3D 添加到 Scene3D 中。 scene.addChild(droid);
addLight(light); 向 3D 场景添加灯光。 scene.addLight(light3);
getAmbientLight(); 检索场景 3D 中的环境光源。 scene.getAmbientLight();
getCamera(); 检索场景 3D 中的摄像机源。 scene.getCamera();
getDirectionalLight(); 检索场景 3D 中的平行光源。 scene.getDirectionalLight();
removeChild(sprite); 从场景 3D 中删除 Sprite3D。 scene.removeChild(droid);
removeLight(light); 从 3D 场景中删除 Light3D。 scene.removeLight(light2);
setAmbientLight(light); 在场景 3D 中设置环境光源。 scene.setAmbientLight(light2);
setCamera(camera); 在场景 3D 中设置活动的摄像机 3D。 scene.setCamera(camera);
setDirectionalLight(light); 设置场景 3D 中的平行光源。 scene.setDirectionalLigh(light6);

示例使用

var scene = new Scene3D();
var sprite = new Sprite3D();

scene.addChild(sprite);

Sprite3D

Sprite3D类是Sprite类的三维等价物。

延伸

EventTarget-Sprite3D

公共属性

  • childNodes:子Sprite3D元素的数组。
  • scaleX:X 轴上的比例因子。
  • scaleY:Y 轴上的比例因子。
  • scaleZ:Z 轴上的比例因子。
  • x:X 轴上Sprite3D的位置。
  • y:Y 轴上Sprite3D的位置。
  • z:Z 轴上Sprite3D的位置。

常用方法

所有的例子都假设变量droid已经被创建为Sprite3D类的一个实例。

方法 说明 例子
addChild(sprite); 向 Sprite3D 对象添加子 Sprite3D。 droid.addChild(droid2);
altitude(amount); 沿 Y 轴移动 Sprite3D。复制 Sprite3D 对象。 droid.altitude(2);
clone(); 复制 Sprite3D 对象。 droid.clone();
forward(amount); 沿 Z 轴移动 Sprite3D。 droid.forward(2);
intersect(object); 如果 Sprite3D 与指定的 3D 对象相交,则返回 true。 droid.intersect(droid2);
removeChild(sprite); 从 Sprite3D 中移除子 Sprite3D 对象。 droid.removeChild(droid2);
rotatePitch(amount); 沿 X 轴旋转 Sprite3D。 droid.rotatePitch(15);
rotateRoll(amount); 沿 Z 轴旋转 Sprite3D。 droid.rotateRoll(15);
rotateYaw(amount); 沿 Y 轴旋转 Sprite3D。 droid.rotateYaw(15);
scale(x,y,z); 沿 x、y 和 z 轴缩放 Sprite3D。 droid.scale(2,2,2);
set(colladaFile); 将 Collada 文件设置为用于 Sprite3D 的模型。 droid.set(game.assets["img/droid.dae"]);
sidestep(amount); 沿 X 轴移动 Sprite3D。 droid.sidestep(2);

ui.enchant.js

这个插件包含各种元素来创建 enchant.js 游戏的用户界面,并支持各种按钮、面板和标签。对于大多数类,所需的图像也必须加载到给定的 enchant.js 游戏中。所需的图像与其各自的类具有相同的名称。

你爸爸

这个类是为一个模拟垫,可以控制任何二维方向的运动。

延伸

EventTarget-Node-Group-APad

公共属性

  • isTouched:如果当前正在触摸APad,则返回true

常用方法

除了从EventTarget, NodeGroup继承的方法之外,APad没有通用的方法。

纽扣

这个类创建了一个带有文本的按钮,玩家可以点击它。

延伸

EventTarget-Node-Entity-Button

公共属性

  • color:文字的颜色。
  • font:文字的字体。
  • size:文字的字体大小。
  • text:按钮上显示的文本。

常用方法

除了从EventTarget, NodeEntity继承的方法之外,Button没有通用的方法。

示例使用

//Creates a virtual 'a' button at (260, 250) and adds it to the rootScene

abtn = new Button(260, 250, 'a');

生活标签

这个类创建了一个标签,显示了一些代表角色健康的心。

延伸

这个类是在 ui.enchant.js 的最高层定义的,它没有父类。

公共属性

  • life:当前寿命由LifeLabel显示。
  • maxLife:由LifeLabel显示的最大寿命。
  • x:LifeLabel的 X 位置。
  • y:LifeLabel的 Y 位置。

常用方法

这个类没有方法。

可变文本

这个类允许使用每个字符的图像来显示文本,允许文本在不同的操作系统和浏览器中完全相同地显示。

延伸

这个类是在 ui.enchant.js 的最高层定义的,它没有父类。

公共属性

  • posX:物体的 X 位置。
  • posY:物体MutableText的 Y 位置。
  • text:由MutableText对象显示的文本。
  • width:以像素为单位的MutableText对象的宽度。

常用方法

所有的例子都假设变量message已经被创建为MutableText类的一个实例。

方法 说明 例子
setText(text); 设置MutableText对象的文本。 message.setText("Hello!");

衬垫

这个类为玩家创建了一个四方向的方向板。当按下面板上的按钮时,会发出与其方向相对应的Events(例如UP, DOWN, LEFT, RIGHT)。

延伸

EventTarget-Node-Entity-Sprite-Pad

公共属性

除了从EventTarget, Node, EntitySprite继承的属性之外,Pad没有共同的属性。

常用方法

除了从EventTarget, Node, EntitySprite继承的方法之外,Pad没有通用的方法。

分数标签

这个类是专门设计的MutableText类的版本,用来显示游戏的分数。

延伸

这个类是在 ui.enchant.js 的最高层定义的,它没有父类。

公共属性

  • score:指定ScoreLabel要显示的分数。
  • x:ScoreLabel的 X 位置。
  • y:ScoreLabel的 Y 位置。

常用方法

这个类没有方法。

posted @ 2024-08-13 14:28  绝不原创的飞龙  阅读(41)  评论(0)    收藏  举报