Building JavaScript Games for Phones Tablets and Desktop(2)-游戏编程基础

这章覆盖了游戏编程的基本原理。首先,你会了解到游戏的基本框架,就是一个游戏世界和一个游戏循环。你将会通过不同的例子学到如何用JavaScript创建游戏框架,最终,我会讨论如何通过注释,布局和恰到好处的空白来增加代码可读性。

搭积木般创建游戏

这部分讨论了如何像搭积木一样创建游戏。我论述游戏世界的同时通过一个更新-绘画循环来向你演示这个过程,这个循环就是,更新游戏世界,显示游戏世界。

游戏世界

是什么让游戏有了如此的娱乐性能让你乐此不疲的探索这个虚构的世界,做你在现实世界中做不到的事情。你可以骑在龙背上,摧毁整个太阳系统,或者创造一个有相当复杂文明的角色,角色说着虚构的语言。这个想象力就被叫做游戏世界。游戏世界可以是一个小的区域比如俄罗斯方块这个游戏世界,也可以复杂到像侠盗猎车手和魔兽世界那样的虚构世界。

当一个游戏运行在电脑或者智能手机上,这个设备维持着这个游戏世界的内部表征。这个表征不是你玩游戏在屏幕上看到的那些东西。它包含了大量描述对象的信息,一个敌人能有多少生命值,一个角色上有多少物品等等。幸好,程序知道如何把这些内部表征显示到屏幕上。否则,玩游戏将会变得非常乏味,因为玩家不得不翻阅大量的数据查看他们是否拯救了女王或者已经死亡。

玩家永远不会看到游戏世界的内部表征,但是游戏开发者可以。当你想开发游戏,你需要考虑如何设计游戏内部。你游戏编程的乐趣之一就是你完成了这些内部设计。

另外一个重要的事情是要意识到与现实世界一样,这个游戏世界随时处于改变中。怪物移动到不同的地方,天气发生改变,车会跑的没油,敌人会挂掉等等。此外,玩家的操作会直接影响游戏世界的改变!因此简单的在计算机内存中储存单一的游戏世界内部表征是不够的。一个游戏需要持续不断的记录玩家的所作所为并且以此更新内部表征。此外,需要把游戏世界展现到玩家的眼前,通过电脑显示器,电视,或者智能手机的屏幕。处理这些事情的过程称为游戏循环。

游戏循环

游戏循环处理游戏的动态方面。在游戏运行过程中有很多事情发生。玩家按下键盘或者点击触摸屏。一个不断改变的游戏世界包含了等级、怪物,和其它需要保持更新的角色。也有其它一些特殊效果,比如爆炸,声音等等。这些不同的任务都需要通过游戏循环处理,这些东西可以分为两类:

  • 跟游戏世界更新和维护相关的
  • 跟显示游戏世界相关的

游戏循环会不断的执行这个任务,一个接一个(如图2-1)。例如,让我们在吃豆人这个游戏里看如何处理角色的移动。吃豆人出现在迷宫中的某个地方并且朝一个固定的方向移动。第一个任务里(更新和维护世界),检测玩家是否按下了方向键。如果按下了,则需要更新吃豆人的位置。也许,因为吃豆人的移动,它吃掉了一个豆,这个豆表明得到了分数。你需要检测这个豆是否是吃豆人吃掉的最后一个豆子,因为吃完豆子表明已经通过了本关卡。最终,如果吃掉了一个大豆,则幽灵就禁止了。然后你需要更新剩下的游戏世界。幽灵的位置需要更新,还要决定积分结果显示在哪里,需要检测吃豆人是否与幽灵发生了碰撞等等。你会发现即使在像吃豆人这样简单的游戏里,许多工作都需要在第一个任务里做。从现在开始,我把这些不同的跟更新和维护游戏世界相关的任务叫做更新行为。

(省略图2-1)

与第二部分任务相关的就是向玩家展示这个游戏世界。在吃豆人这个游戏里,意味着要绘画出迷宫,幽灵,吃豆人和一些向玩家展示的重要信息,比如玩家得到了多少分,还剩下几条命等等。这些东西可以被放在屏幕上不同的位置,比如顶部或底部。这部分也被叫做抬头显示部分(HUD)。现代3D游戏有更复杂的绘画任务。这些游戏需要处理灯光和阴影,反光,类似爆炸的视觉效果等等。

使用JavaScript创建一个游戏

前面的章节教会了如何创建一个简单的JavaScript程序。在那个程序里,指令被装进一个函数里,类似下面:

changeCanvasColor = function(){
    document.body.style.background = "blue";
}

这样书写程序考虑到的是JavaScript是过程式语言:指令由函数组合在一起。第一步是用JavaScript写一个简单的游戏循环。如下所示:

var canvas = undefined;
var canvasContext = undefined;

function start () {
    canvas = document.getElementById("myCanvas");
    canvasContext = canvas.getContext("2d");
    mainLoop();
}

document.addEventListener('DOMContentLoaded', start);

function update () {

}

function draw() {

}

function mainLoop () {
    canvasContext.fillStyle = "blue";
    canvasContext.fillRect(0, 0, canvas.width, canvas.height);
    update();
    draw();
    window.setTimeout(mainLoop, 1000 / 60);
}

上面的脚本中有多个函数。因为下面一条指令,当HTML加载时执行start函数:

document.addEventListener('DOMContentLoaded', start);

在start函数里,你获取画布和画布上下文;把这两个东西储存在变量里以便在其它程序里可以使用。然后执行另外一个函数mainLoop。这个函数里有其它指令,其中两条指令处理背景颜色。然后调用update函数,之后是draw函数。这两个函数又含有其它指令。最后一条指令是下面这条:

document.addEventListener('DOMContentLoaded', start);

这句指令的意思在一个确定的时间后(1000/60,大约16.6毫秒)再次执行mainLoop函数。当mainLoop函数被再次调用,背景色再次发生改变且update函数和draw函数也再次被调用。在这里,update函数和draw函数都是空的,但是可以向里面添加东西用来更新和绘画游戏世界。需要注意的是在循环之间使用setTimeout函数并不是最好的解决方案。有时这个方法的影响能超出你控制之外,比如在缓慢的电脑,在浏览器中打开的其他东西,或者同时其他一些需要处理器运行的应用在运行等等。当你需要处理敏感的时间操作(比如玩家需要5分钟后复活),此时你不是再依靠setTimeout函数而是根据系统的事件调度或者在update函数中检测是否发生这些事件来进行处理。

当你运行这个例子程序,update和draw函数被不断的执行:更新,绘画,更新,绘画等。此外,这一切发生的很快。这个例子运行速度大概是60帧。这种循环称作是固定的时间循环,它在一些小游戏中是非常流行的。你也可以设计不同的游戏以不同的循环方式运行着而不是每秒60帧这样。

这本书教会了你很多不同的方法来填充update和draw函数。在这个过程中,我会介绍许多有用编程技术,对游戏和其他应用程序都有用。下面的部分将更细节的讲解基础游戏应用。那时,你将会为这个游戏基本框架添加其他的指令。

程序结构

这节细节的讲程序结构。在早些时候,许多程序员使用文本而不是图形编程。这种基于文本的应用叫做控制台程序。除了在屏幕上输出文字,它也可以接收来自用户的文本输入。所以,所有与用户的交互都在一个问答表列表里(Do you want to format the hard drive (Y/N)? Are you sure(Y/N)? and so on))。在基于窗口的操作系统变得流行之前,基于文本的接口对应文本编辑程序,电子表格,数学应用甚至游戏都是常见的。这些游戏被叫做文字冒险游戏,文字描述了这个游戏世界。玩家通过输入命令与游戏世界交互。

用JavaScript编写控制台应用是可以实现的。虽然看上去很有趣,但我是还是把注意力放在现代图形游戏上。

应用类型

控制台应用只是应用程序类型的其中一种。其他常见的类型是Windows应用。这类型应用在一个屏幕里包含了窗口,按钮和其他用户图形接口(GUI)。这类型应用一般是事件驱动的,比如按下按钮或者选择一个菜单。

另外一个应用类型是APP,运行在智能手机或平板电脑上。这类型的应用屏幕空间一般有限,但是有更多的互动。比如GPS可以找到设备的地点,传感器可以知道设备的方向,还有触摸屏幕。

当开发应用程序,让其运行在不同的平台上是一个很大的挑战。创建windows应用和app是非常不同的。并且复用不同类型的应用代码是非常难的。因此,基于网页的应用变得越来越流行。在这种情况下,应用被放在服务器端,用户在浏览器中运行程序。这里有许多这种例子:比如基于网页的Email程序或者社交网站。这本书里,学的就是基于网页的应用。

函数

记得在一个过程式程序里,指令做着这个程序里实际的工作:它们一个接一个的执行。改变着内存或者屏幕显示,这样就注意到它们的存在。在BasicGame程序里,不是所有的行都是指令。比如一个指令context.fillRect(0, 0, canvas.width,canvas.height)。

因为Java是过程式语言,指令可以被放在函数里。指令不是必须就要被放在函数里的。比如下面的一条指令就不属于函数:

var canvas = undefined;

然而,函数非常有用。它可以避免代码的零散化,因为指令只在一处地方出现,并且可以让程序员通过函数名轻松的进行调用。函数中的指令在两个花括号之间。这些指令被叫做函数体。在函数体外,书写函数头部,比如下面这样:

var canvas = undefined;

头部包含了函数名。你可以为函数取任何名字。你可以看到gameloop函数有两个部分,draw和update。这两部分也在函数里面。在函数名之前需要function这个单词。在函数名之后是一对花括号。

语法图

如果你不知道编程语言规则那么使用类似JavaScript这样的语言编程是非常困难的。这本书会用被叫做语法图的东西来阐述编程语言的组成结构。一个编程语言的语法参考正式的规则,这些规则决定这是不是一个有效的程序(换句话说,程序能让编译器或解释器读懂)。相比之下,程序的语义参考了它实际的意思。为了区分语法和语义的区别,请看这句话:all your base are belong to us。在语法上来说,这句话有问题。但是语义上相当清楚。

解释器可以检查程序的语法:违背语法的程序都会被解释器报错。不幸的是,解释器不能检查程序的语义是否为程序员心中所想的那样。所以程序语法的正确并不代表语义上的正确。如果语法都不正确的话,程序肯定不能运行。语法图帮你形象化编程语言的规则。比如,下图就是一个关于JavaScript如何定义函数的语法图(图2-2)。

(省略图2-2)

函数调用

(省略)

update与draw

gameloop里面有update和draw函数。因为函数就是指令的集合,每当update函数被调用,函数中的指令就跟着被执行。draw函数也是如此。

假如有这样一个例子,想象你设计个气球跟随鼠标移动的简单游戏。当你移动鼠标,气球也跟着移动。在update和draw函数里,你可以这么做。在update函数里,你需要获取鼠标当前位置并把它储存起来。在draw函数里,你需要把气球显示在刚才储存的位置上。当然,你不知道这些指令是否存在,并且你也不知道这些指令是什么样子。也许你会想这些指令为什么这样运行。你没有移动气球,你只是简单的把它画在update里面储存的位置上。在一个很快的速度里反复调用update和draw函数。因为如此快的速度,在不同的位置画出气球感觉看起来就像气球在移动(实际上不是这样)。这就是所有的游戏的游戏世界绘画的方式和玩家怎样被游戏的世界吸引。本质上,就是以很快的速度的在不同的位置上进行画图。

程序布局

这节处理程序源代码的布局。你首先会了解到如何为你的代码添加注释。然后你会学到如何写出清晰的代码通过使用空格,缩进,单行或多行。

注释

(省略)

指令 vs 多行

(省略)

空格和缩进

(省略)

你学到了什么

在这章里,你学到了:

  • 游戏的框架是什么,包含游戏循环和游戏循环对于游戏世界的作用
  • 怎么组织游戏程序,包括用几条指令获取canvas,update和draw函数构成了游戏循环
  • JavaScript程序的基本布局规则,包括怎样在代码中添加注释和何时放入空格来增加代码的易读性

你可能感兴趣的:(JavaScript)