这章里,学会如何处理按键输入。为了实现这个要求,你须要if语句或者一组相关语句来检测按键条件知足与否。程序员
截止目前为止,全部例子都有一个叫作Game的对象。这个对象里有不少变量。来看看painter1例子中的Game对象:canvas
var Game = { canvas : undefined, canvasContext : undefined, backgroundSprite : undefined, cannonBarrelSprite : undefined, mousePosition : { x : 0, y : 0 }, cannonPosition : { x : 72, y : 405 }, cannonOrigin : { x : 34, y : 34 }, cannonRotation : 0 };
如你所见,即便这个简单的程序只是绘画一个背景和炮身,它也有很多变量。当你开发的游戏变得更复杂的时候,变量会增长,并且程序代码会变得更加难以理解。发生这一切的缘由就是你把全部事情都放在一个Game对象里面。从概念上说,这很容易理解,由于Game含有全部跟painter游戏有关的东西。然而,若是你把这些东西拆分一下会更好理解。浏览器
你仔细观察Game对象,会发现其中某些变量是相关的。好比,canvas和canvasContext相关联,由于都跟画布有关系。好比也有些变量储存有关大炮的信息,好比位置和角度。你能够把这些相关的变量再次组合成其它对象,这样能让代码看起来更清晰。好比:服务器
var Canvas2D = { canvas : undefined, canvasContext : undefined }; var Game = { backgroundSprite : undefined, }; var cannon = { cannonBarrelSprite : undefined, position : { x : 72, y : 405 }, origin : { x : 34, y : 34 }, rotation : 0 }; var Mouse = { position : { x : 0, y : 0 } };
如今,有几个不一样的对象了,每一个都有一些变量,能轻松的看出哪些变量属于哪些对象。更棒的是你也能够对方法作一样的事情。好比,能够为Canvas2D对象添加清除画布和画图的方法,好比:markdown
Canvas2D.clear = function () { Canvas2D.canvasContext.clearRect(0, 0, this.canvas.width, this.canvas.height); }; Canvas2D.drawImage = function (sprite, position, rotation, origin) { // canvas drawing code };
使用不一样的对象而不是使用一个包含一切有关游戏的对象,这样能让代码更加易读。固然,这是在你能逻辑区分这些对象下说的。即便简单的游戏,你也能够有多种组织代码的方式。每一个开发者都有本身的方式。本书就是其中某种方式。你也许不一样意这种方式,某些时候你有一个与本书有关问题的不一样解决办法。固然这是正确的,由于程序问题不只仅是只有一个单一的解决办法。函数
看前面命名的对象,你会发现大多对象的首字母是大写(好比Canvas2D),可是cannon对象小写字母开头。这么作是有缘由的,后面会详细说明。如今,咱们认为大写字母开头的对象对任何游戏都是有用的,小写字母开头的对象只对特定的游戏有用。这里,你能够认为Canvas2D对象对全部的HTML5游戏都有用,但cannon对象只对painter游戏有用。oop
如今你游戏中有不一样的对象了,那么从哪里加载精灵呢?你能够在start方法里面加载全部精灵,可是另一个方法是添加一个相似的方法。好比,cannon对象和其加载都放在cannon里面。到底哪一种好呢?学习
关于在cannon对象里面建立个加载cannon精灵的方法有些东西要说。那样的话,你能够清楚的看到哪一个精灵对应哪一种对象。然而,这也意味着若是你想在不一样的游戏对象里使用这个精灵,须要屡次加载这个精灵。这意味着运行在浏览器中的游戏须要从服务器端下载图片文件,须要花费时间。更好的方法是在游戏开始时加载全部须要的精灵。为了和余下的程序部分区分,你把这些精灵放在一个叫作sprite的对象里面。这个对象做为一个空对象在程序顶部被声明:this
var sprites = {};
在start方法里面填充这个变量。对每个想加载的精灵,建立一个Image对象,而后告诉其图片地址。由于须要使用很多不一样的精灵,你把这些精灵都放在同一个文件夹里。这样在本书中其它例子你就不须要再复制这些图片文件了。下面是painter2例子加载不一样精灵的代码:编码
var spriteFolder = "../../assets/Painter/sprites/"; sprites.background = new Image(); sprites.background.src = spriteFolder + "spr_background.jpg"; sprites.cannon_barrel = new Image(); sprites.cannon_barrel.src = spriteFolder + "spr_cannon_barrel.png"; sprites.cannon_red = new Image(); sprites.cannon_red.src = spriteFolder + "spr_cannon_red.png"; sprites.cannon_green = new Image(); sprites.cannon_green.src = spriteFolder + "spr_cannon_green.png"; sprites.cannon_blue = new Image(); sprites.cannon_blue.src = spriteFolder + "spr_cannon_blue.png";
上面用到了+运算符。举例说明:spriteFolder + “spr_background.jpg”结果就是”../../assets/Painter/sprites/spr_background.jpg”.由于sprites文件夹与painter2文件夹再也不同一个目录,因此有这个东西,因此还添加了两层目录。下一步就是检测玩家的按键状况。
以前的章节里,学习了如何经过使用事件处理来获取当前的鼠标值。获取按键信息的方式与这个相似,定义一个相关的事件处理函数。你须要用个变量来储存用户按下的键值以便之后使用。最先储存键值的方法是使用虚拟键码。虚拟键码就是一个数字表明某个键值。好比,空格键能够是数字13或者数字65。那么为何使用这些特定的数值呢,而不是其它的?由于字符表已是一个标准了,多年来不一样的标准应运而生。
在20时间70年代,程序员认为2的6次方64个符号就足够表明全部的符号,26个字母,10个数字,28个标点符号。虽然这意味着字母没有大小写之分,但在当时这不是个问题。
在20时间80年代,使用2的7次方128个符号:26个小写字母,26个大写字母,10个数字,33个标点符号和33个特殊字符。这些字符就是ASCII字符:美国信息交换标准代码。这在英语里颇有用,可是不适用于其余语言,好比法语,西班牙语等等。
所以,20世纪九十年代,一个256个字符表出现。里面有包含了其余国家的字符。0到127仍是ASCII字符,128-255就是指定语言的字符。这样处理不一样的语言是合理的,若是你想同时用不一样的语言储存文本就是一件复杂的事情了。那些超过128个字符的语言就不能用这种格式表示。
在21世纪初,编码规范再次扩展,如今有65536个字符了。这个字符表能轻易的包含世界上全部的语言了。这个编码表叫作Unicode。
回到前面你想储存的虚拟按键那里,添加一个储存最近按下按键的变量。
var Keyboard = { keyDown : -1 };
变量初始化的值为-1.表示用户如今任何键都没有按下。但用户按下按键时,按键的值就储存在Keyboard.keyDown变量里,写一个事件处理来存放Keyboard.keyDown值:
function handleKeyDown(evt) { Keyboard.keyDown = evt.keyCode; }
上面的参数是一个事件,这个事件对象有个叫作keyCode的变量,储存着用户当前按下的键值。
把这个事件处理函数放在Game.start里面:
document.onkeydown = handleKeyDown;
当用户松掉按键时怎么处理呢?Keyboard.keyDown值应该再次变为-1,表示当前用户没下按下任何键。经过按键弹起事件处理。
function handleKeyUp(evt) { Keyboard.keyDown = -1; }
一样把它放在Game.start里面:
document.onkeyup = handleKeyUp;
如今你已经能够在游戏中处理按键事件了。注意的是这里处理按键有点限制。好比,这里不能记录同时按下的键,好比用户同时按下A和B。第13章里面,将会经过扩展Keyboard对象考虑到这些。
经过上面简单的例子知道了如何使用Keyboard对象作些事情。如今来扩展Painter1程序,在炮身上面画一个彩色的球。经过按下R,G,B键,玩家能够改变小球的颜色为红色,绿色,蓝色。图6-1是这个程序的截图:
(省略图6-1)
须要添加额外的三个精灵,每一个表明一个颜色小球:
sprites.cannon_red = Game.loadSprite(spriteFolder + "spr_cannon_red.png"); sprites.cannon_green = Game.loadSprite(spriteFolder + "spr_cannon_green.png"); sprites.cannon_blue = Game.loadSprite(spriteFolder + "spr_cannon_blue.png");
为cannon对象添加一个initialize方法,用来进行相关变量的赋值。这个方法经过initialize调用。那样,大炮在游戏开始被初始化:
Game.start = function () { Canvas2D.initialize("myCanvas"); document.onkeydown = handleKeyDown; document.onkeyup = handleKeyUp; document.onmousemove = handleMouseMove; ... cannon.initialize(); window.setTimeout(Game.mainLoop, 500); };
cannon.initialize方法实现以下:
cannon.initialize = function() { cannon.position = { x : 72, y : 405 }; cannon.colorPosition = { x : 55, y : 388 }; cannon.origin = { x : 34, y : 34 }; cannon.currentColor = sprites.cannon_red; cannon.rotation = 0; };
如今有两个位置变量。一个是炮身的,一个是彩球的。此外,添加一个表明如今彩球颜色的变量,它的初始值为红色。
为了清楚的区分这些对象,能够为cannon添加一个draw方法。用这个方法来画炮身和彩球:
cannon.draw = function () { Canvas2D.drawImage(sprites.cannon_barrel, cannon.position, cannon.rotation, cannon.origin); Canvas2D.drawImage(cannon.currentColor, cannon.colorPosition, 0, { x : 0, y : 0 }); };
这里的draw方法经过Game.draw调用:
Game.draw = function () { Canvas2D.clear(); Canvas2D.drawImage(sprites.background, { x : 0, y : 0 }, 0, { x : 0, y : 0 }); cannon.draw(); };
那样,你能够更清楚的看到绘画指令对应着哪一个对象。如今准备工做已经作好了,能够开始处理玩家的按键事件了。到目前为止,你的全部代码都在运行着。好比,程序一直在画着背景图和炮身。可是如今你须要知足某些条件下才会执行另一些代码。好比,按下G键时球的颜色才会发生改变,这种指令叫作条件指令,这种指令使用if关键字。
有了条件指令,就能够根据条件知足状况来执行相关代码了。好比用户是否按下某个按键,时间是否已通过去了必定的时间,怪物是否吃掉了你的角色。
条件有真有假,条件是一个表达式,有值,这个值叫作布尔值。当条件值为真时,执行相关语句。好比:
if (Game.mousePosition.x > 200) { Canvas2D.drawImage(sprites.background, { x : 0, y : 0 }, 0, { x : 0, y : 0 }); }
若是这里if下只有一行代码,能够省略花括号写成:
if (Game.mousePosition.x > 200) Canvas2D.drawImage(sprites.background, { x : 0, y : 0 }, 0, { x : 0, y : 0 });
在这个例子中,须要检测用户是否按下了R,G,B键,表示R键按下能够像下面这么写:
Keyboard.keyDown === 82
===运算符比较两边的值,而后返回true或false。如今能够在update方法里面使用if指令来确认是否按下了R键:
if (Keyboard.keyDown === 82) cannon.currentColor = sprites.cannon_red;
比较烦人的事情是须要记住全部的虚拟键值。可是你能够用一个叫作Keys的变量来记住大部分经常使用的键值:
var Keys = { A: 65, B: 66, C: 67, D: 68, E: 69, F: 70, G: 71, H: 72, I: 73, J: 74, K: 75, L: 76, M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82, S: 83, T: 84, U: 85, V: 86, W: 87, X: 88, Y: 89, Z: 90 };
如今判断键值的代码就很清晰好理解了:
if (Keyboard.keyDown === Keys.R) cannon.currentColor = sprites.cannon_red;
常见的运算符就是这些,须要注意的是===、一个=表示赋值,那么有没有两个=的比较运算符了。实际上是有的。==也能进行比较,可是若是==两边不是同一类型,那么就会自动把其中一种类型转换成另外一类型进行比较。因此,有时候结果看起来会很奇怪。好比:
'' == '0' // false 0 == '' // true! 0 == '0' // true!
若是用===的话,上面的结果都是false。因此,最好避免使用==。
(省略)
(省略)
上节里,知道如何经过if来判断用户是否按下R键。如今,假设鼠标左键按下时才更新炮身角度。为了处理鼠标按键,须要额外的两个事件处理:一个处理用户鼠标按下,一个处理用户松开鼠标按键。这与键盘按键的按下松开相似。当鼠标按下或松开,evt对象的which变量能够知道是哪一个按键(1是左键,2是中键,3是右键)能够用一个布尔变量来表示一个按键是否按下:
var Mouse = { position : { x : 0, y : 0 }, leftDown : false };
须要添加两个事件处理函数:
function handleMouseDown(evt) { if (evt.which === 1) Mouse.leftDown = true; } function handleMouseUp(evt) { if (evt.which === 1) Mouse.leftDown = false; }
而后:
document.onmousedown = handleMouseDown; document.onmouseup = handleMouseUp;
如今,在cannon的update方法里,只有当鼠标左键按下时更新鼠标左键。
if (Mouse.leftDown) { var opposite = Mouse.position.y - this.position.y; var adjacent = Mouse.position.x - this.position.x; cannon.rotation = Math.atan2(opposite, adjacent); }
假设想鼠标左键松开后炮身角度为0,须要添加另一条if语句:
if (!Mouse.leftDown) cannon.rotation = 0;
当条件愈来愈复杂,处理条件的语句会愈来愈难理解。这里有一个很是好的处理方式,使用与if相对的条件,当不为真时执行,用到的关键字是else:
if (Mouse.leftDown) { var opposite = Mouse.position.y - this.position.y; var adjacent = Mouse.position.x - this.position.x; cannon.rotation = Math.atan2(opposite, adjacent); } else cannon.rotation = 0;
上面的代码和以前的两条if代码是同样的,可是只需写一个条件。执行painter2程序会看到效果。
(省略)
这节例子是如何处理鼠标的点击而不是鼠标按键按下(也就是说怎么知道本次按下不是先前的按下),看程序Painter2a。当鼠标点击后,炮身跟随鼠标指针移动。再次点击后,炮身中止移动。
炮筒切换中有个问题就是你只知道当前的鼠标状态。这些信息不能说明是否点击了鼠标。能够经过下面两个方面来判断用户是否点击了鼠标:
添加另一个布尔变量Mouse.leftDown ,代表用户是否按下鼠标。若是鼠标按下,该变量为true而且Mouse.leftDown非true。下面是handleMouseDown事件方法:
function handleMouseDown(evt) { if (evt.which === 1) { if (!Mouse.leftDown) Mouse.leftPressed = true; Mouse.leftDown = true; } }
上面的代码有一个if嵌套,如今能够根据鼠标是否按下来进行大炮动做的切换了、
if (Mouse.leftPressed) cannon.calculateAngle = !cannon.calculateAngle;
在if语句中,切换calculateAngle变量。这个变量是cannon对象的一个布尔成员变量。为了进行动做的切换,使用了非逻辑运算符。
如今能够上面的calculateAngle值来决定是否更新角度值了:
if (cannon.calculateAngle) { var opposite = Mouse.position.y - this.position.y; var adjacent = Mouse.position.x - this.position.x; cannon.rotation = Math.atan2(opposite, adjacent); } else cannon.rotation = 0;
为了完成这个程序,须要在每次主循环后将Mouse.leftPressed复位。以下在Mouse对象中添加一个reset方法:
Mouse.reset = function() { Mouse.leftPressed = false; };
最终,主循环看起来像这样:
Game.mainLoop = function() { Game.update(); Game.draw(); Mouse.reset(); window.setTimeout(Game.mainLoop, 1000 / 60); };
这章里,学到了:
如何经过If语句处理按键按下和鼠标点击 如何经过布尔值来规范判断条件 如何选择不一样的if条件