Building JavaScript Games for Phones Tablets and Desktop(6)- 響應玩家輸入

響應用戶輸入

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

比較運算符

  • < Less than
  • <= Less than or equal to
  • > Greater than
  • >= Greater than or equal to
  • === Equal to
  • !== Not equal to

常見的運算符就是這些,須要注意的是===、一個=表示賦值,那麼有沒有兩個=的比較運算符了。實際上是有的。==也能進行比較,可是若是==兩邊不是同一類型,那麼就會自動把其中一種類型轉換成另外一類型進行比較。因此,有時候結果看起來會很奇怪。好比:

'' == '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。當鼠標點擊後,炮身跟隨鼠標指針移動。再次點擊後,炮身中止移動。

炮筒切換中有個問題就是你只知道當前的鼠標狀態。這些信息不能說明是否點擊了鼠標。能夠經過下面兩個方面來判斷用戶是否點擊了鼠標:

  • 當前鼠標狀態爲按下
  • 在最近的一次update方法調用期間中鼠標鍵狀態不是按下

添加另一個布爾變量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條件
相關文章
相關標籤/搜索