HTML5 游戏开发跨越手册(一)
一、简介
“如果你有一个花园和一个图书馆,你就拥有了你需要的一切。”
马库斯·图利乌斯·西塞罗(公元前 106—公元前 43 年)
var replaceWord1 = str.replace("garden", "computer");
var replaceWord2 = str.replace("a library", "time");
我从 1996 年开始开发软件,至今已经为大大小小的公司开发游戏十多年了。像现实世界中任何形式的开发一样,在考虑编码策略和构建流程之前,您需要知道为什么要构建游戏。在游戏世界中,这以游戏故事的形式出现。这包括背景,玩的理由,游戏的目标。
介绍我们的游戏:太空僵尸
这是我们的故事,我们将把它发展成一个游戏。
你好。我的名字是 Ace Star。时间是 2107 年。在过去的三个月里,我一直在仙女座星系唯一的卫星 ZC636 上担任安全警卫。除了我和一群来自地球的政要,还有一群大约 500 名顶尖科学家驻扎在这里进行秘密实验。
我需要你帮我。
昨晚,一个实验室发生了爆炸。一种气体释放出来,把实验室里所有的科学家都变成了僵尸。
我已经在殖民地主楼唯一的门外。其他幸存者都在里面。在地球救援到来之前,我是最后一道防线。
我发现我们的武器对僵尸毫无用处。然而,在跑出实验室时,我发现了一种新的实验性武器。
它似乎有效果。
我能听到他们来了。你准备好了吗?
让我们来看看我们将用于开发的一些图形。
|  | 这是我们游戏的背景图片。它将通过水平和垂直拉伸来填充屏幕。我们的僵尸将从地面和天空的交汇处诞生。一旦产卵,它们就会向我们靠近,变得越来越大。 | |  | 向 Z 教授问好,我们的普通僵尸。就速度而言,他不是很快,也不会突然冲到前面。他只是以自己悠闲的速度向你走来!在我们的武器射击方面,他不会太难或太容易“中和”需要两次电击才能抓到他。 | |  | 向颠茄说再见,我们最快的僵尸。盯紧她因为她会出现一分钟然后突然冲刺到前面。然而,她不会太难被压制。一次电击就能干掉她。 | |  | 最后,这是布拉德,我们的重量级僵尸。不幸的是,由于长时间举重,他比一般的僵尸要慢。一旦产卵,它会慢慢加速。然而,他将更难被压制。需要三次电击才能抓到他。 | |  | 这是我们的英雄在一个实验室找到的实验武器。当发射时,它会喷出一种特殊的液体,如果使用成功,这种液体会将僵尸包裹在一个气泡中。它需要经常重装。 | |  | 这是我们的重新加载按钮。从游戏设计的角度来看,它为游戏增加了另一个维度。 | |  | 这是我们游戏的标志。我们将不会看到它,直到本书的最后一章,当我们嵌入我们的游戏。 | |  | 这是我们将嵌入到游戏中的盒子。最初,你会看到游戏横跨整个屏幕。然而,在接近尾声时,我们看到了将代码嵌入到这个盒子中。 | |  | 这是我们在最后一章使用的背景图片。背景是当我们建立一个专门的网页来嵌入我们的游戏时使用的主要图像。 |以下是游戏完成后的截图。
设置您的工作环境
本节讨论如何设置工作环境。
第一部分:设置我们的文件夹
您需要创建一个工作文件夹来存储您的所有工作文件。这使得将您的工作文件与计算机上的所有其他文件区分开来变得更加容易。所以首先,在 c 盘中创建一个名为My_Work_Files的根(或主)文件夹。
一旦你有了根文件夹,下一步就是创建游戏需要的子文件夹。在My_Work_Files中创建四个文件夹。将文件夹命名如下:
CSSImagesRaw Imagesjs
你的文件夹应该看起来像下面的截图。
文件夹将保存特殊的代码文件,帮助构建游戏的设计。该文件夹中的所有文件将以.css结尾。
文件夹将保存我们所有的 JavaScript 文件,这些文件将构成我们游戏的引擎。它们将包含控制我们游戏中发生的事情的命令和指令。该文件夹中的所有文件将以.js结尾。
顾名思义,Images文件夹将包含游戏所需的所有图像或媒体文件。
从技术上讲,Raw Images文件夹不会用于原始图像。在我们的例子中,我们将使用这个文件夹作为我们所有媒体的临时专用空间。当我们需要它们时,我们会将它们移动到Images文件夹中。
第二部分:设置我们的文件
出于本书的目的,我将使用记事本(如果你使用苹果电脑,那么我将使用文本编辑)。我发现记事本更简单,更容易使用;但是,几乎任何 IDE(集成开发环境)都可以用于这个项目。因此,请继续使用您最熟悉的 IDE。
如果您确实想使用 IDE,这里列出了一些免费使用的 IDE:
- 月食。这是一个开源编辑器,通常用于 C 和 C++(以及其他高级语言)项目。
- NetBeans。像 Eclipse 一样,这是一个开源编辑器;然而,它与过多的开发框架捆绑在一起。
- 阿普塔纳。这是一个在 web 开发人员中非常流行的 IDE,它可以插入 Eclipse。通常用于 HTML 项目。
- 代码运行。这是一个有点不寻常的选择,因为它运行在浏览器上(即,它是一个基于 web 的 IDE)。就我个人而言,我发现它非常适合在远程位置进行最后的修复。
- Visual Studio 社区。这对个人程序员来说是免费的,它包含了 Visual Studio Professional 系列中所有令人惊叹的特性。
虽然使用 IDE 有它的好处,但我认为值得记住这句关于使用 IDE 处理多种语言的名言:“尽管许多 IDE 可以处理多种语言,但很少有人做得很好。此外,如果你才刚刚开始,这可能有些过头了。”
现在文件夹已经设置好了,让我们来创建您将用来开发游戏的文件。
首先,您需要创建一个default.html文件。如果使用的是 IDE,请单击“文件”“➤”“新建”,然后选择“HTML”。如果您正在使用记事本,请打开一个新文件,并将其另存为default.html。
您的文件夹现在应该看起来像这样:
现在,我们需要在我们创建的一些文件夹中创建文件。双击js文件夹。重复前面的步骤(即创建一个新文件,然后另存为)。以下是要输入的文件名:
> SZ_main.js> SZ_movement.js> SZ_setupContent.js> SZ_SS.js> SZ_touch.js> SZ_zombie_movement.js
您的js文件夹现在应该是这样的:
最后,我们需要在CSS文件夹中创建一个文件。重复前面的步骤(即创建一个新文件,然后另存为)。要输入的文件名是
> SZ_master.css.
您的CSS文件夹现在应该是这样的:
我们需要我们的文件在全球网络上成功地工作,所以我们应该尽量遵守标准化的命名约定。
最好避免在文件名中使用字符空格。从技术上讲,这在本地环境(Apple 和 Windows OS)中是可以接受的,但是,字符空间不被其他系统识别。理想情况下,使用下划线或连字符来分隔文件名中的单词。
不要使用任何特殊字符,例如!, ?、%、#或/。最好将文件名限制为下划线、数字和字母。
对于这个项目,你会注意到,我尝试一致地以SZ_开始我所有的文件名。这是因为它们是游戏名字的首字母——太空僵尸。在命名和组织文件时保持一致性和描述性是很重要的,这样就可以清楚地找到特定的数据和文件包含的内容。
通过以有意义的方式命名文件,您可以增加将来找到这些文件并知道它们包含什么信息的机会。当你来开发新游戏时,你将很容易通过搜索所有以SZ_开头的文件来定位空间僵尸文件。
最后,保持文件名尽可能短是一个好习惯。除了增加文件的大小,这也让他们更容易记住六个月后的事情。
托管和媒体文件
只要文件还在你的硬盘上(之前创建的文件和文件夹),你就可以在你的电脑上轻松地测试游戏。这对刚起步的开发人员来说当然没问题。
尽管如此,当你开发了几个游戏时,你可能希望展示给所有人看和玩。
为此,您需要将文件上传到服务器计算机上。服务器本质上是一台连接到互联网的计算机。
第一部分:您的计算机与托管服务器
你需要在服务器上开一个账户。如果你在谷歌上搜索“服务器托管免费试用”,你有几个选择。如果你仍然不确定,请不要犹豫,在 Twitter 上给我发消息@zarrarchishti。
以下是可用主机选项的简短列表。
专用服务器
这是最昂贵的选择。本质上,你拥有连接到互联网的计算机。如果你是一家大公司或经销商,这是唯一的选择。
共享服务器
这通常是托管的最经济的选择。这对像你这样租用服务器的人来说意义重大。当然,主要优势是低得离谱的成本。然而,随着您的游戏开发专业知识的增加,您可能会发现这个选项有局限性,不适合您的特定需求。
云托管
前两个选项依赖于一台物理计算机,而云托管允许无限数量的计算机作为一个系统。
第二部分:下载项目的媒体
项目中使用的媒体文件(图像和声音文件)可供您下载。
打开您的互联网浏览器并转到以下 URL:
http://zarrarchishtiauthor.com/downloads/
单击下载按钮。这将启动下载。完成后,浏览器会通知您。导航到您的下载文件夹并找到下载的文件。
应该是一个叫raw_media_1.rar的文件。现在您需要将这个压缩文件解压到一个名为Raw Media的新文件夹中。双击该文件夹,您将看到以下四个文件夹:
> Images> JS> sounds> html_web
首先,将所有四个文件夹复制到您的Raw Images文件夹,它位于My_Work_Files文件夹中。
在这个阶段,我们只对JS文件夹中的文件感兴趣。随着游戏的进行,我们将回到其他文件夹,根据需要复制文件。双击JS文件夹(在Raw Images文件夹中)。使用与之前相同的技术复制文件,继续复制所有文件,然后将它们粘贴到您自己的js文件夹(在My_Work_Files文件夹中)。
您的js文件夹(在My_Work_Files文件夹中)现在应该是这样的:
暂时就这样吧!我们已经成功地建立了我们的游戏开发环境。我们现在准备开始编码我们的游戏!
我们从JS文件夹中复制的文件是特殊的 JavaScript 程序,我们可以在游戏中使用。想象一下由谷歌等公司维护的代码库,其中包含的功能让我们的生活变得更加轻松。
这些文件——例如 jQuery——是快速、小型且功能丰富的 JavaScript 库。它们共同使 HTML 文档遍历和操作、事件处理、动画和 AJAX 等事情变得更加简单,并提供了一个可在多种浏览器上工作的易于使用的 API。
当使用这样的库时,我们不需要担心它们是如何工作的。我们只需要知道他们做了什么,这样我们就可以决定是否要在我们的游戏中使用他们。
使用 jQuery 等库的另一个优点是,它在所有主流浏览器中运行完全相同,包括 Internet Explorer 6!所以不用担心跨浏览器的问题。
通常,我们直接从源服务器链接到这些文件。这样做的好处是,当我们运行游戏时,我们总是得到代码的最新副本。然而,因为我们希望能够离线玩游戏,所以让我们选择将它们下载到本地文件夹中。
- 在
窗口中,是否出现了在此提取的选项?如果没有,需要从下面下载 WinRAR:
http://www.win-rar.com/download.html - 你在用笔记本电脑吗?要右击,需要先点击
,然后点击鼠标垫。
- 下载媒体文件时,您是否收到来自浏览器的消息,警告您下载不常用,可能有危险?如果是,这是因为我选择了 WINRAR 而不是 WINZIP 文件。这些文件并不危险。您可以单击“保持”;但是,在打开文件夹之前,请随意对其进行病毒检查。
二、HTML 入门
"Nine people can't have a baby in a month."
弗雷德·布鲁克斯
HTML 是一种用于开发网站的标记语言。那么为什么我们的游戏需要这个呢?最好把 HTML 想象成我们游戏的骨架或骨骼结构。
顺便说一下,一旦你完成了这一章,你不仅开始了你的游戏开发之旅,也开始了你的网页开发之旅!
你好世界
在我 20 年的编程生涯中,我学习了许多编程语言。我一直在做的第一个项目是学习如何输出单词“Hello World”对着屏幕。我打赌你也遵循这个传统,所以让我们用 HTML 开发一个“Hello World”页面。
使用在第一章的“第二部分:设置我们的文件”一节中使用的相同程序或 IDE,在记事本或文本编辑中打开My_Work_Files文件夹中的default.html文件。
当文件打开时,它应该是完全空白的。键入以下几行:
<html>
<head>
</head>
<body>
<div id="SZ_maincontent">
Hello World.
</div>
</body>
</html>
导航到菜单,单击文件,然后单击保存。您现在可以关闭该文件了。导航回菜单,单击文件,然后单击退出/关闭。
你准备好测试你的第一个程序了吗?
回到My_Work_Files文件夹,双击default.html文件。这将在您的默认 Internet 浏览器中打开;比如微软 Edge,谷歌 Chrome,或者 Safari。
在浏览器上打开的页面应该是一个完全空白的页面,上面写着“Hello World”显示在左上角。太好了。我们的程序成功了,我们已经编写了第一段代码!
显然,这还远不是一场游戏。尽管如此,从现在到那时要坚持工作。请放心,到本书结束时,我们将已经开发了整个游戏。这肯定是值得的。你将会学到很多不同的技术来开始你开发一套游戏的旅程!
HTML 代表超文本标记语言。超文本是你在互联网上导航的方法。Hyper 只是意味着它不是线性的,或者你可以通过点击链接去网络上的任何地方。标记是 HTML 标签对其中的文本所做的事情。他们将其标记为某种类型的文本(例如粗体文本)。
以下是您刚刚编码的每个标签的描述:
- 每个新网页的开头和结尾都需要这样做。这两个标签中的所有内容构成了页面的内容。
- head 标签的内容包括页面标题、脚本、样式和元信息。
- 我们网页的所有视觉内容,如文本、超链接和图像,都包含在这个标签中。
- 这定义了我们页面的一个特定部分。最好将 div 标签视为容器。在更大的 div 标签中包含 div 标签并不罕见。
你会注意到结束标签和开始标签基本上是一样的,前面有一个正斜杠;例如,</div>表示您正在关闭那个特定的标签。
请记住,每个标签都必须关闭。
背景图像
游戏的背景图像不会改变、移动或与游戏互动。它为所有将被实际游戏控制的各种元素提供了背景。
首先,转到My_Work_Files文件夹的Raw Images文件夹中的images文件夹。找到名为SZ_background_image.jpg的文件。你需要将这个文件复制到你的Images文件夹中,看起来应该是这样的:
让我们重新打开default.html文件。通过选择“Hello World”行并单击删除/退格删除该行。现在键入以下新行(所有新文本都以粗体显示):
<html>
<head>
</head>
<body>
<div id="SZ_maincontent">
<img id="SZ0_0" src="img/SZ_background_image.jpg" />
</div>
</body>
</html>
保存文件,然后关闭它。回到My_Work_Files文件夹,双击default.html文件。
通过使用<img>标签,我们为页面定义了一个背景图片。需要注意的是,从技术上讲,图像并没有插入到我们的 HTML 页面中;相反,背景图片已经链接到我们的 HTML 页面。标签为背景图像创建了一个保存空间。
“Hello World”文本应该已经消失了,背景图像现在在它的位置上。它看起来不像是覆盖了屏幕。不要担心那个。我们将在下一章调整图片的大小。
在这一节中,您遇到了<img>标签,当您想要在 web 页面中放置图像时会用到它。
在<img>标签中,你会注意到
id="SZ0_0"
顾名思义,这是图像标签的 ID。当我们在第四章开始用 JavaScript 编码时,会用到这个 ID。
此外,您可能已经注意到了src标签:
src="img/SZ_background_image.jpg"
src,代表“源”,允许您指定图像的位置。在本节的前面,我们将SZ_background_image.jpg放在了images文件夹中。如您所见,src是图像文件的确切位置和名称。
现在,让我们回想一下上一节,我说过你总是需要包含结束标签。在本节的最后,我声明所有的标签都必须关闭。然而,我们刚刚编写的代码没有包含</img>。我忘了吗?
我所做的是在开始标签中关闭我们的标签。注意,在我们的img标签的末尾,>前面有一个正斜杠。如果您不需要在开始标签本身的内容之外添加元素,这是结束标签的另一种方式。
让我们分析一下我们的代码行:
<img id="SZ0_0" src="img/SZ_background_image.jpg" />
我们已经设法将所有关于我们的图像的信息放在开始标记中。不需要额外信息;因此,我们可以通过写/>来结束我们的标签。
如果你想知道,下面是同样有效的:
<img id="SZ0_0" src="img/SZ_background_image.jpg" ></img>
添加剩余的图像
以下图片也需要添加到我们的 HTML 页面:
SZ_gun.pngSZ_reload.pngSZ_score.png
当我们完成游戏时,将会有更多的图像;然而,这就是我们在现阶段所需要的一切。
像以前一样,进入My_Work_Files文件夹的Raw Images文件夹中的images文件夹。找到三个新的.png文件,并将它们复制到你的Images文件夹中,看起来应该是这样的:
现在,重新打开default.html文件,键入以下新行(所有新文本都以粗体显示):
<html>
<head>
</head>
<body>
<div id="SZ_maincontent">
<img id="SZ0_0" src="img/SZ_background_image.jpg" />
<img id="SZ0_1" src="img/SZ_gun.png" />
<img id="SZ0_2" src="img/SZ_reload.png" />
<img id="SZ0_3" src="img/SZ_score.png" />
</div>
</body>
</html>
保存文件,然后关闭它。回到My_Work_Files文件夹,双击default.html文件。
您现在应该会看到三个新图像。您可能需要向下滚动网页。同样,不要担心图像如何出现在页面上。请确保您可以看到之前的背景图像和我们刚刚添加的三个新图像。
在这个阶段,相当多的人问我 HTML5 游戏开发是否和 web 开发人员一样。是的,就像 Xbox 游戏机开发者只是一个 C#/C++表单开发者一样。然而 HTML5 游戏看起来和感觉上都不像一个普通的网站,不是吗?当你开发这个游戏的时候,你会发现一个 HTML5 游戏开发者必须学习作为一个网页开发者的所有知识,甚至更多。你需要弄清楚一个网络开发者的边界在哪里,然后学习为了你的游戏引擎你能把它们推进多远。
在这一章中,我们已经设法将四个初始的图形元素编码到我们的屏幕上。他们可能看起来不太好看,因为他们似乎不在正确的地方,也没有正确的大小。不过不用担心,在下一章中我们将对四张图片应用 CSS,这将使它们准确地对齐我们想要的位置。
三、是时候应用一点 CSS 了
#tower-of-pisa
{
font-style: italic;
}
CSS 代表级联样式表,是一种用于帮助设计网站样式的语言。它可以用来描述页面在颜色、布局和字体方面的外观。
那么为什么我们的游戏需要这个呢?以前,我们把 HTML 想象成我们游戏的骨架或骨骼结构。CSS 代码将成为我们游戏的外观。然而,如果你熟悉构建网站,你可能会想知道 CSS 在 HTML5 游戏开发中实际上扮演了多大的角色。
随着 CSS3 的到来,CSS 中的动画允许浏览器确定哪些元素应该获得 GPU 层,这导致了硬件加速。但是,不要一开始就把所有的动画都转移到 CSS 中。一般来说,让每个元素都有自己的层并不是一个好主意。如果你这样做,那么你的 GPU 将会很快耗尽内存。我相信您会同意,作为开发人员,没有比收到可怕的“内存不足”错误更糟糕的感觉了。
从一个快速测试开始
在我们对齐和调整我们的图像之前,让我们从一个简单的测试开始我们的 CSS 文件。这个测试是为了看看我们是否能把整个页面的背景变成红色。通过这样做,我们将确保default.html页面与我们的 CSS 页面成功通信。
让我们打开SZ_master.css文件。当文件打开时,它应该是完全空白的。键入以下几行:
html {
height: 100%;
}
body {
padding: 0 0 0 0;
margin: 0;
user-select: none;
background-colour: red;
}
您现在可以保存并关闭该文件。
从这段代码中可以看出,CSS 文件的语法由三部分组成。
selector这通常是你想要定义的 HTML<tag>。在前面的代码中,我们将<html>和<body>标记定义为选择器。- 顾名思义,我们在这里定义我们希望对标签的什么属性应用样式。在我们的
<html>示例中,我们将 height 属性定义为 style。 value您希望为属性定义的实际样式。在我们的例子中,我们决定标签的高度是屏幕尺寸的 100%。
有趣的是,您可以通过简单的分组为多个标签指定相同的参数。
在测试之前,我们需要将这个文件链接到我们的default.html文件中。重新打开default.html文件,键入以下新行(粗体):
<html>
<head>
<link href="css/SZ_master.css" rel="stylesheet" />
</head>
<body>
<div id="SZ_maincontent">
<img id="SZ0_0" src="img/SZ_background_image.jpg" />
<img id="SZ0_1" src="img/SZ_gun.png" />
<img id="SZ0_2" src="img/SZ_reload.png" />
<img id="SZ0_3" src="img/SZ_score.png" />
</div>
</body>
</html>
保存文件,然后关闭它。回到My_Work_Files文件夹,双击default.html文件。
您应该会看到与上次相同的屏幕(即四幅图像),但背景是红色而不是白色。这很好,因为这意味着您成功地将 CSS 文件链接到了主 HTML 页面。
我们已经在SZ_master.css文件中介绍了相当多的 CSS 技术。让我们过一遍。
- 这将我们的 HTML 页面的高度设置为 100%。意味着我们的内容应该能够从上到下覆盖可见屏幕。
值得注意的是,我们可以使用属性 min-height 和 max-height 来覆盖 height 属性。
padding: 0 0 0 0;这将清除页面内容周围的一个区域。考虑做一个四边空白,在这里你指定你想要它有多厚。在我们的例子中,我们希望内容覆盖整个页面,所以我们将所有四个边的填充设置为 0。四个 0 分别对应top、right、bottom、left。position: fixed;顾名思义,这是根据浏览器窗口将图像定位到一个固定的位置。所以top: 0;意味着距离浏览器顶部 0 像素(像素是一种度量单位)(也就是说,你希望它固定在顶部)。类似地,bottom: 0;表示您希望图像放在浏览器窗口的底部。最后,left: 0;和right: 0;是指放置在浏览器窗口左侧或右侧的图像。margin:0;边距设置元素周围空白的大小。在我们的例子中,我们不希望屏幕边缘有任何空白。- 通过使用
user-select属性,我们可以控制玩家如何与屏幕上的文本元素交互。在这种情况下,它被设置为none,这意味着我们不希望用户选择或单击任何文本元素。这样做的原因是,它可能会分散玩实际游戏的注意力(例如,允许用户选择高分的文本)。 background-colour: red;顾名思义,这个设置屏幕的背景颜色。如果你愿意,试着用yellow或者你选择的任何颜色来代替red这个词。保存文件并刷新浏览器。
我们还在 HTML 文件中添加了另一行代码:
<link href="css/SZ_master.css" rel="stylesheet" />
标签是在页面上包含 CSS 文件的标准方式。href指定了我们希望包含的 CSS 文件的位置。rel标签指定了 HTML 文件和 CSS 文件之间的关系。在这种情况下,CSS 文件充当 HTML 文件的样式表。
我们的背景图像
让我们开始修复图像。我们将从背景图像开始。理想情况下,我们希望这个图像填充我们的页面(就像红色背景颜色一样)。
打开SZ_master.css文件,输入以下新行(所有新文本以粗体显示):
html {
height: 100%;
}
body {
padding: 0 0 0 0;
margin: 0;
user-select: none;
}
img {
max-width: 100%;
height: auto;
user-drag: none;
user-select: none;
-moz-user-select: none;
-webkit-user-drag: none;
-webkit-user-select: none;
-ms-user-select: none;
}
#SZ0_0 {
position: fixed;
top: 0;
left: 0;
min-width: 100%;
min-height: 100%;
}
保存文件,然后关闭它。回到My_Work_Files文件夹,双击default.html文件。
请注意,我们已经从body标签中移除了background-colour: red;。请确保从代码中删除该行。您的文件看起来应该与显示的完全一样。
您可能想知道为什么我们用四种不同的方式对user-select属性进行编码。第一种方法是 CSS 中的标准属性(即user-select)。然后,我们继续定义由各种渲染引擎提供的厂商前缀属性。这允许针对每个单独的浏览器引擎设置特定的属性,以安全地解决实现之间的不一致。
以下是我们使用的供应商前缀属性:
webkit对于 Chrome 和 Safarimoz对于火狐来说ms对于 Internet Explorer
历史上,在 W3 最终澄清之前,我们使用这些前缀来实现新的 CSS 特性。因此,随着时间的推移,属性的最终版本将移除前缀。
保存文件,然后关闭它。回到My_Work_Files文件夹,双击default.html文件。
你的屏幕应该看起来像下面的截图。
您应该首先注意到背景图像现在覆盖了整个屏幕。另外,其他三个图像完全从屏幕上消失了。别担心。它们仍然在那里——在背景图像后面。
添加到 CSS 中的第一个样式是用于标签的。这意味着定义的样式适用于我们添加到页面中的每一幅图像。我相信你会同意这是一个节省大量时间的技术,因为另一种选择是为我们添加的每张图片重复费力的样式。
无论如何,不是每个图像都需要相同的风格。您可以看到第二种样式是专门为一个图像标签编写的,该标签被标识为#SZ0_0。
我们放在<img>标签中的样式是更通用的样式,应该适用于所有图像。然后,我们可以为特定的图像添加单独的样式,并添加更多的样式。我们甚至可以覆盖写在<img>标签中的样式。
在我们离开这之前,我们为什么把标签叫做#SZ0_0?如果回到第 2.3 节,请注意以下几点:
<img id="SZ0_0" src="img/SZ_background_image.jpg" />
该图像被标识为SZ0_0。在 CSS 中,您可以通过在 ID 前放置散列符号(#)来识别图像。
让我们来看看我们使用的新 CSS 技术。
max-width: 100%; height: auto;我们希望图像伸展到其容器的整个宽度。此外,我们希望代码在应用新宽度时自动确定高度。这确保我们在调整大小时保持图像的纵横比。user-drag: none;我们不希望用户能够拖动屏幕上的图像。- 这些是 CSS 扩展,是网络浏览器支持的属性,但还不是官方 CSS 规范的一部分。
top: 0; left: 0;设置图像的上边缘和左边缘。在这种情况下,我们希望图像始终位于其容器的左上角。- 顾名思义,我们希望图像的最小宽度和高度是其容器的最大尺寸。
我们的其他图像
我们可以开始修复其他三幅图像。首先,这里有一个关于图像和它们应该放在哪里的提示:
SZ_gun枪的图像应该在屏幕的右下角。SZ_reload重新加载按钮应该出现在屏幕的左上角。SZ_score得分图像应该出现在屏幕的右上角。
现在打开SZ_master.css文件,键入以下新行(所有新文本都以粗体显示):
html {
height: 100%;
}
body {
padding: 0 0 0 0;
margin: 0;
user-select: none;
}
img {
max-width: 100%;
height: auto;
user-drag: none;
user-select: none;
-moz-user-select: none;
-webkit-user-drag: none;
-webkit-user-select: none;
-ms-user-select: none;
}
#SZ0_0 {
posi
tion: fixed;
top: 0;
left: 0;
min-width: 100%;
min-height: 100%;
}
#SZ0_1 {
position: fixed;
bottom: 0;
right: 0;
}
#SZ0_2 {
position: fixed;
top: 0;
left: 0;
}
#SZ0_3 {
position: fixed;
top: 0;
right: 0;
}
保存文件,然后关闭它。
在这段代码中,我们为三幅图像中的每一幅定义了三个属性。但是,请注意,这些属性及其后续值是完全相同的。前面,我提到了这样一个事实,即我们可以通过简单地对多个标签进行分组来为它们指定相同的参数。因此,如果您愿意,可以尝试使用前面的代码,将粗体代码替换为以下代码:
#SZ0_1, #SZ0_2, #SZ0_3 {
position: fixed;
top: 0;
right: 0;
}
回到My_Work_Files文件夹,双击default.html文件。
您的屏幕应该看起来像下面的截图。
虽然你现在可以看到所有四个图像的对齐位置,但它们的大小不太合适;但是,不要担心这个。在下一章,我们将使用 JavaScript 来调整图像的大小。
position属性指定用于元素的定位方法的类型(静态、相对、固定或绝对)。然后使用 top、right、bottom 和 left 属性定位元素。但是,除非首先设置了position属性,否则这些属性将不起作用。根据位置值的不同,它们的工作方式也不同。
让我们简单看一下四个位置值。
static元素不受上、右、下、左属性的影响。relative表示设置一个相对定位的元素的顶部、右侧、底部和左侧属性,导致它被调整远离其正常位置。fixed表示相对于视窗定位,这意味着即使页面滚动,它也总是停留在同一位置。top、right、bottom 和 left 属性用于定位元素。absolute表示相对于最近的祖先定位(而不是相对于视口定位,如 fixed)。
在我们的例子中,我们使用了fixed和bottom: 0; right: 0;。在上一节中,我们设置了图像的左上角;而在这里,我们可以从图像容器的右下角设置图像。
因为我们需要枪总是位于屏幕的右下角,所以在这种情况下使用 bottom-right 属性比使用 top-left 属性更有意义。
四、使用 JavaScript 应用智能
“总是把维护你代码的人想象成一个知道你住在哪里的暴力精神病患者。”
里克·奥斯本
如你所知,HTML 是我们游戏的骨架,CSS 是我们游戏的外观。那么 JavaScript 带来了什么呢?JavaScript 是一种用于在网站中创建交互性的编程语言。所以我们可以说我们使用 JavaScript 作为游戏的主控制器。
那么为什么我们的游戏需要它呢?显而易见的答案是,游戏需要能够创造僵尸,开枪,并响应用户的命令。这是事实,但是游戏需要 JavaScript 来执行大量的其他工作。例如,在前一章中,我们发现需要根据浏览器的大小来调整图像的大小。现在让我们用 JavaScript 来做这件事。
为什么我们需要调整大小?
我们的游戏可以在多种设备上玩;电脑,笔记本电脑,移动设备,平板电脑,甚至连接到大电视的控制台。在这些设备中,有许多不同的屏幕尺寸。手机和笔记本电脑有各种各样的屏幕尺寸。
让我们更进一步。如果有人调整了互联网浏览器窗口的大小,该怎么办?现在我们正在谈论无限多的组合。
为一切可以想象的事物创建图形将是极其耗时的。事实上,这是不可能的,因为似乎总是有新型号的手机(因此,新的屏幕尺寸)或新的电脑显示器问世。因此,我们需要找到一种通用的方法来调整任何屏幕尺寸的图像。
我们如何普遍调整大小?
如果你手边有一把 30 厘米的尺子,那就看看吧。想象我们为 15 厘米标记设计游戏。我们可以使用 JavaScript 来告诉我们标尺上的实际尺寸。所以我们假设它返回为 10 厘米。然后,我们可以计算出一个比率(即 10 除以 15),该比率可用于所有测量;十除以十五是 0.67。这意味着当我们将它应用到我们的图像时,它们会变得更小,这正是我们想要的。同样,如果尺寸变大了,比如说 20 厘米,比例会反映这一点,并使所有图像比我们设计的要大。
让我们写一个函数来计算这个比率。打开SZ_main.js。这个文件应该是完全空白的。键入以下几行:
//global vars
//need to store the ratio
var ratio;
//need easy access to the width
var newWidth;
//function that gets called when game starts
$(window).load(function () {
//need to grab an instance of our screen
var div = $(window);
//we can now work out the ratio
ratio = (div.width() / 1024);
//while we are here we can grab the width for future use
newWidth = div.width();
});
您现在可以保存并关闭该文件。
各种 JavaScript 术语的定义可以在下面的进一步信息部分找到。另一件要注意的事情是,我试图输入尽可能多的注释来解释我们的代码行(正如您在自己的编程语言中所习惯的那样)。
在测试之前,我们需要将这个文件链接到我们的default.html文件。因此,让我们重新打开default.html并键入以下三个新行(所有新文本都以粗体显示):
<html>
<head>
<script src="js/jquery.js"></script>
<script src="js/jquery-ui.js"></script>
<script src="js/SZ_main.js"></script>
<link href="css/SZ_master.css" rel="stylesheet" />
</head>
<body>
<div id="SZ_maincontent">
<img id="SZ0_0" src="img/SZ_background_image.jpg" />
<img id="SZ0_1" src="img/SZ_gun.png" />
<img id="SZ0_2" src="img/SZ_reload.png" />
<img id="SZ0_3" src="img/SZ_score.png" />
</div>
</body>
</html>
保存文件,然后关闭它。
这三个新行包含了<script>标签。在我们的例子中,我们选择通过src属性将这个标签链接到一个外部脚本文件。或者,我们可以使用相同的标记来定义包含脚本语句的客户端脚本。
现在双击default.html文件。
什么都没有改变,是吗?这是因为 JavaScript 代码在后台执行任务。此外,我们还没有告诉它对我们的图像做任何事情。我们所做的只是在代码中存储一个值(即比率)。
尽管如此,看看我们的第一部分代码是否正常工作还是不错的。让我们添加一行代码,在屏幕上显示一个消息框。在这个框中,我们将输入代码刚刚计算出的比率值。这并不十分令人兴奋,但至少我们从代码中获得了某种形式的反馈。这太令人兴奋了。
现在打开SZ_main.js文件,键入以下新行(粗体部分):
//global vars
//need to store the ratio
var ratio;
//need easy access to the width
var newWidth;
//function that gets called when game starts
$(window).load(function () {
//need to grab an instance of our screen
var div = $(window);
//we can now work out the ratio
ratio = (div.width() / 1024);
//while we are here we can grab the width for future use
newWidth = div.width();
//We are adding in a temporary bit of code here
window.alert("Hi this is your code and I have just worked out that the ratio will be "+ratio);
});
我们现在可以保存并关闭该文件。
我们刚刚使用了window.alert()方法,它通常显示一个带有指定消息和 OK 按钮的警告框。通常,警告框用于确保重要信息显示给用户。在我们的例子中,我们使用警告框来通知我们一个变量的值,在我们的代码中,这个值是我们无法访问的。
我应该注意到,警告框通过迫使浏览器阅读消息,将焦点从当前窗口移开。我不建议过度使用这种方法,因为它会转移用户玩游戏的注意力,直到盒子关闭。
回到我们的My_Work_Files文件夹,双击default.html文件。您应该会看到我们的网站有一个消息框:
我们这里有我们的代码与我们交谈。它告诉我们,它计算出的比值。
在继续之前,我们需要删除刚刚添加的两行。打开SZ_main.js文件。删除这两行代码后,代码应该再次显示如下:
//global vars
//need to store the ratio
var ratio;
//need easy access to the width
var newWidth;
//function that gets called when game starts
$(window).load(function () {
//need to grab an instance of our screen
var div = $(window);
//we can now work out the ratio
ratio = (div.width() / 1024);
//while we are here we can grab the width for future use
newWidth = div.width();
});
在这本书里,你会创造出许多奇妙的功能,毫无疑问,在你继续开发的游戏里也是如此。然而,我希望你们珍惜这一刻,就像我在 1994 年学习 Pascal 编码时一样。
窗口框没有出现吗?别担心。以下建议之一应该会有所帮助:
- 返回并重新检查每一行代码是否相同
- 你是否漏掉了行尾的分号(
;)? - 您是否确保在 HTML 文件中添加了三行代码?
js文件夹中的九个文件是否都在它们应该在的位置?
如果你的代码仍然不工作,那么请不要犹豫,在 Twitter 上给我发消息@zarrarchishti。
接下来,我们将让这个比例发挥作用。
什么是函数?
我们会用 JavaScript 写很多函数。函数只是一组指令,当函数本身被调用运行时执行这些指令。所以当我们的函数被调用时,它决定了比例并存储了屏幕的宽度。
为什么//开头的台词写的像会话英语?
当你用双正斜杠(//)开始一行时,你是在告诉计算机忽略这一行。你为什么要这么做?它是为我们准备的,叫做评论热线。它的目的是给我们自己(或者其他程序员)留言。通过留下信息,我们打破了代码,使整个程序更容易阅读。你可以写任何你喜欢的东西。我喜欢用它来解释为什么注释后的代码是原来写的。
您不必注释每一行;然而,我总是被教导要注释尽可能多的代码行。对于一些程序员来说,这可能显得有些过火;然而,我发现当我几年后回到我的代码时,我写的注释帮助我理解了代码背后的推理。
为什么我们将另外两个文件添加到我们的 HTML 文件中?
说到添加SZ_main.js文件,我们还添加了 jQuery 和 jQuery-UI 文件。这些基本上都是高级函数(就像你写的那个)。只要我们使用它们的函数,我们所要做的就是将它们添加到我们的 HTML 中。
这些功能快速、可靠且功能丰富。世界上一些最大的公司使用它们,像我们这样的小游戏开发者也使用它们。
现在让我们看看我们写的一些 JavaScript 代码。
var ratio;我们在这里声明一个叫做ratio的变量。变量是可以存储数据的容器。我们可以将数据转换成比率并从中读取。- 一旦整个页面加载完毕,这个函数就会被调用。这使得它非常有用,因为这个函数内部的指令要求元素(例如,图像)出现并加载到屏幕上。
var div= $(window);正如我们之前发现的,var创建了一个容器来存储数据。然而,在这种情况下,我们使用它来传递整个窗口的实例。这个名为div的变量现在包含了所有关于我们窗口的重要信息。例如,我们继续使用下面的语句。newWidth= div.width();这意味着我们可以将窗口的宽度存储在名为newWidth的变量中。
让我们调整图像的大小
提醒一下,这些是理想尺寸的图片:
SZ_gunWidth 133px和Height 150pxSZ_reloadWidth 200px和Height 90pxSZ_scoreWidth 235px和Height 100px
打开js文件夹中的SZ_setupContent.js文件。当文件打开时,它应该是完全空白的。键入以下几行:
//main function
function main_call_setupContent() {
//need to resize all elements
//first we set their normal sizes in CSS
//Gun
$('#SZ0_1').css('width', 150 * ratio);
$('#SZ0_1').css('height', 150 * ratio);
//Reload Button
$('#SZ0_2').css('width', 200 * ratio);
$('#SZ0_2').css('height', 90 * ratio);
//Score
$('#SZ0_3').css('width', 235 * ratio);
$('#SZ0_3').css('height', 100 * ratio);
}
保存并关闭该文件。
在某些时候,您可能希望重新访问这个函数并重新编码这个流。我建议您将图像 id 的值放入一个数组中;例如,
var image_ids= ["#SZ0_1","#SZ0_2","#SZ0_3"];
然后,您还需要将每个图像的值放入另一个数组中;例如,
var image_sizes = [ [150, 150], [200, 90], [235, 100] ];
然后,您可以编写一个for loop来执行相同的代码三次,用 ID 数组中的下一个值替换 ID,用 size 数组中的值替换宽度和高度值。
打开SZ_main.js文件,输入以下新行(新文本以粗体显示):
//global vars
//need to store the ratio
var ratio;
//need easy access to the width
var newWidth;
//function that gets called when game starts
$(window).load(function () {
//need to grab an instance of our screen
var div = $(window);
//we can now work out the ratio
ratio = (div.width() / 1024);
//while we are here we can grab the width for future use
newWidth = div.width();
//let’s apply the ratio to our elements
main_call_setupContent();
});
在测试之前,我们需要将这个文件链接到我们的default.html文件。重新打开default.html文件并输入以下行(新文本以粗体显示):
<html>
<head>
<script src="js/jquery.js"></script>
<script src="js/jquery-ui.js"></script>
<script src="js/SZ_main.js"></script>
<script src="js/SZ_setupContent.js"></script>
<link href="css/SZ_master.css" rel="stylesheet" />
</head>
<body>
<div id="SZ_maincontent">
<img id="SZ0_0" src="img/SZ_background_image.jpg" />
<img id="SZ0_1" src="img/SZ_gun.png" />
<img id="SZ0_2" src="img/SZ_reload.png" />
<img id="SZ0_3" src="img/SZ_score.png" />
</div>
</body>
</html>
回到My_Work_Files文件夹,双击default.html文件。现在,您应该可以看到三个元素的大小已经调整,如下面的屏幕截图所示:
恭喜你!我们现在已经完成了游戏的第一部分!我们将在接下来的章节中进一步开发这个游戏。然而,主要的构建模块已经完成。从现在开始,我们将添加更多的 HTML,更多的 CSS,是的,甚至更多的 JavaScript,直到我们的游戏最终可以玩。
理想的尺寸从何而来(比如枪:宽 175px,高 200px)?
在任何开发可以开始之前采取的步骤是每个屏幕的布局设计。这是你在屏幕上放置元素的地方,比如枪。
你首先需要选择一个正常的尺寸。在我们的例子中,我们选择了屏幕宽度 1025px 和高度 800px。当然,用户拥有这种精确屏幕尺寸的可能性非常小。这也是我们之前算出比例的原因。
您可以使用任何软件设计程序(如 Macromedia Photoshop 或 Fireworks)来创建布局文件。一旦我们创建了大小为 1024 × 800 的新画布,我们就可以调整元素的大小并将其重新定位在画布上我们想要的位置。例如,我们把枪放在右下角,宽 175 像素,高 200 像素。
现在,我们可以将我们的比例应用到宽度和高度,以获得所使用的屏幕的准确大小。
我们用 JavaScript 创建了如下函数:
function main_call_setupContent(){需要注意的是,在我们调用这个函数之前,这个函数中的指令不会被执行,这是我们在SZ_main.js中通过调用main_call_setupContent();完成的。main_call_setupContent();现在指令才被程序执行。
最后,让我们看看下面一行代码:
- 我们可以直接从 JavaScript 中操作一个元素的 CSS。这是游戏开发中一个非常强大和有用的工具。例如,如果我们希望一个元素在拍摄后变大,我们可以直接从用于识别按钮点击的 JavaScript 代码中完成。
五、尝试一下:第一部分
代码从不说谎,但注释有时会说谎
罗恩·杰弗里斯
在这一章中,我们将研究我们想要我们的枪做什么。当用户按下屏幕上的任何地方,除了重新加载按钮,它应该被视为一个镜头。射击是如何发生的以及射击的后果在第七章中讨论。现在,让我们看看对用户点击的反应。
将会有一些令人敬畏的技术被使用,包括用于动画的精灵表和用于流体运动的数学。当你将来开发其他游戏时,你可能会发现自己会回来重用这些功能和技术。这正是商业游戏开发中发生的事情。
顺便说一下,这一章中的一些代码来自我最近为一个儿童游戏做的一个项目,这个项目在格拉斯哥的 Kelvingrove 艺术画廊和博物馆举办。
改变我们的光标并记录一次点击
在我们正在开发的射击游戏中,鼠标光标通常会变成十字准线。
只需使用 CSS 就可以改变光标。打开我们的CSS文件夹中的SZ_master.css文件。键入以下新行(粗体):
html {
height: 100%;
}
+
body {
padding: 0 0 0 0;
margin: 0;
user-select: none;
cursor: crosshair;
}
img {
max-width: 100%;
height: auto;
user-drag: none;
user-select: none;
-moz-user-select: none;
-webkit-user-drag: none;
-webkit-user-select: none;
-ms-user-select: none;
}
#SZ0_0 {
posi
tion: fixed;
top: 0;
left: 0;
min-width: 100%;
min-height: 100%;
}
#SZ0_1 {
position: fixed;
bottom: 0;
right: 0;
}
#SZ0_2 {
position: fixed;
top: 0;
left: 0;
}
#SZ0_3 {
position: fixed;
top: 0;
right: 0;
}
保存文件,然后关闭它。回到我们的My_Work_Files文件夹,双击default.html文件。
请注意,鼠标光标已从箭头变为十字光标。
我们刚刚写的那行是什么?
cursor: crosshair;指定用鼠标点击时显示的光标类型。
您可能想知道还有哪些其他类型的光标可供您使用。以下是光标类型的列表。如果您愿意,可以将 CSS 文件中的单词 crosshair 替换为这些单词中的任何一个。
- 电子调整大小
- 移动
- 西北-调整大小
- s-调整大小
- 文本
- 不掉线
- 夺取
- n-调整大小
- 指针
- se-调整大小
- w-调整大小
- 不允许
- 帮助
- 重新调整大小
- 进步
- SW-调整大小
- 等待
让我们的枪更真实
对用户来说,游戏越吸引人,他们就越喜欢一遍又一遍地玩。增加用户参与度的方法之一是在游戏中加入一些小细节。例如,如果当用户在屏幕上移动光标时,枪会做出反应,这不是很好吗?
为此,我们将使用 JavaScript。打开js文件夹中的SZ_movement.js文件。当文件打开时,它应该是完全空白的。键入以下几行:
function rotateGun(e) {
//using the e value we can deduce the X co-ordinates
var xPos = e.clientX;
//We need to work out where the mouse cursor is as a percentage of the width of the screen
//We will work this out by dividing the current X position by the overall screen width which if you remember we put in newWidth
var currentXPositionPercentage = xPos/newWidth;
//We now want to apply this
to the maximum amount of rotation which is 50 however the starting rotation is -15 not 0
var amountToRotate = -15 + (currentXPositionPercentage * 50);
//Let’s rotate the gun!
$("#SZ0_1").css('transform', 'rotate('+amountToRotate+'deg)');
}
我们现在可以保存并关闭该文件。
下面的“更多信息”一节详细解释了该代码。
在测试之前,我们需要将这个文件链接到我们的default.html文件。重新打开default.html文件,并在我们现有的一行中键入以下新行和额外的文本(所有新文本都以粗体显示):
<html>
<head>
<script src="js/jquery.js"></script>
<script src="js/jquery-ui.js"></script>
<script src="js/SZ_main.js"></script>
<script src="js/SZ_setupContent.js"></script>
<script src="js/SZ_movement.js"></script>
<link href="css/SZ_master.css" rel="stylesheet" />
</head>
<body>
<div id="SZ_maincontent">
<img id="SZ0_0" onmousemove="rotateGun(event)" src="img/SZ_background_image.jpg" />
<img id="SZ0_1" src="img/SZ_gun.png" />
<img id="SZ0_2" src="img/SZ_reload.png" />
<img id="SZ0_3" src="img/SZ_score.png" />
</div>
</body>
</html>
保存文件,然后关闭它。现在双击default.html文件。
试着沿着屏幕移动鼠标。枪应该旋转,就好像瞄准我们要射击的目标。我相信你会同意这比一把静止的枪更吸引人。
我想讨论一下前面代码中的一个有趣的地方。onmousemove顾名思义,当用户将鼠标移动到图像上时,触发一个 JavaScript 函数。它不会在手机等触摸屏设备上触发。您可能希望将来重新访问这部分代码,并修改它,以便当用户触摸图像的任何部分时它都会触发。
接下来,我们将看看使枪开火!
代码不起作用吗?其中一行不同于我们通常添加代码的方式。让我们一起经历这一切:
打开default.html文件。
找到以开始的行
<img id="SZ0_0" onmousemove="rotateGun(event)" src="img/SZ_background_image.jpg" />
你是否完全按照显示的那样添加了额外的文本?
添加以下案文:
onmousemove="rotateGun(event)"
在...之间
id="SZ0_0" and src="
如果你的代码仍然不工作,那么请不要犹豫,在 Twitter 上给我发消息@zarrarchishti。
我们用 JavaScript 写了下面一行:
var xPos = e.clientX;
按如下方式将e传递给我们的函数:
function rotateGun(e) {
e包含已发生事件的所有信息。在本例中,是鼠标在我们的图像上移动,我们声明如下:
<img id="SZ0_0" onmousemove="rotateGun(event)" src="img/SZ_background_image.jpg" />
当用户将鼠标移动到这个图像上时,我们的rotateGun函数被调用,移动的数据被传入。
在第一行中,您可以看到我们从e中提取了“clientX”。这是刚刚发生的鼠标事件的水平坐标(通常称为 x 轴)。
那么这个 X 轴是什么?试着把你的屏幕从左到右的用户动作想象成 X 轴。下图说明了我们想要的 X 轴和枪的旋转之间的关系。
最左边和最右边的枪之间的最大旋转是 50 度。因此,通过找出我们在屏幕上的确切位置,我们可以使用一个数学方程来确定枪的确切旋转角度。
还有一点:我们用来实际旋转枪的代码是通过使用 JavaScript 和 CSS 来完成的。JavaScript 通过执行以下操作来完成大部分工作:
- 每次用户移动鼠标时提醒一个功能
- 已确定鼠标移动了多少
- 将上面的值应用到我们的数学方程中
从这里,我们把这个值交给 CSS,然后它实际上旋转枪。
随意摆弄数字,测试出现的不同旋转;例如,更改以下行
var amountToRotate = -15 + (currentXPositionPercentage * 50);
把 50 改成 100。你会注意到枪的移动方式有了更大的变化。不断改变数字,直到你达到你满意的旋转水平。如果您想返回,只需键入前面的代码。
最后,我们在下面一行中遇到了另一个在 JavaScript 中操作 CSS 的例子:
$("#SZ0_1").css('transform', 'rotate('+amountToRotate+'deg)');
顾名思义,transform属性将变换应用于任何元素。其他转换包括scale、move和skew。
用精灵表制作枪的动画
当用户点击屏幕时,我们想让我们的枪开火。为了做到这一点,我们将使用一种叫做 sprite sheets 的东西。在开始添加 sprite 工作表之前,我们需要编写一些代码。这是因为 sprite 工作表不是你的浏览器(例如 Chrome)可以像处理我们目前使用的图片那样单独处理的。
我们需要编写一些代码来指导浏览器如何处理我们的 sprite 工作表图像。为此,我们将使用 JavaScript。提醒一句,这段代码比你目前所写的稍微长一点。我鼓励你坚持下去,因为这个特殊的函数可以在你做的每个项目中重用,而不需要改变代码中的任何东西。请确保您完全按照所示复制所有代码。
第一部分
打开js文件夹中的SZ_SS.js文件。当文件打开时,它应该是完全空白的。键入以下几行:
//We need a one stop function that will allow us to process sprite sheets
function setup_SpriteSheet(div_name, image_name, no_of_frames, widthx, heightx) {
//need the ratio of the container's width/height
var imageOrgRatio = $(div_name).height() / $(div_name).width() ;
//need to ensure no trailing decimals
var ratio2 = Math.round(ratio * 10) / 10;
//check that the width is completely divisible by the no of frames
var newDivisible = Math.round((widthx * ratio2) / no_of_frames);
//the new width will be the number of frames multiplied by our new divisible
var newWidthx = newDivisible * no_of_frames;
//also the new height will be our ratio times the height of the div containing our image
var newHeightx = heightx * ratio2;
//apply our new width to our CSS
$(div_name).css('width', (newWidthx));
//apply our new height to our CSS
$(div_name).css('height', newHeightx);
//
//take the image name and apply as a background image to our div
$(div_name).css('background-image', 'url(' + image_name + ')');
//finally we need to apply a background size remembering we need to multiply width by the number of frames
$(div_name).css('background-size', newWidthx * no_of_frames + 'px ' + newHeightx + 'px');
}
最初,我只是想添加一个标准的 sprite 工作表库;但是,通过我们自己编码,我们在未来的游戏中有更多的灵活性。随着你构建更多的游戏,你会发现并不是所有的 sprite sheet——或者使用它们的所有参数——都是相同的。因此,您需要重新访问前面的函数并对其进行调整,以确保它适合您当前的项目。如果我们只使用一个标准函数,它将严重限制你可以使用的 sprite 表的类型。
与所有其他标准函数一样,只要我们将来将这个文件链接到任何 HTML 文件,就可以使用我们的小函数。
什么是雪碧床单?
sprite sheet 是一种特殊的图像,它在平铺的网格排列中包含几个图像。
那么为什么要用雪碧床单呢?
Sprite sheets 让游戏运行得更快,更重要的是,占用更少的内存。通过将几个图形编译成一个文件,你可以让你的游戏在只需要加载一个文件的时候就可以使用这些图形。
雪碧床单是怎么设计的?
我们的精灵表有三个部分。第一,正常的静态是枪重新装弹的时候,枪开火的时候。下面说明了这一点:
为什么我们需要编写自己的特殊函数来使用 sprite 工作表?
有许多方法来处理雪碧表。每个程序员设计他们的代码来操作适合他们的 sprite 表。我在这里使用了一个非常简单的方法,处理线性布局的精灵。
此外,我们的游戏不需要复杂的使用任何精灵表。由于您正在编写所有的代码,我想确保您只需要编写最少的代码。然而,你可以使用这些代码作为你下一个游戏的基础,并在你认为必要的时候在其上进行构建。
第二部分
既然我们已经设置了处理任何 sprite 表的函数,我们可以用我们的枪来测试它。首先,我们需要用 sprite sheet 版本替换我们枪的静态图像。
转到My_Work_Files文件夹的Raw Images文件夹中的images文件夹。将名为SZ_gun_SS.png的文件复制到Images文件夹,现在看起来应该像下面的截图。
第三部分
接下来,我们需要通知代码枪是一个 sprite 表,并传递关于它的所有信息(例如,您复制的图像名称)。
我们将使用 JavaScript 来做到这一点。重新打开js文件夹中的SZ_SS.js文件。键入以下新行(所有新文本都以粗体显示):
//We need a one stop function that will allow us to process sprite sheets
function setup_SpriteSheet(div_name, image_name, no_of_frames, widthx, heightx) {
//need the ratio of the container's width/height
var imageOrgRatio = $(div_name).height() / $(div_name).width() ;
//need to ensure no trailing decimals
var ratio2 = Math.round(ratio * 10) / 10;
//check that the width is completely divisible by the no of frames
var newDivisible = Math.round((widthx * ratio2) / no_of_frames);
//the new width will be the number of frames multiplied by our new divisible
var newWidthx = newDivisible * no_of_frames;
//also the new height will be our ratio times the height of the div containing our image
var newHeightx = heightx * ratio2;
//apply our new width to our CSS
$(div_name).css('width', (newWidthx));
//apply our new height to our CSS
$(div_name).css('height', newHeightx);
//
//take the image name and apply as a background image to our div
$(div_name).css('background-image', 'url(' + image_name + ')');
//finally we need to apply a background size remembering
we need to multiply width by the no of frames
$(div_name).css('background-size', newWidthx * no_of_frames + 'px ' + newHeightx + 'px');
}
//setup the Gun
function setup_gun_SS(){
//first let’s setup our gun SS
setup_SpriteSheet("#SZ0_1","img/SZ_gun_SS.png",28,150,150);
//need to access a special function in our js/ss.js file
$("#SZ0_1").animateSprite({
fps: 10,
animations: {
static: [0],
reload: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23],
fire: [24,25,26,27,28],
},
duration: 50,
loop: false,
complete: function () {
// use complete only when you set animations with 'loop: false'
//alert("animation End");
}
});
}
保存并关闭文件。
为一个特定的动画编写所有不同的帧会变得非常乏味。想象一下,如果你有超过 500 帧!将来,当重新访问animateSprite函数时,将其更改为取一系列值。您也可以尝试编写函数来获取一组范围;例如,帧(1 到 7、9 到 11 和 29 到 31)。
在测试之前,我们需要对 HTML 文件进行以下两项更改:
- 在头部引用新的 JavaScript 文件
- 将图像包含在它们自己的
div中
重新打开default.html文件并键入以下新行。请小心替换现有的代码行,使整个文件看起来像下面的代码(所有新文本都以粗体显示):
<html>
<head>
<script src="js/jquery.js"></script>
<script src="js/jquery-ui.js"></script>
<script src="js/SZ_main.js"></script>
<script src="js/SZ_setupContent.js"></script>
<script src="js/SZ_movement.js"></script>
<script src="js/ss.js"></script>
<script src="js/SZ_SS.js"></script>
<link href="css/SZ_master.css" rel="stylesheet" />
</head>
<body>
<div id="SZ_maincontent">
<img id="SZ0_0" src="img/SZ_background_image.jpg" onmousemove="rotateGun(event)" />
<div id="SZ0_1" ></div>
<div id="SZ0_2" >
<img src="img/SZ_reload.png" />
</div>
<div id="SZ0_3" >
<img src="img/SZ_score.png" />
</div>
</div>
</body>
</html>
在本节中,我们用下面的代码设置了第一个 sprite 表:
setup_SpriteSheet
("#SZ0_1","img/SZ_gun_SS.png",28,150,150);
让我们分别考虑括号中的每个参数。
#SZ0_1是图像 IDimg/SZ_gun_SS.png是精灵工作表的位置28是我们的 sprite 工作表中包含的图像总数150,150是 sprite 工作表中每个单独图像的大小
您可能已经注意到,我们对图像应用了一个特殊的功能。让我们仔细看看这个函数的每一行。
fps:我们希望应用于精灵工作表动画的理想每秒帧数- 我们可以将一个 sprite 表中的图像细分成单独的动画
duration:我们希望每个动画运行的时间长度(以毫秒为单位)loop:一旦动画结束,我们希望动画重复播放还是停止播放?complete: function () {如果loop选项设置为假(即不重复),那么一旦动画完成,我们可以给出一组要执行的指令。
第四部分
最后,我们需要确保我们调用的是setup_gun_SS函数。我们可以在SZ_setupContent文件中这样做,它初始化我们所有的图像。
打开SZ_setupContent.js文件,输入以下新行(所有新文本以粗体显示)。
//main function
function main_call_setupContent() {
//need to resize all elements
//first we set their normal sizes in CSS
//Gun
$('#SZ0_1').css('width', 150 * ratio);
$('#SZ0_1').css('height', 150 * ratio);
//Reload Button
$('#SZ0_2').css('width', 200 * ratio);
$('#SZ0_2').css('height', 90 * ratio);
//Score
$('#SZ0_3').css('width', 235 * ratio);
$('#SZ0_3').css('height', 100 * ratio);
//Any sprite sheets?
//Our Gun
setup_gun_SS();
}
我们现在准备测试!但是,不要期望太高,因为我们最初告诉代码只显示第一张图片。因此,让我们测试一下,确保我们的代码按预期运行。
保存所有文件,然后关闭它们。回到My_Work_Files文件夹,双击default.html文件。枪应该和以前一模一样。事实上,整个屏幕看起来应该是一样的。这很好,因为我们已经用一个 sprite 表替换了静态的枪图像,并告诉它显示第一个图像。
接下来,我们看看如何使用我们编写的代码来制作枪支重新上膛的动画。
为什么我的屏幕看起来和以前一样?
这是个好消息。做了这么多工作之后,我想期待有所不同是很自然的。也许是一些像开枪这样的动画。
事实上,尽管我们删除了枪的图像并用大规模的 sprite sheet 图像替换它,但一切看起来都很正常,这正是我们希望从代码中得到的。
枪已经不在屏幕上了。
由于这是很大一部分代码,下面是一些可能发生的典型编码错误的建议:
- 回顾每一行代码,确保它与书中所写的一致。
- 检查您是否将
}符号放置在指示的位置。 - 确保
SZ_gun_SS.png在images文件夹中。 - 确保在 HTML 文件的头部包含了两个新的 JavaScript 文件(即
ss.js和SZ_SS.js)。
这把枪看起来不对劲。
要么是枪看起来比它应该的要大得多,要么看起来像是图像的一部分被切掉了。这意味着枪的精灵表的设置方式有问题;特别是SZ_SS.js文件中的setup_gun_SS()函数。请重新检查您的代码,确保所有代码行都完全如所示。
如果你的代码仍然不工作,那么请不要犹豫,在 Twitter 上给我发消息@zarrarchishti。
给我们的枪装子弹
我们需要专注于重新装弹的两个方面:原因和结果。原因是用户点击了屏幕上的重新加载图像。效果是枪从 sprite 表中激活适当的图像。
打开js文件夹中的SZ_touch.js文件。当文件打开时,它应该是完全空白的。键入以下几行:
//this function is called to reload our gun
function reloadGun(e) {
//play the reload animation of our SS
$("#SZ0_1").animateSprite("play", "reload");
}
保存并关闭该文件。
当重新访问这个项目时,为重新加载序列提供选项是一个好主意。如果枪是空的,我建议一个较长的序列,如果枪不是空的,我建议一个较短的序列。您需要定义两个 reload 函数,然后在调用 reload 函数之前检查枪的状态。这样,你就是在奖励用户在枪没子弹之前重新装弹!
在测试之前,我们需要将这个文件和函数链接到default.html文件。重新打开default.html文件,键入以下新行和对现有行的添加(全部以粗体显示):
<html>
<head>
<script src="js/jquery.js"></script>
<script src="js/jquery-ui.js"></script>
<script src="js/SZ_main.js"></script>
<script src="js/SZ_setupContent.js"></script>
<script src="js/SZ_movement.js"></script>
<script src="js/ss.js"></script>
<script src="js/SZ_SS.js"></script>
<script src="js/SZ_touch.js"></script>
<link href="css/SZ_master.css" rel="stylesheet" />
</head>
<body>
<div id="SZ_maincontent">
<img id="SZ0_0" src="img/SZ_background_image.jpg" onmousemove="rotateGun(event)" />
<div id="SZ0_1" ></div>
<div id="SZ0_2" >
<img src="imag
es/SZ_reload.png" onmousedown="reloadGun(event)" />
</div>
<div id="SZ0_3" >
<img src="img/SZ_score.png" />
</div>
</div>
</body>
</html>
保存文件,然后关闭它。回到My_Work_Files文件夹,双击default.html文件。
当屏幕出现时,尝试单击重新加载按钮。你应该看到枪会动。这一次,在第一个动画完成之前,单击几次该按钮。不顺利吧?我们需要解决这个问题,使枪不接受重新加载请求,直到前一个已经完成。
重新打开js文件夹中的SZ_touch.js文件。键入以下新行(粗体):
//this function is called to reload our gun
function reloadGun(e) {
//play the reload animation of our SS
$("#SZ0_1").animateSprite("play", "reload");
}
//We need a flag to keep track to avoid repetition of animations before the first has finished
var canIclick= 0;
//this function is called to reload our gun
function reloadGun(e) {
//Let’s check if we can allow this to occur
if(canIclick== 0){
//looks like we can so we better set our flag
canIclick=1;
$("#SZ0_1").animateSprite("play", "reload");
}
}
保存并关闭该文件。回到My_Work_Files文件夹,双击default.html文件。同样,在第一个动画完成之前,单击几次重新加载按钮。问题已经解决了。
然而,我们现在有另一个问题:游戏只接受一次重新加载请求。我们不能在第一次尝试后让枪重新装弹。这是因为我们没有在代码中的任何地方重置我们的标志。所以让我们现在就开始吧。重新打开SZ_SS.js文件,键入以下新行(粗体):
//We need a one stop function that will allow us to process sprite sheets
function setup_SpriteSheet(div_name, image_name, no_of_frames, widthx, heightx) {
//need the ratio of the container's width/height
var imageOrgRatio = $(div_name).height() / $(div_name).width();
//need to ensure no trailing decimals
var ratio2 = Math.round(ratio * 10) / 10;
//check that the width is completely divisible by the no of frames
var newDivisible = Math.round((widthx * ratio2) / no_of_frames);
//the new width will be the number of frames multiplied by our new divisible
var newWidthx = newDivisible * no_of_frames;
//also the new height will be our ratio times the height of the div containing our image
var newHeightx = heightx * ratio2;
//apply our new width to our CSS
$(div_name).css('width', (newWidthx));
//apply our new height to our CSS
$(div_name).css('height', newHeightx);
//
//take the image name and apply as a background image to our div
$(div_name).css('background-image', 'url(' + image_name + ')');
//finally we need to apply a background size remembering we need to multiply width by the no of frames
$(div_name).css('background-size', newWidthx * no_of_frames + 'px ' + newHeightx + 'px');
}
//setup the Gun
function setup_gun_SS(){
//first let’s setup our gun SS
setup_SpriteSheet("#SZ0_1","img/SZ_gun_SS.png",28,150,150);
//need to access a special function in our js/ss.js file
$("#SZ0_1").animateSprite({
fps: 10,
animations: {
static: [0],
reload: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23],
fire: [24,25,26,27,28],
},
duration: 50,
loop: false,
complete: function () {
// use complete
only when you set animations with 'loop: false'
//alert("animation End");
//we need to reset our universal flag
canIclick=0;
}
});
}
保存并关闭该文件。
当重新访问这个项目时,将我们所有的全局var存储在一个单独的文件中是一个好主意。我确信您在自己的编程环境中实践了这一点;它确保您的项目在未来是可管理的。如果你创建了一个全局的var文件,确保在你的 HTML 文件中使用<script>标签链接它。
回到My_Work_Files文件夹,双击default.html文件。
第一个动画完成后,再次单击“重新加载”按钮。这个问题现在应该解决了。
接下来,我们将通过使我们的枪开火来完成这一章。
为什么枪在第一次试装后就停止了装弹?
在我们告诉我们的代码运行reload命令之前,我们询问它我们的标志(即canIclick)是否设置为 0。当程序启动时,我们将canIclick初始化为 0。一旦通过测试,代码做的第一件事就是将canIclick设置为 1。
下一次按下 Reload 按钮时,当询问canIclick是否为 0 时,它返回负值。因此,理想情况下,我们希望在重新装填枪支的动画完成后,将canIclick重置回 0。我们在animate命令的一个特殊子功能中实现了这一点。该函数特别询问动画结束后是否有任何特殊指令需要执行。
回想一下我们讨论onmousemove事件时,我们使用了以下内容:
<img src="img/SZ_reload.png" onmousedown="reloadGun(event)" /> -
顾名思义,每当图像被点击时,它就调用reloadGun函数。下面是我们可以使用的事件函数的简短列表。
- 当鼠标移动到一个元素上时,这个事件发生。
- 当鼠标移出一个元素时,这个事件发生。
- 当鼠标移动到一个元素或它的一个子元素上时,这个事件发生。
- 当用户将鼠标指针移出元素或其子元素时,会发生此事件。
- 当用户在一个元素上释放鼠标按钮时,这个事件发生。
开枪吧
如你所料,让我们的枪开火的方法和我们给枪装子弹的方法非常相似。首先,我们需要注册请求开枪的用户。然后,我们需要使枪有生命。
重新打开js文件夹中的SZ_touch.js文件。键入以下新行(粗体):
//We need a flag to keep track to avoid repetition of animations before the first has finished
var canIclick= 0;
//this function is called to reload our gun
function reloadGun(e) {
//Let’s check if we can allow this to occur
if(canIclick== 0){
//looks like we can so we better set our flag
canIclick=1;
$("#SZ0_1").animateSprite("play", "reload");
}
}
//this function is called to fire our gun
function fireGun(e) {
//Let’s check if we can allow this to occur
if(canIclick== 0){
//looks like we can so we better set our flag
canIclick=1;
$("#SZ0_1").animateSprite("play", "fire");
}
}
保存文件并关闭它。
将来,在允许用户开枪之前,你可能会检查更多的变量(例如,如果屏幕暂停,或者在一关结束时)。此时,创建一个函数来检查所有参数值,然后输出结果决策是一个好主意。然后,在决定是否继续时,该输出将由其他功能(如是否可能暂停)和fireGun()功能进行全面检查。
在测试之前,我们需要将函数添加到default.html文件中。重新打开default.html文件。键入以下新行(粗体)和对现有行的添加(修改后的文本为红色):
<html>
<head>
<script src="js/jquery.js"></script>
<script src="js/jquery-ui.js"></script>
<script src="js/SZ_main.js"></script>
<script src="js/SZ_setupContent.js"></script>
<script src="js/SZ_movement.js"></script>
<script src="js/ss.js"></script>
<script src="js/SZ_SS.js"></script>
<script src="js/SZ_touch.js"></script>
<link href="css/SZ_master.css" rel="stylesheet" />
</head>
<body>
<div id="SZ_maincontent">
<img id="SZ0_0" src="img/SZ_background_image.jpg" onmousemove="rotateGun(event)"onmousedown="fireGun(event)" />
<div id="SZ0_1" ></div>
<div id="SZ0_2" >
<img src="img/SZ_reload.png" onmousedown="reloadGun(event)" />
</div>
<div id="SZ0_3" >
<img src="img/SZ_score.png" />
</div>
</div>
</body>
</html>
保存文件,然后关闭它。回到My_Work_Files文件夹,双击default.html文件。现在点击屏幕上的任何地方。枪应该激活射击序列。
到现在为止,枪应该在做以下事情:
- 响应屏幕上的鼠标而移动
- 当用户单击重新加载按钮时重新加载
- 当用户点击屏幕上的任何地方时触发
在本节中,我们使用以下代码行调用了一组特定的 sprite 动画:
$("#SZ0_1").animateSprite("play", "fire");
系统如何知道如何处理"fire"?如果你回到代码,注意我们写了如下内容:
animations: {
static: [0],
reload: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23],
fire: [24,25,26,27,28],
},
我们定义的一个动画叫做"fire",它是 24 到 28 张单独的精灵图片。
请注意,我们使用以下代码将两个鼠标事件插入到一个图像标记中:
<img id="SZ0_0" src="img/SZ_background_image.jpg" onmousemove="rotateGun(event)" onmousedown="fireGun(event)" />
这是完全可能的,因为(a)您可以为一个元素定义多个鼠标事件,并且(b)这两个鼠标事件不会相互冲突。
最后一件事…
您可能已经注意到,当您单击重新加载按钮时,光标会保持为十字准线。如果它能变回一个更合适的光标就好了,这样更直观,也有助于更好的游戏体验。
我们可以通过改变 CSS 来做到这一点。您现在需要重新打开SZ_master.css文件,并键入以下新行(粗体):
html {
height: 100%;
}
body {
padding: 0 0 0 0;
margin: 0;
user-select: none;
cursor: crosshair;
}
img {
max-width: 100%;
height: auto;
user-drag: none;
user-select: none;
-moz-user-select: none;
-webkit-user-drag: none;
-webkit-user-select: none;
-ms-user-select: none;
}
#SZ0_0 {
position: fixed;
top: 0;
left: 0;
min-width: 100%;
min-height: 100%;
}
#SZ0_1 {
position: fixed;
bottom: 0;
right: 0;
}
#SZ0_2 {
position: fixed;
top: 0;
left: 0;
cursor: pointer;
}
#SZ0_3 {
position: fixed;
top: 0;
right: 0;
}
保存文件,然后关闭它。回到My_Work_Files文件夹,双击default.html文件。
现在,当你将光标移到重载按钮上时,它会立即变成一个普通的“手”图像。类似地,当您将鼠标从重新加载按钮移开时,它应该会变回光标图像。
下一章将僵尸引入我们的游戏,这最终给了我们的玩家一些互动性。
光标在重载按钮上时不会变成指针?此错误可能来自 HTML 文件。首先,检查您是否插入了这一行
cursor: pointer;
在#SZ0_2部分。
如果您已经这样做了,那么我们需要看一下default.html文件。确保您已经按照指示将所有图像标签替换为div标签。
比如,以前是什么
<img id="SZ0_1" src="img/SZ_gun.png" />
<img id="SZ0_2" src="img/SZ_reload.png" />
<img id="SZ0_3" src="img/SZ_score.png" />
现在应该是
<div id="SZ0_1" ></div>
<div id="SZ0_2" >
<img src="img/SZ_reload.png" onmousedown="reloadGun(event)" />
</div>
<div id="SZ0_3" >
<img src="img/SZ_score.png" />
</div>
如果你的代码仍然不工作,那么请不要犹豫,在 Twitter 上给我发消息@zarrarchishti。
六、僵尸在哪里?
“用代码行来衡量编程进度就像用重量来衡量飞机制造进度一样。”
比尔盖茨
让我们回顾一下我们的僵尸在游戏中需要做什么。我们需要六个走向屏幕的僵尸。每个僵尸都有一个精灵表和它的行走动画。当僵尸到达其动画的结尾时,它需要重置到原始位置。
创造僵尸:第一部分
首先,我们需要将以下四个 sprite 工作表添加到您的image文件夹中:
zombiesSS_1.png:僵尸行走的科学家zombiesSS_2.png:行走的女丧尸zombiesSS_3.png:行尸走肉男SZ_bubble.png:被困在泡泡里的三只僵尸
转到My_Work_Files文件夹的Raw Images文件夹中的images文件夹。找到名为zombiesSS_1.png、zombiesSS_2.png、zombiesSS_3.png和SZ_bubble.png的文件,并将这些文件复制到Images文件夹中,现在看起来应该是这样的:
创造僵尸:第二部分
在这一节的结尾,你会看到一个僵尸在我们星球的边缘。为此,我们需要从头开始编写僵尸代码。再次,我提前道歉,因为将有相当多的编码。然而,看到自己的僵尸出现在屏幕上的兴奋感值得所有的努力。
打开SZ_zombie_movement.js文件,里面应该是完全空白的。键入以下几行:
//let’s create a zombie
function SZ_createZombie(whichOne){
//create a new div to hold the zombie SS
var div = document.createElement('div');
//we need to hard code the CSS styles we want
div.setAttribute('style','position: fixed; top:0; left:0;')
//we want to position our zombie exactly at the tip of the planet
var top_position= $('#SZ0_0').height() * 0.435;
//Xpos can be anywhere on our x axis
var left_position = Math.floor(Math.random() * ($('#SZ0_0').width())-(ratio*50)) + (ratio*50);
//let's position our zombie
div.style.left = left_position+'px'; div.style.top = top_position+'px';
//give it an id
div.id = 'zombie'+whichOne;
//finally let's add our zombie to the screen
document.body.appendChild(div);
//put this new zombie through our SS function
setup_zombie_SS(whichOne);
}
您现在可以保存并关闭该文件。
我们在准则中引入了一些新的元素,在“更多信息”一节中有详细介绍。
在我们进一步编码之前,我们需要将这个新文件链接到我们的default.html文件。重新打开default.html文件,键入以下新行,以及现有行中的额外文本(所有新文本都以粗体显示):
<html>
<head>
<script src="js/jquery.js"></script>
<script src="js/jquery-ui.js"></script>
<script src="js/SZ_main.js"></script>
<script src="js/SZ_setupContent.js"></script>
<script src="js/SZ_movement.js"></script>
<script src="js/ss.js"></script>
<script src="js/SZ_SS.js"></script>
<script src="js/SZ_touch.js"></script>
<script src="js/SZ_zombie_movement.js"></script>
<link href="css/SZ_master.css" rel="stylesheet" />
</head>
<body>
<div id="SZ_maincontent">
<img id="SZ0_0" src="img/SZ_background_image.jpg" onmousemove="rotateGun(event)" onmousedown="fireGun(event)" />
<div id="SZ0_1" ></div>
<div id="SZ0_2" >
<img src="img/SZ_reload.png" onmousedown="reloadGun(event)" />
</div>
<div id="SZ0_3" >
<img src="img/SZ_score.png" />
</div>
</div>
</body>
</html>
保存文件,然后关闭它。现在,我们可以继续进一步发展我们的僵尸雪碧表。重新打开js文件夹中的SZ_SS文件。
键入以下新行(所有新文本都以粗体显示):
//We need a one stop function that will allow us to process sprite sheets
function setup_SpriteSheet(div_name, image_name, no_of_frames, widthx, heightx) {
//need the ratio of the container's width/height
var imageOrgRatio = $(div_name).height() / $(div_name).width() ;
//need to ensure no trailing decimals
var ratio2 = Math.round(ratio * 10) / 10;
//check that the width is completely divisible by the no of frames
var newDivisible = Math.round((widthx * ratio2) / no_of_frames);
//the new width will be the number of frames multiplied by our new divisible
var newWidthx = newDivisible * no_of_frames;
//also the new height will be our ratio times the height of the div containing our image
var newHeightx = heightx * ratio2;
//apply our new width to our CSS
$(div_name).css('width', (newWidthx));
//apply our new height to our CSS
$(div_name).css('height', newHeightx);
//
//take the image name and apply as a background image to our div
$(div_name).css('background-image', 'url(' + image_name + ')');
//finally we need to apply a
background size remembering we need to multiply width by the no of frames
$(div_name).css('background-size', newWidthx * no_of_frames + 'px ' + newHeightx + 'px');
}
//setup the Gun
function setup_gun_SS(){
//first let’s setup our gun SS
setup_SpriteSheet("#SZ0_1","img/SZ_gun_SS.png",28,150,150);
//need to access a special function in our js/ss.js file
$("#SZ0_1").animateSprite({
fps: 10,
animations: {
static: [0],
reload: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23],
fire: [24,25,26,27,28],
},
duration: 50,
loop: false,
complete: function () {
// use complete only when you set animations with 'loop: false'
//alert("animation End");
//we need to reset our universal flag
canIclick=0;
}
});
}
//setup a newly
created
zombie
function setup_zombie_SS(whichOne){
//let’s identify what type of zombie we should create
var type_zombie = [1,2,3,1,2,3];
//let’s setup a speed for each type of zombie
var speed_zombie = [100,50,150];
//first let’s setup our zombie SS
setup_SpriteSheet("#zombie"+whichOne,"img/zombiesSS_"+type_zombie[whichOne-1]+".png",9,20,20);
//need to access a special function in our js/ss.js file
$("#zombie"+whichOne).animateSprite({
fps: 10,
animations: {
static: [0,1,2,3,4,5,6,7],
},
duration: speed_zombie[type_zombie[whichOne-1]-1],
loop: true,
complete: function () {
// use complete only when you set animations with 'loop: false'
//alert("animation End");
}
});
}
保存文件,然后关闭它。
您会注意到,我们一直在重复代码,以便在各种 JavaScript 文件中设置一个 sprite 表。我这样做是为了保持代码线性流动;但是,您可以决定为所有 sprite 工作表操作创建一个文件。
我们现在需要在安装文件中调用这个函数来创建一个僵尸。重新打开js文件夹中的SZ_setupContent文件,并键入以下新行(所有新文本均以粗体显示):
//main function
function main_call_setupContent() {
//need to resize all elements
//first we set their normal sizes in CSS
//Gun
$('#SZ0_1').css('width', 150 * ratio);
$('#SZ0_1').css('height', 150 * ratio);
//Reload Button
$('#SZ0_2').css('width', 200 * ratio);
$('#SZ0_2').css('height', 90 * ratio);
//Score
$('#SZ0_3').css('width', 235 * ratio);
$('#SZ0_3').css('height', 100 * ratio);
//Any sprite sheets?
//Our Gun
setup_gun_SS();
//Create a zombie
SZ_createZombie(1);
}
我们现在准备测试!保存所有文件,然后关闭它们。回到你的My_Work_Files文件夹,双击default.html文件。你应该看到的是行星表面边缘的科学家僵尸。
如果你点击浏览器的刷新按钮(或者,你可以按 F5),僵尸应该出现在一个不同的位置(但仍然在行星表面的边缘)。继续刷新几次,测试这种行为。
我们现在已经设法在我们的游戏中产生一个僵尸!我们的下一步将是让我们的僵尸向我们走来。
没用吗?以下是需要检查的几个方面:
- 检查您是否在
default.html中正确链接了SZ_zombie_movement.js文件。 - 我们第一次使用数组(参见“更多信息”一节关于什么是数组)。确保您使用的是键盘上 P 键旁边的方括号。
- 最后,确保下面一行代码完全如所示:
setup_SpriteSheet("#zombie"+whichOne,"img/zombiesSS_"+type_zombie[whichOne-1]+".png",9,20,20);
如果你的代码仍然不工作,那么请不要犹豫,在 Twitter 上给我发消息@zarrarchishti。
这一部分有三个令人兴奋的特点。让我们更深入地探索一下。
- 动态创建一个
div。动态是什么意思?意思是僵尸的div是游戏运行时产生的;也就是说,我们没有在default.html文件中为僵尸编写一个div,因为我们在那里有所有其他的div,这样做的主要原因是通过调用一个函数来生成多个僵尸,而不是手动写出每个div。 - 数组。如果你在阅读任何计算机语言的传统编码书籍,你会在第一天就接触到数组。然而,我认为现在学习更好,因为你刚刚实际使用了一个,因此你能够更好地理解解释。让我们快速看一下我们的一个数组。我们有一个由六个整数组成的数组,以数字 1 开始,以数字 3 结束。
var将数组声明为新元素。type_zombie是数组的名称。[ ]这些括号中的任何内容都是数组的内容,用逗号分隔
我们在编码中使用了几个数学函数。让我们来看看其中的一些。
Math.random()是一个生成随机数的特殊 JavaScript 函数。这个随机数被用来(通过一些操作)随机放置我们的僵尸。Math.floor()是一个基本上向下舍入数字的函数;例如,45.89 将返回 45。顺便说一下,这个函数的反函数(即上舍入)是Math.ceil(),所以 45.89 将返回 46。
将僵尸移近
为了让僵尸靠近我们,我们将使用 JavaScript。代码将同时做两个动画。首先,它会把僵尸拉下屏幕。第二,僵尸将被缩放,看起来更大。通过一起做这两个动画,我们给出了僵尸向我们走来的错觉。
打开SZ_zombie_movement.js文件,输入以下新行(所有新文本以粗体显示):
//let’s create a zombie
function SZ_createZombie(whichOne){
//create a new div to hold the zombie SS
var div = document.createElement('div');
//we need to hard code the CSS styles we want
div.setAttribute('style','position: fixed; top:0; left:0;')
//we want to position our zombie exactly at the tip of the planet
var top_position= $('#SZ0_0').height() * 0.435;
//Xpos can be anywhere on our x axis
var left_position = Math.floor(Math.random() * ($('#SZ0_0').width())-(ratio*50)) + (ratio*50);
//let's position our zombie
div.style.left = left_position+'px'; div.style.top = top_position+'px';
//give it an id
div.id = 'zombie'+whichOne;
//finally let's add our zombie to the screen
document.body.appendChild(div);
//put this new zombie through our SS function
setup_zombie_SS(whichOne);
//put this new zombie through our animate function
SZ_animateZombie(whichOne);
}
//let’s animate
our
zombie towards us
function SZ_animateZombie(whichOne){
//assign the speed for each of our zombies
var timex = [13000,8000,16000,14000,10000,18000];
//assign a user friendly name for our div
var $zombiex = $("#zombie"+whichOne);
//work out the amount the zombie has to come towards us
var amty = ($(window).height()*0.7);// -($zombiex.height()*2));//topx);
//each type of zombie will have their own walking style
var ZS_ease = ['easeInSine','easeOutQuart','easeInOutQuad','easeInSine','easeOutQuart','easeInOutQuad'];
//finally we are ready to animate
$zombiex.animate({
//first bring our zombie slowly down the screen
left: amty+ "px",
},{ easing:ZS_ease[whichOne-1], duration: timex[whichOne-1],
step: function(now, fx){
//at each step we can manipulate the scale of our zombie
if (fx.prop == "left") {
//work out the amount to scale
var xx = (fx.pos)*16;
//apply the scale
$(this).css('transform','scale('+xx+')');
}
}, complete: function () {
}
});
}
保存此文件,然后关闭它。回到My_Work_Files文件夹,双击default.html文件。
当屏幕出现时,你应该看到僵尸向你走来!根据你的屏幕分辨率,僵尸可能会越过边缘或在它应该停下来之前停下来。不要担心那个;我们将在第八章处理。
接下来,让我们来看看创造游戏所需的所有僵尸。
下面的数组有文本值,但是它们是什么呢?
var ZS_ease = ['easeInSine','easeOutQuart','easeInOutQuad','easeInSine','easeOutQuart','easeInOutQuad'];
这些值就是我们用于缓动函数的值。缓动函数指定僵尸随时间变化的速率。最简单和最广泛使用的缓动值是线性缓动值。这是僵尸行走期间以恒定速度移动的地方。然而,那会有点无聊和不现实。
我们有多种放松功能可供选择。以下是我们将使用的方法:
-
For our scientist zombie,
easeInSine. It starts quite slowly and then accelerates for the rest of the journey. Here is a graph depicting the function: -
For our female zombie,
easeOutQuart. There is no delay at the start; she begins abruptly and eases off near the end. Here is a graph depicting the function: -
For our male zombie,
easeInOutQuad. There is a delay both at the start and at the finish. The midway section is fairly average as well. Here is a graph depicting the function:
创造所有的僵尸
我们将为我们的游戏创造六个僵尸。我们可以想创造多少就创造多少;但是,我们在创建游戏时需要考虑内存问题。如果我们创造了太多的僵尸,游戏可能会耗尽电脑内存。另一方面,如果我们创造的太少,那么游戏可能就不够有挑战性。从本质上来说,这一切都是为了找到游戏和玩家的完美参数。
打开js文件夹中的SZ_setupContent.js文件。首先,找到以下两行并删除它们:
//Create a zombie
SZ_createZombie(1);
键入以下新行(所有新文本都以粗体显示):
//main function
function main_call_setupContent() {
//need to resize all elements
//first we set their normal sizes in CSS
//Gun
$('#SZ0_1').css('width', 150 * ratio);
$('#SZ0_1').css('height', 150 * ratio);
//Reload Button
$('#SZ0_2').css('width', 200 * ratio);
$('#SZ0_2').css('height', 90 * ratio);
//Score
$('#SZ0_3').css('width', 235 * ratio);
$('#SZ0_3').css('height', 100 * ratio);
//Any sprite sheets?
//Our Gun
setup_gun_SS();
//Create all our 6 zombies
for (i = 1; i < 7; i++) {
//this will get called 6 times
SZ_createZombie(i);
}
}
保存此文件,然后关闭它。
出于本书的目的,我们决定将僵尸的最大数量保持在六个。然而,为了保证这个函数不会过时,我建议重新访问它,以接受最大僵尸数量作为参数。您可以用这个参数名替换for loop中的 7。
回到My_Work_Files文件夹,双击default.html文件。
当屏幕出现时,你应该会看到所有六个僵尸向屏幕走去。你不仅会看到科学家僵尸,还会看到女性和男性僵尸。我应该注意到他们的速度是相对于我们在第一章中讨论的。
我没什么以下顾虑:
- 僵尸互相重叠。
- 枪藏在丧尸后面。
- 鼠标光标在僵尸上时不会变成十字准线。
我在下面的截图中说明了前两点:
在这个阶段不要太担心这些问题。所有这些问题都在第八章中解决。接下来,我们将着眼于回收我们的僵尸的生命,以便一旦它完成,该元素准备好为我们的程序再次使用。
在这一节中,我们遇到了一个for loop来创造我们的六个僵尸:
for (i = 1; i < 7; i++) {
通过使用这个循环,我们消除了六次编写相同代码来创建僵尸的需要。如果是这样的话,那么想象一下,如果我们要创造 100 个僵尸!
如示例所示,如果您想用不同的值反复运行相同的代码,循环是必不可少的。
这里有四种不同的循环,我们可以在游戏中使用:
- 多次循环通过一个代码块
for/in遍历对象的属性- 当指定的条件为真时,循环通过代码块
- 当指定的条件为真时,
do/while也循环通过代码块
生成僵尸生命周期
正如你所看到的,僵尸一旦到达屏幕,就一直留在屏幕上。最终,如果有僵尸出现在屏幕上,我们会想要结束游戏。现在,我们很乐意让他们回到起点。
打开SZ_zombie_movement.js文件,输入以下新行(所有新文本以粗体显示):
//let's create a zombie
function SZ_createZombie(whichOne){
//create a new div to hold the zombie SS
var div = document.createElement('div');
//we need to hard code the CSS styles we want
div.setAttribute('style','position: fixed; top:0; left:0;')
//we want to position our zombie exactly at the tip of the planet
var top_position= $('#SZ0_0').height() * 0.435;
//Xpos can be anywhere on our x axis
var left_position = Math.floor(Math.random() * ($('#SZ0_0').width())-(ratio*50)) + (ratio*50);
//let's position our zombie
div.style.left = left_position+'px'; div.style.top = top_position+'px';
//give it an id
div.id = 'zombie'+whichOne;
//finally let's add our zombie to the screen
document.body.appendChild(div);
//put this new zombie through our SS function
setup_zombie_SS(whichOne);
//put this new zombie through our animate function
SZ_animateZombie
(whichOne);
}
//let’s animate our zombie towards us
function SZ_animateZombie(whichOne){
//assign the speed for each of our zombies
var timex = [13000,8000,16000,14000,10000,18000];
//assign a user friendly
name for our div
var $zombiex = $("#zombie"+whichOne);
//reset the zombies scale value
$zombiex.css('transform','scale('+0+')');
//work out the amount the zombie has to come towards us
var amty = ($(window).height()*0.7);// -($zombiex.height()*2));//topx);
//each type of zombie will have their own walking style
var ZS_ease = ['easeInSine','easeOutQuart','easeInOutQuad','easeInSine','easeOutQuart','easeInOutQuad'];
//finally we are ready to animate
$zombiex.delay(timex[whichOne-1]/3).animate({
//first bring our zombie slowly down the screen
left: "+="+1+ "px",
},{ easing:ZS_ease[whichOne-1], duration: timex[whichOne-1],
step: function(now, fx){
//at each step we can manipulate the scale of our zombie
if (fx.prop == "left") {
//work out the amount to scale
var xx = (fx.pos)*16;
//do a check to see if we should end this animation
if(xx>15){
//stop all animation
$(this).stop();
//call a function to reset this zombie
SZ_resetZombie(whichOne);
} else {
//apply the scale
$(this).css('transform','scale('+xx+')');
}
}
}, complete: function () {
}
});
}
//a function to
comp
letely reset our zombie
function SZ_resetZombie(whichOne){
//assign a user friendly name for our div
var $zombiex = $("#zombie"+whichOne);
//we want to position our zombie exactly at the tip of the planet
var top_position= $('#SZ0_0').height() * 0.435;
//Xpos can be anywhere on our x axis
var left_position = Math.floor(Math.random() * ($('#SZ0_0').width())-(ratio*50)) + (ratio*50);
//let's re-position our zombie
$zombiex.css({top: top_position+'px', left: left_position+'px'});
//finally let’s make the zombie come towards the screen again
SZ_animateZombie(whichOne);
}
保存此文件,然后关闭它。
同样,重置僵尸的功能需要面向未来。我建议将僵尸起始位置的所有可能参数放在一个单独的文件中。在此函数中,我们指示代码访问新文件,以获取具有不同方差的可能参数的指令(例如,如果用户处于更高级别,则值可能不同)。
回到My_Work_Files文件夹,双击default.html文件。
当屏幕出现时,你应该看到僵尸像以前一样出现在屏幕上,但随后消失了。当它们再次出现时,它们应该出现在不同的位置。
在下一章,我们来看看如何射击我们的僵尸。
没用吗?这可能是由于我们在一些现有代码周围添加了几行代码。让我们看看。
最初的代码行是
$zombiex. animate({
确保新代码行如下所示:
$zombiex.delay(timex[whichOne-1]/2).animate({
最初的代码行是
//apply the scale
$(this).css('transform','scale('+xx+')');
确保新代码行如下所示:
//do a check to see if we should end this animation
if(xx>15){
//stop all animation
$(this).stop();
//call a function to reset this zombie
SZ_resetZombie(whichOne);
} else {
//apply the scale
$(this).css('transform','scale('+xx+')');
}
}
如果你的代码仍然不工作,那么请不要犹豫,在 Twitter 上给我发消息@zarrarchishti。