javascript
Building JavaScript Games for Phones Tablets and Desktop(3)-创造一个游戏世界
2019獨角獸企業重金招聘Python工程師標準>>>
創造一個游戲世界
這章教會你如何通過內存中儲存的信息創造一個游戲世界。介紹了基本類型和變量并且這些變量是如何儲存和改變信息的。接下來,你會看到如何用對象儲存更復雜的信息,里面包含成員變量和方法。
基本類型和變量
先前的章節幾次討論到了內存。你已經看到了如何執行一個簡單的指令譬如canvasContext.fillStyle = “blue”;為畫布設置一個顏色。在這章例子里,你使用內存儲存臨時信息,是為了記住一些簡單運算的結果。在這個例子里,你使用經過的時間來改變背景色。
類型
類型,或者數據類型,代表不同類型的信息。先前的例子使用了不同類型的信息,當做參數傳遞給了函數。例如,fillRect函數,需要4個整數,start函數需要一個文本標識引用自canvas,update和draw函數不需要任何信息。瀏覽器/解釋器可以區分這些不同類型的信息并且很多情況下可以把一個類型的信息轉換成另一個類型的信息。比如,在JavaScript里,你可以使用單引號或者雙引號來標識文本。如下的兩句指令是一樣的:
canvas = document.getElementById("myCanvas"); canvas = document.getElementById('myCanvas');瀏覽器可以自動的在不同類型的信息間進行轉換。比如,下面的語句不會導致語法錯誤:
canvas = document.getElementById(12);這個當做參數傳遞的整數會被轉換成文本。在這種情況下,當然,這里沒有ID叫做12的canvas,所以這個程序是不正確的。如果你像下面一樣替換canvas的ID,程序就會正常運行:
<canvas id="12" width="800" height="480"></canvas>瀏覽器自動的在文本和數字之間進行轉換。
現代編程語言都比JavaScript嚴格的多。比如Java和C#,不同類型之間轉換是有限制的。大多時候,你必須明確的告訴編譯器這里有一個類型轉換。
為什么對類型轉換需要這么嚴格的限制呢?首先,一個函數或方法中明確的類型參數可以讓程序員簡單的使用函數。比如下面這樣:
function playAudio (audioFileId)簡單的看一下,你并不確定audioFileId是一個數字還是文本。在C#里,這個函數會類似這樣:
void playAudio(string audioFileId)可以看出,不僅有參數名,而且還附有參數類型。類型是string,也就是字符串。此外,在函數名前面還有void這個單詞,表示這個函數沒有返回值。
聲明和變量賦值
在JavaScript里面儲存信息和使用信息都是很簡單的。你需要做的只是為你要引用的信息提供一個名字。這個名字就叫做變量。當你想在程序里使用變量時,在使用之前聲明它是一個好習慣。下面是如何聲明一個變量:
var red;在這里,red就是變量名。你可以在你隨后的程序用它儲存信息。
當你聲明了一個變量,你不需要提供你需要儲存信息的類型。變量僅僅是內存里有一個名字。有些編程語言要求在變量聲明時確定變量的類型。比如在Java或者C++里。然而,很多的腳本語言(包括JavaScript)允許你聲明一個變量而不需要指定其類型。當編程語言聲明變量時不要指定類型,這個編程語言就有松散類型。在JavaScript里,你可以聲明不止一個變量,比如:
var red, green, fridge, grandMa, applePie;這里你聲明了5個不同的變量。當你聲明這些變量時,你沒有賦值。在這種情況下,變量是未定義的。你可以用賦值指令為變量賦值。比如,給red賦值:
red = 3;賦值指令包含下面這些:
- 變量名
- ”=“符號
- 變量值
- 一個分號
你可以發現賦值指令中間有個等號。然而,在JavaScript里,最好把”=“看成”變為“而不是”等于“。畢竟,變量還沒有等于右邊的值,它在指令執行之后才變成那個值。下面的語法圖描述了賦值指令。(圖3-1)
(省略圖3-1)
現在知道如何聲明一個變量和給變量賦值了。如果你在聲明變量時就知道給變量賦什么值,你可以聲明的同時進行賦值。比如:
var red = 3;當執行這句后,內存中就會有3這個值,如圖3-2所示。
(省略圖3-2)
這里有更多關于數值變量的聲明和賦值:
var age = 16; var numberOfBananas; numberOfBananas = 2; var a, b; a = 4; var c = 4, d = 15, e = -3; c = d; numberOfBananas = age + 12;在第四行,你可以發現一次可以聲明多個變量。也可以像第六行一樣有多個變量進行了聲明賦值。在賦值的右邊,你可以放置其它變量或者數學表達式,正如最后兩行所示。指令 c = d;讓儲存在d中的值也儲存在了c里面。因為d是15,所以執行這條指令后,c也是15.最后一條指令讓age加上12,把結果儲存在numberOfBananas里面。概括來說,執行這些指令后,這些內存值看起來就像圖3-3所示:
(省略圖3-3)
全局變量和嚴格模式
除了在使用變量之前進行聲明,javascript也可以直接使用變量而不用聲明它。比如下面這樣:
var a = 3; var b; b = 4; x = a + b;如上所示,前兩行指令通過var關鍵字聲明了變量a和b。變量x從來沒有聲明,但是它用來儲存a和b的和。javascript允許這樣。這很糟糕,而且這也是它為什么糟糕的原因。問題在于沒有聲明的變量javascript解釋器會自動的把它進行聲明,而你卻完全不知道。如果你在其它地方使用同樣名字的變量,你的程序可能發生你意想不到的結果。另外,如果你使用了很多不同的變量,你最好同時記錄這些變量。但是最大的問題是下面這個:
var myDaughtersAge = 12; var myAge = 36; var ourAgeDifference = myAge - mydaughtersAge;當編寫這些指令時,你希望的是ourAgeDifference的值是24.但是,這個值是未定義的。這是因為第三行這里有個打字錯誤。變量的名字不是mydaughtersAge,而是myDaughtersAge。這種情況下,瀏覽器/解釋器是悄悄的聲明一個叫做mydaughtersAge的變量而不是拋出一個錯誤停止運行腳本。因為變量沒有定義,所有與此變量有關的計算都是未定義的。因此,變量ourAgeDifference也是未定義的。
這個問題真的很棘手。幸運的是,新的EMCAScript5標準有個叫做嚴格模式的東西。當腳本用嚴格模式來解釋時,它不允許在沒有聲明變量之前使用變量。如果你想你的腳本在嚴格模式下執行,你需要做的僅僅只是在腳本開始加上下面這行,例如:
"use strict"; var myDaughtersAge = 12; var myAge = 36; var ourAgeDifference = myAge - mydaughtersAge;“use strict”告訴了解釋器在嚴格模式下解釋腳本。如果你現在執行這段代碼,瀏覽器會停止運行且拋出錯誤告知有變量沒有進行聲明。
除了能檢查變量在使用之前是否聲明了,嚴格模式也包含了其他一些能讓書寫正確javascript代碼更簡單的東西。
我非常推薦你在嚴格模式下書寫你所有的javascript代碼。所以本書的所有javascript代碼都是嚴格模式下書寫的,這能幫程序員省掉很多麻煩且這樣的代碼在未來版本的javascript也是無可挑剔的。
指令和表達式
如果你看到語法圖里面的元素,你可能已經注意到一些在賦值語句右邊的值或者程序片段,它們被叫做表達式。那么表達式和指令有什么不同呢?兩者不同的是指令某種方式上改變內存,而表達式有一個值。指令通常使用表達式。這里有幾個表達式的例子:
16 numberOfBananas 2 a + 4 numberOfBananas + 12 - a -3 "myCanvas"所有的這些表達式代表了一個確定的類型。除了最后一行,所有的表達式都是數值。最后一行是字符串。除了數值和字符串,還有其他類型的表達式。我討論的是本書最重要的表達式。比如,下節我會討論運算符的表達式,第7章會講使用函數或者方法作為一個表達式。
運算符和更復雜的表達式
這節討論javascript中不同的運算符。你會了解到運算符的優先級。你也可以了解到有些時候,在javascript中表達式也能相當的復雜。比如,一個變量可以包含多個值,或者可以代表一個函數。
算術運算符
(省略)
運算符優先級
(省略)
把一個函數賦值給變量
在javascript中,函數被儲存在內存里。正因為如此,函數也是表達式。所以,可以給一個變量賦值為函數。例如:
var someFunction = function () { // do something }這個例子聲明了一個變量并進行了賦值。這個變量的值是一個無名函數。如果你想執行這個函數,你可以通過變量名字調用,如下:
someFunction();那么像下面這樣定義一個函數和你之前看到的有什么不同呢?
function someFunction () { // do something }實際上,這并沒有什么不同。主要是如果不像傳統方式那樣定義一個函數,那么這個函數在定義之前不能使用。當瀏覽器執行一個javascript文件,有兩個步驟。第一個步驟,瀏覽器構造一個函數的列表。第二步,瀏覽器解釋剩下的腳本。這對正確執行腳本很有必要,瀏覽器需要知道哪個函數是有效的。比如,下面的這段代碼可以運行,即使這個在后面被定義:
someFunction(); function someFunction () { // do something }然而,如果一個函數被賦值給一個變量,那么就只剩上述的第二步了。意味著下面的代碼會報錯。
someFunction(); var someFunction = function () { // do something }瀏覽器會告知有一個變量沒有進行聲明。在定義之后進行調用就可以了,比如:
someFunction(); var someFunction = function () { // do something }多個值組成的變量
一個變量可以由多個值組成而不是只能單一值。這就像函數里面做的一樣,把指令組合在一起。比如:
function mainLoop () { canvasContext.fillStyle = "blue"; canvasContext.fillRect(0, 0, canvas.width, canvas.height); update(); draw(); window.setTimeout(mainLoop, 1000 / 60); }跟函數一樣,你可以把一些變量放在一個更大的變量里面。這個更大的變量就有了更多的值。就像下面這個例子:
var gameCharacter = { name : "Merlin", skill : "Magician", health : 100, power : 230 };這是一個復合變量的例子。變量gameCharacter有一些變量。這些變量有名字和值。因此,在某種意義上說,變量gameCharacter由其它一些變量組成。每個子變量都有一個名字,冒號后面是值。這個包含名字的表達式和花括號之間的值被叫做對象字變量。圖3-6顯示了對象字變量的的語法圖:
(省略圖3-6)
在聲明和實例化變量gameCharacter之后,這塊內存看起來如圖3-7:
(省略3-7)
你可以像下面這樣獲取一個復合變量的值。
gameCharacter.name = "Arjan"; var damage = gameCharacter.power * 10;正如你看到的那樣,你可以獲取gameCharacter變量中的某個變量,通過在gameCharacter后面加上一個點和子變量名。javascript甚至允許在聲明和實例化復合變量后修改這個復合變量的值。舉個例子,看下面這段代碼:
var anotherGameCharacter = { name : "Arthur", skill : "King", health : 25, power : 35000 };anotherGameCharacter.familyName = "Pendragon";現在anotherGameCharacter有5部分了,name, skill, health, power, familyName。
因為變量也可以指向函數,所以你可以包含一個指向函數的子變量。如下所示:
var anotherGameCharacter = { name : "Arthur", familyName : "Pendragon", skill : "King", health : 25, power : 35000, healMe : function () { anotherGameCharacter.health = 100; } };像之前一樣,你可以在這之后也能為其子變量添加一個函數。
anotherGameCharacter.killMe = function () { anotherGameCharacter.health = 0; };你可以調用其他變量一樣調用這些函數。下面的指令恢復了游戲角色的生命:
anotherGameCharacter.healMe();如果你想殺死角色,可以調用anotherGameCharacter.killMe();組合變量和函數最棒的地方在于你可以把相關的變量和函數放在一起。這個例子就是把與同一個游戲角色相關的變量放在了一起,同時增加了幾個相關的函數。從現在開始,如果一個函數屬于一個變量,我把這個函數叫做方法。把一個由其他變量組成的變量叫做對象。如果一個變量是對象的一部分,這個變量叫做成員變量。
你可以想象對象和方法的能量有多強大。它們提供了一個進入復雜游戲世界的方式。如果javascript沒有這種能力,那么在程序的開頭,你會聲明一長串的變量,并且不知道這些變量之間如何相關且能做什么。把變量裝進對象里且給對象提供方法,你可以寫出更容易理解的程序。在下節,你就要使用這種強大的能力來寫一個簡單的移動方塊程序。
方塊移動游戲
這節實現了一個方塊在畫布上移動的簡單程序。主要有兩個目的:
- 關于游戲循環里Update和draw函數的更多細節
- 如何使用對象來結構化程序
在寫這個程序之前,讓我們再一次看看BasicGame例子的代碼:
var canvas = undefined; var canvasContext = undefined; function start () { canvas = document.getElementById("myCanvas"); canvasContext = canvas.getContext("2d"); mainLoop(); } document.addEventListener('DOMContentLoaded', start); function update () { } function draw () { canvasContext.fillStyle = "blue"; canvasContext.fillRect(0, 0, canvas.width, canvas.height); } function mainLoop () { update(); draw(); window.setTimeout(mainLoop, 1000 / 60); }我們現在用上面學到的關于對象的知識來把這段代碼重新整理成一個游戲程序。如下:
"use strict"; var Game = { canvas : undefined, canvasContext : undefined };Game.start = function () { Game.canvas = document.getElementById("myCanvas"); Game.canvasContext = Game.canvas.getContext("2d"); Game.mainLoop(); }; document.addEventListener('DOMContentLoaded', Game.start); Game.update = function () { }; Game.draw = function () { Game.canvasContext.fillStyle = "blue"; Game.canvasContext.fillRect(0, 0, Game.canvas.width, Game.canvas.height); }; Game.mainLoop = function () { Game.update(); Game.draw(); window.setTimeout(mainLoop, 1000 / 60); };你創建了一個叫做Game的復合變量。這個對象有兩個成員變量,canvas和canvasContext。此外,你給這個對象添加了幾個方法,包括構成這個游戲循環的方法。你分開定義了這個對象的方法。這樣做的原因是你可以清楚的分辨出這些數據和方法組成的對象可以如何與數據打交道。需要注意的是,像我推薦的一樣,添加了“use strict”。
現在,讓我們對這個程序進行擴展。它需要顯示一個在屏幕上移動的方塊。你想隨著時間改變方塊的X坐標值。為了做到這些,你必須把現在的X坐標值儲存在變量里。那樣你就可以在update里面給變量進行賦值并且使用這個值在draw方法里畫出這個方塊。放置這個變量的地方是在Game對象里,你可以像下面一樣聲明和實例化:
var Game = { canvas : undefined, canvasContext : undefined, rectanglePosition : 0 };使用變量rectanglePosition來儲存在方塊的X坐標值。在draw方法里,你可以使用這個值在屏幕上畫出方塊。這個例子里,繪畫出一個不超過畫布大小的方塊,下面是draw方法的內容:
Game.draw = function () { Game.canvasContext.fillStyle = "blue"; Game.canvasContext.fillRect(Game.rectanglePosition, 100, 50, 50); }現在你需要做的就是計算X的坐標值。在update方法里計算X值,因為改變X值以為著更新著游戲世界。在這個例子里,我們基于時間的流逝來改變方塊的坐標值。在javascript獲取系統的時間值:
var d = new Date(); var currentSystemTime = d.getTime();在此之前,你還沒看過像第一行那樣的符號。現在,假設new Date()創造了一個復合變量(對象),里面有跟時間相關的信息,還有一些有用的方法。其中一個方法是getTime。你通過對象d調用此方法并儲存在currentSystemTime里。現在這個變量就有了從1970年1月1日開始的時間。你可以想象到這個變量值有點大。如果你想設置X坐標值,那么就要一個很大的屏幕。當然,不可能這樣做,你用時間對畫布的寬度求余,余數作為X坐標值。那樣,你始終得到的是一個在0到畫布寬度之間的值。update方法如下:
Game.update = function () { var d = new Date(); Game.rectanglePosition = d.getTime() % Game.canvas.width; };如你所知,update和draw方法順序調用,大約每秒60幀。每一次時間改變,系統時間也改變,意味著方塊的坐標值也改變,那么方塊顯示的地方就與之前不一樣。
在這個例子運行的如你所想之前你還要做一件事情。如果你像這樣運行程序,一個藍色的條將出現在屏幕上。因為你在舊的方塊上繪畫了新的方塊。為了解決這個問題,每次畫方塊之前你需要清除畫布。清除畫布用clearRect方法。這個方法清除掉指定大小的畫布。舉例:
Game.canvasContext.clearRect(0, 0, Game.canvas.width, Game.canvas.height);為了方便,把這條指令放在一個叫做clearCanvas的方法里,如下:
Game.clearCanvas = function () { Game.canvasContext.clearRect(0, 0, Game.canvas.width, Game.canvas.height); };你需要做的就是在游戲循環里,在update和draw方法之前調用上面的方法。
Game.mainLoop = function() { Game.clearCanvas(); Game.update(); Game.draw(); window.setTimeout(Game.mainLoop, 1000 / 60); };這個例子就完成了。運行結果如圖3-8所示:
(省略圖3-8)
變量的范圍
你聲明變量的地方決定了你能在哪些地方使用這些變量。看上面程序的d變量。它聲明在update方法里,所以它只能在update方法里面使用。就是說,你不能在draw方法里面使用這個變量。當然,你可以在draw方法里面重新申請一個d變量,但是需要意識到的是這里的d變量和update方法里面的d變量是不一樣的。
相對的,你在一個對象的水平上聲明一個變量,你就可以在任何地方使用這個變量。你需要在update和draw方法里面使用方塊的X坐標,因為在update里面需要更新坐標值,draw里面需要繪畫出方塊。因此需要在對象這個水平上聲明變量,那樣對象的所有方法都可以使用這個變量。
變量可以在哪里使用叫做變量范圍。在這個例子里面,d的變量范圍是update方法,Game.recentPostion是全局范圍。
你學到了什么
在這章里,你學到了:
- 怎樣使用變量在內存里儲存信息
- 怎樣創建含有變量和方法的對象
- 怎樣通過變量和update方法改變游戲世界和draw方法在屏幕上顯示游戲世界
轉載于:https://my.oschina.net/u/1587304/blog/399862
總結
以上是生活随笔為你收集整理的Building JavaScript Games for Phones Tablets and Desktop(3)-创造一个游戏世界的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在SD/MMC卡中可读写的FAT文件系统
- 下一篇: javascript高程3 学习笔记(三