這章裏,創造一個叫作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); };
這章裏,學到了: