这章里,创造一个叫作Painter的游戏。这个游戏里,须要在屏幕上显示移动的精灵。你已经知道了一些加载和显示精灵的例子。并且,知道了如何用通过的时间值来改变精灵的位置。在这些基础上来建立这个painter游戏。此外,会学到如何处理用户游戏中的输入。你将从以前的FlyingSprite例子开始,把它改变成气球的位置跟随鼠标移动。下一章将检测其它类型的输入,好比键盘或者触摸屏。javascript
如今你知道如何在屏幕上显示精灵,如今来使用用户的输入来控制精灵的位置。为了作到这些,你须要知道当前鼠标的位置。这节教你如何获取鼠标位置和怎样画出一个跟随鼠标移动的精灵。java
看例子Ballon1。这个程序与以前的FlyingSprite程序没有多大的不一样。在FlyingSprite中,经过系统时间来计算气球的位置。程序员
var d = new Date(); Game.balloonPosition.x = d.getTime() * 0.3 % Game.canvas.width;
位置储存在balloonPosition变量里面。如今的程序不是根据时间来计算位置,而是根据鼠标的位置。使用事件来获取当前鼠标值是很是简单的。canvas
在javascript里有许多不一样类型的事件。常见事件例子以下:浏览器
发生上述事件,就能够选择执行指令。好比玩家移动鼠标时,你能够经过几条指令来获取鼠标位置值,储存位置信息,以后就能够用它来绘画精灵。好比当HTML网页加载时,document可让你获取网页中的元素。可是更重要的是,这些变量能让玩家经过鼠标移动,键盘或者点击触摸屏来获取信息。markdown
你已经使用过这些变量,好比下面经过document获取来自HTML网页的canvas元素:网络
Game.canvas = document.getElementById("myCanvas");
除了getElementById,document还有其它成员变量和方法。好比有个叫作onmousemove的成员变量。这个变量不是数值或字符串,而是一个方法。当鼠标移动,浏览器就会调用这个方法。那么就能够在这个方法里面写你想写的代码。正是如此,这种类型的方法才被叫作事件处理。使用事件处理是处理用户输入很是有效的方式。函数
另一个处理用户输入的方式是在每次循环里都检测用户的输入。虽然那样也行,可是那比使用事件处理慢多了,由于须要在每次的循环进行检测,而事件处理是用户作了某事就会自动知道。指针
一个事件处理函数有个特定的书写方式。它有一个参数,这个参数是个对象,能提供有关事件的信息。好比下面有个空的事件处理函数:rest
function handleMouseMove(evt) { // do something here }
这个函数有个单一的参数evt,包含了须要处理事件的信息。如今能够把这个函数赋值给onmousemove变量。
document.onmousemove = handleMouseMove;
从如今起,每次鼠标移动,handleMouseMove函数就被调用。你能够在这个函数里经过evt对象获得鼠标的位置。好比下面的例子:
function handleMouseMove(evt) { Game.balloonPosition = { x : evt.pageX, y : evt.pageY }; }
evt对象的两个成员变量pageX和pageY储存了鼠标位置,位置从左上角(0,0)算。在图5-1中能够看到一些有关位置的信息。
(省略图5-1)
由于draw方法只是简单的以鼠标位置画出气球,因此气球跟着鼠标。图5-2展现了这种效果。你能够发现气球在鼠标指针下面;当你移动鼠标,气球会跟着鼠标。
(省略图5-2)
从图5-2能够看出气球并无出现鼠标指针尖端的中心位置。这就是缘由所在,下节会详细说明这一切。如今把这个精灵看成一个矩形。左上角就是鼠标的尖端。气球看起来没有与尖端对齐是由于气球是圆的且没有彻底覆盖矩形。
除了pageX和pageY,也可使用clientX和clientY,也能表明鼠标位置。然而,clientX和clientY不计算鼠标的滚动值。假以下面这样计算鼠标位置:
Game.balloonPosition = { x : evt.clientX, y : evt.clientY };
图5-3告诉了错误所在。由于鼠标滚动,clientY比480小,即便鼠标已经来到了图片的底部。所以,气球不会出如今出表出现的位置。所以,我建议一直使用pageX和pageY。固然在某些状况下,不把滚动值加入位置是有用的——好比,当网页有广告时不会随着滚轮移动而移动。
(省略图5-3)
当运行Balloon1例子,注意到气球的左上角是当前鼠标位置。当你把精灵画在某个肯定的位置,那么这个精灵的左上角就是这个位置。好比下面这条指令:
Game.drawImage(someSprite, somePosition);
someSprite的左上角就在somePosition位置上。也能够说左上角就是精灵的原点。若是你想改变原点怎么办?好比你想让精灵的中心为原点,此时能够经过Image类的width和height变量计算这个原点值。如今声明一个叫作origin的变量来储存精灵中心的位置值。
var origin = { x : someSprite.width / 2, y : someSprite.height / 2 };
如今若是你想把精灵someSprite画在不一样的原点,能够这么作:
var pos = { x : somePosition.x - origin.x, y : somePosition.y - origin.y }; Game.drawImage(someSprite, pos);
经过减去原点值,精灵的原点位置如今就是精灵的中心了。除了本身计算相关原点值,来自canvasContext的方法drawImage能够指定原点偏离值。以下:
Game.canvasContext.save(); Game.canvasContext.translate(position.x, position.y); Game.canvasContext.drawImage(sprite, 0, 0, sprite.width, sprite.height, -origin.x, -origin.y, sprite.width, sprite.height); Game.canvasContext.restore();
经过设定上述不一样的偏离值,能够获得精灵两种不一样的显示方式,一种原点在精灵的左上角,一种在精灵的中心,如图5-4所示。
(省略图5-4)
在JavaScript中与另外一个位置之间作减法有点麻烦:
var pos = { x : somePosition.x - origin.x, y : somePosition.y - origin.y };
若是能够下面这样写那就舒服多了:
var pos = somePosition - origin;
不幸的是,在JavaScript里这是不可能的。其余一些语言(好比Java和C#)支持运算符重载,其容许程序员定义他们本身的运算操做。两个对象能够经过“+”进行相加。可是并非彻底没有可能,咱们能够定义方法来让对象之间进行运算就像上面那样。第8章会仔细讲解这个东西。
如今知道怎样绘画精灵的不一样原点。同理你也可让鼠标跟随气球底部的中心进行移动。例子balloon2实现了这种想法。在这里定义了一个额外的储存原点的变量:
var Game = { canvas : undefined, canvasContext : undefined, backgroundSprite : undefined, balloonSprite : undefined, mousePosition : { x : 0, y : 0 }, balloonOrigin : { x : 0, y : 0 } };
你能够只在精灵加载的时候进行一次原点的计算。所以,在draw方法中经过下面的代码计算出原点:
Game.balloonOrigin = { x : Game.balloonSprite.width / 2, y : Game.balloonSprite.height };
原点位置是精灵宽度的一半,可是高度是精灵的所有高度。换句话说,原点在精灵的底部中心处。在draw方法里面计算原点并非最好的,它出如今只须要计算一次原点的地方。以后,你会发现更好的处理方法。
如今只须要扩展下drawImage方法,加入新的一个参数来传递原点值就能够实现想要的效果了。下面是实现的代码:
Game.drawImage = function (sprite, position, origin) { Game.canvasContext.save(); Game.canvasContext.translate(position.x, position.y); Game.canvasContext.drawImage(sprite, 0, 0, sprite.width, sprite.height, -origin.x, -origin.y, sprite.width, sprite.height); Game.canvasContext.restore(); };
在draw方法里,能够计算原点的位置而后传递给drawImage方法,以下:
Game.draw = function () { Game.drawImage(Game.backgroundSprite, { x : 0, y : 0 }, { x : 0, y : 0 }); Game.balloonOrigin = { x : Game.balloonSprite.width / 2, y : Game.balloonSprite.height }; Game.drawImage(Game.balloonSprite, Game.mousePosition, Game.balloonOrigin); };
painter游戏的特色之一就是经过能够经过鼠标来进行炮身的旋转。玩家能够经过大炮来射击气球改变气球颜色。painter1例子就实现了炮身经过鼠标进行的旋转。
如今须要声明一些成员变量。首先是储存背景图和炮身图的变量。还有当前鼠标的位置。而后还要能储存炮身位置的变量等等。看起来Game对象是这样的:
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 };
炮身的位置和炮身原点的位置都已经定义了。炮身位置的选取是和大炮基座很是契合的。炮身中有一部分圆的。你想让大炮跟随圆心转动。意味着圆心就是原点。由于圆形的这部分在图片的左半边且半径是炮身高度的一半(炮身高度68像素),因此原点就是(34,34),这跟上面的代码同样。
为了让炮身看起来有一个角度,你须要在屏幕上画炮身时让炮身有一个旋转。在canvascontext中有个rotate方法。
如今在drawImage方法中添加关于旋转角度的参数。下面是新的drawImage方法:
Game.drawImage = function (sprite, position, rotation, origin) { Game.canvasContext.save(); Game.canvasContext.translate(position.x, position.y); Game.canvasContext.rotate(rotation); Game.canvasContext.drawImage(sprite, 0, 0, sprite.width, sprite.height, -origin.x, -origin.y, sprite.width, sprite.height); Game.canvasContext.restore(); };
在start方法里,添加两个精灵:
Game.backgroundSprite = new Image(); Game.backgroundSprite.src = "spr_background.jpg"; Game.cannonBarrelSprite = new Image(); Game.cannonBarrelSprite.src = "spr_cannon_barrel.png";
下一步就是实现游戏循环,截止目前,update中还什么都没有。如今要开始往里面写东西了。须要计算出炮身要偏转的角度。那么怎么计算这个角度呢,看图5-5:
(省略图5-5)
能够用数学中的三角函数来计算角度。在这里,使用正切函数:
tan(angle) =tan(opposite/adjacent)
换个样式,也就是:
angle = arctan(opposite/adjacent)
能够经过计算当前鼠标位置和炮身位置的不一样来计算出邻边和对边的长度。以下:
var opposite = Game.mousePosition.y - Game.cannonPosition.y; var adjacent = Game.mousePosition.x - Game.cannonPosition.x;
经过上面的值能够算出角度。怎么计算呢,javascript提供了一个Math对象,里面包含了一些很是有用的数学函数,包括三角函数。有两个函数能够计算角度值。第一个函数只须要一个单一的函数,可是不能用由于当鼠标在炮身正上方,除数是0.
考虑到这种状况,这里有另一个方法。atan2使用邻边和对边两个参数来进行角度计算。以下:
Game.cannonRotation = Math.atan2(opposite, adjacent);
如今update看起来就像下面这样:
Game.update = function () { var opposite = Game.mousePosition.y - Game.cannonPosition.y; var adjacent = Game.mousePosition.x - Game.cannonPosition.x; Game.cannonRotation = Math.atan2(opposite, adjacent); };
最后剩下的就是使用draw方法显示出这些精灵了。
Game.draw = function () { Game.clearCanvas(); Game.drawImage(Game.backgroundSprite, { x : 0, y : 0 }, 0, { x : 0, y : 0 }); Game.drawImage(Game.cannonBarrelSprite, Game.cannonPosition, Game.cannonRotation, Game.cannonOrigin); };
这章里,学到了: