這章裏,學會如何處理按鍵輸入。爲了實現這個要求,你須要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條件