game.html
js/
裏面建立game.js
images/
裏面放三張圖片,一張背景圖片(background.png),一張英雄圖片(hero.png),一張怪物的圖片(monster.png)在game.html
裏面寫上如下幾行簡單的HTML代碼:html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Simple Canvas Game</title> </head> <body> <script src="js/game.js"></script> </body> </html>
咱們在game.html
引入了game.js
文件,沒錯,剩下的全部工做都是在操做game.js
,爲其添加遊戲的js代碼。前端
在game.js 裏面,咱們首先須要爲遊戲的舞臺建立一張畫布(canvas):web
var canvas = document.createElement("canvas"); var ctx = canvas.getContext("2d"); canvas.width = 512; canvas.height = 480; document.body.appendChild(canvas);
這裏經過js來建立了一個元素並設置canvas
的寬和高,最後將其添加到<body>標籤後。var ctx = canvas.getContext("2d");
中的ctx
變量是咱們後面會用到的,具體的canvas用法查看這裏的連接:canvas
https://developer.mozilla.org/en/canvas_tutorial瀏覽器
遊戲須要加載咱們以前存放在images文件夾下面的三張圖片:app
// Background image var bgReady = false; var bgImage = new Image(); bgImage.onload = function () { bgReady = true; }; bgImage.src = "images/background.png"; // Hero image var heroReady = false; var heroImage = new Image(); heroImage.onload = function () { heroReady = true; }; heroImage.src = "images/hero.png"; // Monster image var monsterReady = false; var monsterImage = new Image(); monsterImage.onload = function () { monsterReady = true; }; monsterImage.src = "images/monster.png";
以上三張圖片都是經過建立簡單的圖片對象來實現加載的,相似bgReady的三個變量用來標識圖片是否已經加載完成,若是若是在圖片加載未完成狀況下進行繪製是會報錯的。若是你不太肯定new Image()
究竟是個什麼東西,你能夠在bgImage.src = "images/background.png";
以後使用console.log(bgImage);
來查看,你看到的將是相似:dom
<img src="images/background.png" >
咱們須要定義一些對象,以便咱們在後面會用到:異步
var hero = { speed: 256 // movement in pixels per second }; var monster = {}; var monstersCaught = 0;
既然是英雄抓獲怪物,咱們得要有一個英雄
和怪物
的對象。而英雄
有一個speed
屬性用來控制他每秒移動多少像素。怪物遊戲過程當中不會移動,因此暫時不用設置屬性。monstersCaught
則用來存儲怪物被捉住的次數,初始值固然爲0了。函數
遊戲是給人玩的,那麼咱們怎麼知道用戶到底在這個過程當中幹了什麼?按了鍵盤?點了鼠標?這些都是用戶在玩遊戲的時候的輸入,因此咱們一旦捕獲到這些輸入,咱們就能夠根據遊戲的邏輯對用戶的輸入進行處理了:oop
// Handle keyboard controls var keysDown = {}; addEventListener("keydown", function (e) { keysDown[e.keyCode] = true; }, false); addEventListener("keyup", function (e) { delete keysDown[e.keyCode]; }, false);
這裏咱們只是監聽兩個用戶的輸入:
而後咱們將用戶的輸入先保存起來,並無當即響應。爲此,咱們用keysDown
這個對象來保存用戶按下的鍵值(keyCode)
,若是按下的鍵值在這個對象裏,那麼咱們就作相應處理。
在前端開發中,通常是用戶觸發了點擊事件而後纔去執行動畫或發起異步請求之類的
遊戲在結束的時候,咱們須要開始新的一輪遊戲,因此在game.js
添加reset
函數
// Reset the game when the player catches a monster var reset = function () { hero.x = canvas.width / 2; hero.y = canvas.height / 2; // Throw the monster somewhere on the screen randomly monster.x = 32 + (Math.random() * (canvas.width - 64)); monster.y = 32 + (Math.random() * (canvas.height - 64)); };
reset()
函數用於開始新一輪和遊戲,在這個方法裏咱們將英雄放回畫布中心同時將怪物放到一個隨機的地方。
在遊戲的過程當中,不論是用戶在玩(有正確輸入的狀態)仍是遊戲結束,咱們都是須要及時更新遊戲的對象:
var update = function (modifier) { if (38 in keysDown) { // Player holding up hero.y -= hero.speed * modifier; } if (40 in keysDown) { // Player holding down hero.y += hero.speed * modifier; } if (37 in keysDown) { // Player holding left hero.x -= hero.speed * modifier; } if (39 in keysDown) { // Player holding right hero.x += hero.speed * modifier; } // Are they touching? if ( hero.x <= (monster.x + 32) && monster.x <= (hero.x + 32) && hero.y <= (monster.y + 32) && monster.y <= (hero.y + 32) ) { ++monstersCaught; reset(); } };
update
函數負責更新遊戲的各個對象,會被規律地重複調用。首先它負責檢查用戶當前按住的是中方向鍵,而後將英雄往相應方向移動。
有點費腦力的或許是這個傳入的modifier
變量。你能夠後面
將要實現的main
方法裏看到它的來源,但這裏仍是有必要詳細解釋一下。它是基於1開始且隨時間變化的一個因子。例如1秒過去了,它的值就是1,英雄的速度將會乘以1,也就是每秒移動256像素;若是半秒鐘則它的值爲0.5,英雄的速度就乘以0.5也就是說這半秒內英雄以正常速度一半的速度移動。理論上說由於這個update
函數被調用的很是快且頻繁,因此modifier
的值會很小,但有了這一因子後,無論咱們的代碼跑得快慢,都可以保證英雄的移動速度是恆定的。
這裏須要說明一下下面的判斷怪物和英雄是什麼根據:
if ( hero.x <= (monster.x + 31) && monster.x <= (hero.x + 31) && hero.y <= (monster.y + 32) && monster.y <= (hero.y + 32) )
上面的31
,32
是由hero
和monster
圖片的大小決定的,咱們的hero圖片是32x32
,monster圖片是30x32
,因此根據座標的位於圖片中心的法制,就能夠獲得上面的判斷條件。
如今英雄的移動已是基於用戶的輸入(按下上
,下
,左
,右
鍵)了,接下來該檢查移動過程當中所觸發的事件了,也就是英雄與怪物相遇。這就是本遊戲的勝利點,monstersCaught +1
而後從新開始新一輪。
以前寫的代碼都是在準備前期工做和處理一些遊戲的狀態等,下面將進入正題:咱們須要將全部的東西畫出來
// Draw everything var render = function () { if (bgReady) { ctx.drawImage(bgImage, 0, 0); } if (heroReady) { ctx.drawImage(heroImage, hero.x, hero.y); } if (monsterReady) { ctx.drawImage(monsterImage, monster.x, monster.y); } // Score ctx.fillStyle = "rgb(250, 250, 250)"; ctx.font = "24px Helvetica"; ctx.textAlign = "left"; ctx.textBaseline = "top"; ctx.fillText("Goblins caught: " + monstersCaught, 32, 32); };
這裏的ctx
就是最前面咱們建立的變量。而後利用canvas
的drawImage()
首先固然是把背景圖畫出來。而後如法炮製將英雄和怪物也畫出來。這個過程當中的順序是有講究的,由於後畫的物體會覆蓋以前的物體。
這以後咱們改變了一下Canvas
的繪圖上下文的樣式並調用fillText
來繪製文字,也就是記分板那一部分。本遊戲沒有其餘複雜的動畫效果和打鬥場面,繪製部分大功告成
咱們實現了將畫面畫出來之後,咱們緊接着須要實現的就是遊戲的循環結構,因而將它放在main
函數裏:
// The main game loop var main = function () { var now = Date.now(); var delta = now - then; //console.log(delta); update(delta / 1000); render(); then = now; // Request to do this again ASAP requestAnimationFrame(main); };
上面的主函數控制了整個遊戲的流程。先是拿到當前的時間用來計算時間差(距離上次主函數被調用時過了多少毫秒)。獲得modifier
後除以1000(也就是1秒中的毫秒數)再傳入update
函數。最後調用render
函數而且將本次的時間保存下來。
在上面的main
函數中,咱們經過requestAnimationFrame()
調用了main
函數,因此咱們須要聲明:
var w = window; requestAnimationFrame = w.requestAnimationFrame || w.webkitRequestAnimationFrame || w.msRequestAnimationFrame || w.mozRequestAnimationFrame;
這裏這麼多的||
,不爲別的,就是考慮到瀏覽器兼容問題而已。
萬事具有,只欠東風。到此,全部的遊戲代碼基本就寫完了,咱們如今須要作的就是調用相應的函數來啓動遊戲:
// Let's play this game! var then = Date.now(); reset(); main();
到這裏代碼就寫完了。先是設置一個初始的時間變量then
用於首先運行main
函數使用。而後調用 reset
函數來開始新一輪遊戲(若是你還記得的話,這個函數的做用是將英雄放到畫面中間同時將怪物放到隨機的地方以方便英雄去捉它)
用瀏覽器打開game.html
,開始玩遊戲吧!
在玩遊戲的過程當中,你會發現每一次hero
捕獲到monster
,hero
就回到了canvas
畫布的正中間。那麼如今須要作的就是,將hero
在捕捉到monster
的時候讓hero
就停留在捕獲的位置,再也不是回到canvas
正中間。
這個現象的出現主要是由於在reset
函數中將hero.x
和hero.y
寫死了,因此一個最簡單的方法就是在reset
中傳入參數:
var reset = function (x,y) { hero.x = x; hero.y = x; };
而後在update
調用reset
的時候傳入捕獲時位置的參數:
var update = function (modifier) { //...other codes ++monstersCaught; reset(heor.x,hero.y); };
最後在開始遊戲的時候將hero
放在canvas
最中間便可:
var then = Date.now(); reset(canvas.width / 2,canvas.height / 2); main();
大功告成!
Hapyy Hacking