一個簡單的2D網頁小遊戲它的製做過程是有規律可尋的,它每一個部分都有必定的套路,咱們應該
把有規律的部分總結在一塊兒,造成一個模板,把相應的模板寫好了,到要生產某個對象時就能夠用
模板,還有遊戲的整個流程每一個函數,每一個js文件之間都是分工明確的;咱們要總結好了才寫,
不要想到哪就寫哪,這樣雖然結果是相同的,但可能代碼的可讀性和維護性不是很強,思路不是很
清晰;那麼就要總結出一個清晰的遊戲流程;web
遊戲很是簡單,無非就是我放飛機移動發射子彈設計敵機相碰撞就得分,若碰到敵機就over,而後
遊戲從新開始,怪物出如今地圖的隨機位置,英雄初始化在地圖的中間。canvas
首先是靜態界面--->第二創建js文件(
1.一個遊戲的主要的js便是包括了整個遊戲的主循環函數,還有改變全部具備動態效果的對象的位
置的函數,還有當全部具備動態效果的對象的位置改變時從新化界面的函數-app.js、
2.加載全部圖片的js便是要等遊戲中要用到的全部圖片都加載完成後在開始進行下一步操做-
resources.js、
3.全部的對象的一個精靈js即便把遊戲中全部對象所共有的一些屬性或是行爲封裝成一個新的對象-
Sprite.js、
四、對全部輸入事件進行處理操做的js-input.js;)數組
2D網頁小遊戲它的重點就是靠圖片來美化界面,顯而易見圖片很重要,全部操做都基於圖片上,因此
要等全部的圖片都加載好了才進行動態操做 這就涉及到如何判斷所有圖片都加載好了 瀏覽器
咱們先舉個列子來看如何等圖片加載好了在進行操做:app
// 背景圖片 bgImage var bgReady = false; var bgImage = new Image(); bgImage.onload = function () { alert("ssssss") bgReady = true; }; bgImage.src = "images/background.png";
這裏須要注意的一點,把bgImage.src
寫在bgImage.onload
以後是爲了解決IE顯示的bug,
因此建議你們都這麼麼寫。框架
由此段代碼咱們能夠發現當圖片加載好了以後 bgReady 就是true,判斷圖片是否加載好了就能夠
以此爲判斷依據; dom
那麼對於這個遊戲模板來講咱們會用到兩張圖片(背景、全部對象的合成圖),對這兩張圖盤片的操
做以下:ide
(function(){//這樣寫是爲了減小全局變量,節約瀏覽器的空間(全局變量要等瀏覽器生命週期結 束時才銷燬) var resourceCache = {};//這個對象的建立是爲了裝全部圖片的url,有兩個值(1.一個 img對象或2.false) // 做用是爲了判斷是否全部的圖片都加載完成 var readyCallback = [];//這個數組的建立是爲了裝回調函數,而且到必定的時候用 forEach調用回調行數; //將傳入的參數進行分類,若是是數組就用forEach循環將每一個元素做爲參數傳入調用) _load()方法;若是不是數組 //而是單獨的一個圖片路徑就直接傳入調用_load()方法; function load(urlOrArray){ if(urlOrArray instanceof Array){ urlOrArray.forEach(function(url){//forEach(function (item,index,array){}), // 第一個參數表示數組中的每一個元素,第二個表示每一個元素下表,第三個表示數組 自己, // 只傳一個參數表示數組裏的每一個元素; _load(url); }) }else{ _load(urlOrArray); } } function _load(url){ //首先判斷url是否有值,如有就說明resourceCache[url]是一個img對象,直接返回 if(resourceCache[url]){ return resourceCache[url] }else{//若沒有值就先將建立一個Image()對象,將resourceCache[url] 設置爲 false,後面就調用isReady()方法 // (就是判斷每一個URL是不是false,判斷是否圖片全都加載好了,若好了表示 全部的圖片都加載好了,才調用回調函數; var img = new Image(); img.onload = function(){ resourceCache[url] = img; if(isReady()){ readyCallback.forEach(function(func){ func(); }); } } resourceCache[url] = false; img.src = url; } } //判斷是否全部的圖片都加載好了,是就返回true,否就返回false; function isReady(){ var flag = true; for(var i in resourceCache){ if(!resourceCache[i]){ flag = false; } } return flag; } //將回調函數加入readyCallback數組; function onReady(func){ readyCallback.push(func); } //得到單個img對象 function get(url){ return resourceCache[url]; } //創建一命名空間,好實如今某個局部範圍裏的函數在全局(window)裏均可以調用,但 又不失局部函數的特色; window.resources = { load: load, onReady: onReady, get: get } })();
在上面的代碼中提到回調函數這個概念,那麼什麼是回調函數呢?函數
咱們看一個例子就顯而易見了 : 函數是對象,也就意味着函數能夠看成參數傳入另一個函數中。給函數writeCode()
傳入一個
函數參數introduceBugs()
,在某個時刻writeCode()
執行了(或調用了)introduceBugs ()
,在這種狀況下,咱們稱introduceBugs()
是一個「回調函數」,簡稱「回調」(以個人理解就
是把一個函數寫好先放着,當要調用時再把函數做爲參數傳入後調用): 佈局
function writeCode(callback) { // 作點什麼…… callback(); // …… } function introduceBugs() { // …… } writeCode(introduceBugs);
注意introduceBugs()
做爲參數傳入writeCode()
時,函數後面是不帶括號的。括號的意思是
執行函數,而這裏咱們但願傳入一個引用,讓writeCode()
在合適的時機執行它(調用它)。
咱們從一個例子開始,首先介紹無回調的狀況,而後再進行修改。假設你有一張圖片你要在加載好
時,1.改變他距離左邊的距離,2.寫好後你又想給它加一個邊框,3.有些好後又像改變他距離上邊
的距離 以通常的思路咱們會這樣寫:
var img = new Image(); img.onload = function(){ img.style.position = 'absolute'; img.style.left = "100px"; img.style.top = "100px"; img.style.border = "1px solid red"; } img.src = 'images/1.jpg';
這樣寫雖然效果是達到了,但若是我又想修改的話,就只有在源代碼上修改,這樣就會破壞代碼原生
態性;那咱們就加上回調行數後就能夠這樣寫:
var readyCallback = []; var img = new Image(); img.onload = function(){ readyCallback.forEach(function(func){ func(); }) } img.src = 'images/1.jpg'; document.body.appendChild(img); function onReady(func){ readyCallback.push(func); } function move(){ img.style.position = 'absolute'; img.style.left = "100px"; } function move2(){ img.style.top = "100px"; } function border(){ img.style.border = "1px solid red"; } onReady(move); onReady(move2); onReady(border);
這樣用了回調函數後就增長函數的完整性,不用破壞代碼的源生態性,能夠隨便添加或修改刪除;
這個遊戲的回調函數是:
//初始化函數 //要作遊戲,那麼在初始化的時候,最重要的首先是先要確保圖片所有都先加載進來了 //第一個難點:確保全部圖片加載完成以後,再調用初始化函數,這個時候就能夠用到回調模式
function init(){ //畫背景 terrainPattern = ctx.createPattern(resources.get('img/ terrain.png'),'repeat'); main(); }
var terrainPattern;//背景 var isGameOver=false; var singleCount= 0,doubleCount=0;//控制敵機出現的時間間隔;
//建立飛機/敵機對象,與其餘對象都有的屬性就在sprite裏已添加,本身不一樣或獨有的就新加在上面; var player = { pos : [0, 50], sprite : new Sprite('img/sprites.png',[0, 0], [39,39], [0, 1], 16) } var enemies=[];//裝敵機的數組;
var bullets=[];//裝子彈的數組; var explosions=[];//裝子彈碰撞敵機後的爆炸的數組; var lastFire=Date.now();//控制發射子彈的時間間隔; var enemySpeed=100;//敵機向左運動的速度; var playerSpeed=200;//我放飛機的運動速度; var bulletSpeed=500;//子彈的運動速度; var scores=0;//計算分數
在一個遊戲中不少對象都擁有一些相同的屬性和一些動態行爲等,但咱們常常忽略了這一點,只是要
用那個對象時才臨時的建立,這樣既浪費了空間又不是橫方便簡潔,若是咱們事先將這些屬性和一些
動態行爲都結合在一塊兒,封裝成一個新的對象,到要建一個新的對象是把它加進去,若是是這個對象
本身所獨有的或與其與對象不一樣的一些屬性就行加載本身的屬性中;這樣就節約了咱們的時間,應爲
在那個大的精靈對象裏該作的事都處理好了,咱們就只須要傳入一參數就行了;
對於這個遊戲模板來講有這些對象:飛機(我放飛機,敵機,爆炸的敵機)子彈......對它們來講
所共有的屬性有以下:
function Sprite(url, pos, size, frames, speed, dir, once){ this.url = url; //圖片路徑 this.pos = pos; //遊戲對象在圖片上的位置(偏移量) [] this.size = size; //遊戲對象在圖片上的大小 [] this.speed = speed || 0;//遊戲對象自身的運動速度 this.frames = frames; //動畫每次的偏移數組[0,1,2,3,2,1] this.dir = dir || 'horizontal';//遊戲對象的方向默認爲水平 this.once = once || false; this._index = 0; this.done=false;//是否爆炸完成 }
所共有的動態屬性(經過改變背景偏移量來改變圖片,從而達到可以動態效果)有:
Sprite.prototype = { constructor: Sprite,//爲了達到對象的完整性將擁有prototype這個原型的函數有 指向Sprite; update: function(dt){ this._index += this.speed * dt;//這個數會一直增大,它的做用是爲告終合 this.frames數組長度算餘數 //經過餘數來獲取this.frames[idx % max]; }, render: function(ctx){ var frame; if(this.speed > 0){ var max = this.frames.length; var idx = Math.floor(this._index); frame = this.frames[idx % max]; //判斷若是this.done傳入爲true而且idx與max知足關係,就改變this.done 的值-->後面用來判斷爆炸圖片是否刪除 if(this.once && idx>=max){ this.done=true; return; } }else{ frame = 0; } var x = this.pos[0]; var y = this.pos[1]; //經過取於後獲得下標,獲取數組裏的數從而改變背景偏移量 if(this.dir == "vertical"){ y += frame * this.size[1]; }else{ x += frame * this.size[0]; } //改變了背景偏移量後又從新畫圖片 ctx.drawImage(resources.get(this.url), x, y, this.size[0], this.size[1], 0, 0, this.size[0], this.size[1]); } }
這段代碼的實現原理了就是: (咱們以甲蟲圖片爲例)
若是全部的對應圖片的位置在一條水平線上,Y軸不變,第一個圖片的座標假設爲(0,0),它自身
寬爲79,一共有四張圖片,若要在界面改變圖片的樣子,就只改變圖片的背景偏移量而已,它們的背
景偏移量分別是(0,79),(0,179),(0,279),(0,379),這樣很快就能看出規律,咱們生命一個
數組[0,1,2,3,2,1],將數組裏的元素一次單個圖片的寬度,就能夠達到圖片循環變化的樣子; 在這個遊戲中就是運用了這個原理的;但就是得到數組元素的方式有點差異;
對於每一個遊戲來講都要處理玩家的輸入,不一樣的處理方式,效果雖然都同樣,但若是處理的不當那麼可能咱們快速切換按鍵或者是其餘時候會出現Bug,這樣看來輸入的處理方式很重要;
對於這個遊戲咱們的處理方式將所鍵的keyCode傳化成字符串,這樣避免了要記每一個鍵對應的keyCode值,咱們看下面代碼:
var pressKey = {};//這個數組是用來裝輸入或鬆開時的key屬性的值(true,false) //將對應的keyCode處理成對應的字符串; function setKey(e, flag){ var code = e.keyCode; var key; switch (code){ case 37: key = 'LEFT'; break; case 38: key = 'UP'; break; case 39: key = 'RIGHT'; break; case 40: key = 'DOWN'; break; case 32: key = 'SPACE'; break; default: key = String.fromCharCode(code);//除了上面列舉的以外全都轉化爲字 符串; break; } pressKey[key] = flag; }
處理判讀是否按下某個鍵的操做是這樣實現的:當咱們按下時就把 pressKey[key] 設置爲true,
鬆開時是就設置爲false; 具體操做爲:
document.addEventListener('keydown',function(e){ setKey(e,true); },false); document.addEventListener('keyup',function(e){ setKey(e,false); },false); window.addEventListener('blur',function(e){ pressKey = {}; },false);
結合本遊戲咱們的按鍵對應的操做是:
function handleInput(dt){ if(input.isDown('LEFT') || input.isDown('A')){//按的是A鍵或左鍵X軸減 小...... if( player.pos[0]<=0){ player.pos[0]=0; }else{ player.pos[0] -= playerSpeed * dt; } } if(input.isDown('UP') || input.isDown('W')){ if(player.pos[1]<=0){ player.pos[1]=0; }else{ player.pos[1] -= playerSpeed * dt; } } if(input.isDown('RIGHT') || input.isDown('D')){ if( player.pos[0]>canvas.width-39){ player.pos[0]=canvas.width-39; } else{ player.pos[0] += playerSpeed * dt; } } if(input.isDown('DOWN') || input.isDown('S')){ if( player.pos[1]>canvas.height-35){ player.pos[1]=canvas.height-32; }else{ player.pos[1] += playerSpeed * dt; } } if(input.isDown('SPACE') && (Date.now() - lastFire > 100)){//按的是空格鍵鍵 而且時間間隔>100毫秒是添加子彈...... //三種子彈的方向不一樣,但都是子彈能夠用同一個數組,就加了一個dir-->後面判斷方向 後改變不一樣的座標就是了; bullets.push({ pos: [player.pos[0] + player.sprite.size[0]/2,player.pos[1] + player.sprite.size[1]/2], sprite: new Sprite('img/sprites.png',[0, 39],[18,8]), dir: 'forward' });//向水平方向的子彈 bullets.push({ pos: [player.pos[0] + player.sprite.size[0]/2,player.pos[1] + player.sprite.size[1]/2], sprite: new Sprite('img/sprites.png',[0, 50], [9, 5]), dir: 'upward' });//向上方的子彈 bullets.push({ pos: [player.pos[0] + player.sprite.size[0]/2,player.pos[1] + player.sprite.size[1]/2], sprite: new Sprite('img/sprites.png',[0, 60], [9, 5]), dir: 'downward' });//向下向的子彈 lastFire = Date.now(); } }
遊戲中對象的位置呀,行爲呀時刻都在變化中,那咱們就習慣把它們都寫在updat函數中: 在這個遊戲中咱們變化的有子彈,敵機,我方飛機,爆炸,他們分爲一個自身某個部位的運動,另外
還有他們相對於座標的一個移動,自身的移動咱們把它看作全部對象的共有行爲。把它封裝在
sprite精靈中,因此咱們還要結合sprite對象使用,敵機、子彈爆炸都不是一個,因此還結合了循環一塊兒完成;
1.對於敵機的移動咱們這樣寫的:
singleCount++; if(singleCount % 2==0){ doubleCount++; if(doubleCount % 5==0){ //前面的判斷就是手動的給敵機的添加設置阻礙,讓先後有必定的時間間隔; enemies.push({ //全部的敵機外置都是在畫布的右邊出現,那X軸就是畫布的寬,Y軸就是(0, canvas.height-敵機自己的高); //一個數的變化範圍若是在(0,自己),那麼就把這個數*(0,1); pos:[canvas.width,(canvas.height-39)*Math.random()],// Math.random()的範圍[0.1),0<=Math.random() <1; sprite:new Sprite('img/sprites.png',[0,79],[80,39], [0,1,2,3,2,1],6) }); doubleCount=0; } }
因爲咱們用的時間函數時間都肯定了, 那咱們就認爲的加了一些條件開控制時間;
2.對與敵機的我只改變:
for(var i=0;i<enemies.length;i++){ enemies[i].pos[0]-=enemySpeed * dt; enemies[i].sprite.update(dt); if(enemies[i].pos[0]+enemies[i].sprite.size[0]<0){ enemies.splice(i,1); i--; } }
3.對於子彈的運動:
//處理每一個子彈的運動而且判斷是否出界,若出界就刪除當前的值,數組長度相應減小一個; for(var i=0;i<bullets.length;i++){ var bullet = bullets[i]; //根具子彈的dir判斷它應向哪一個方向運動,對應的改變相應的座標; switch(bullet.dir) { case 'upward': bullet.pos[1] -= bulletSpeed * dt; break; case 'downward': bullet.pos[1] += bulletSpeed * dt; break; default://forward bullet.pos[0] += bulletSpeed * dt; } bullet.sprite.update(dt); if(bullet.sprite.size[0] + bullet.pos[0] > canvas.width){ bullets.splice(i,1); i --; } }
4.對於爆炸的運動改變:
//處理每一個子彈碰撞敵機後的爆炸圖片自身的一個動態變化,若變化完以後就刪除當前的值, 數組長度相應減小一個; for(var i=0;i<explosions.length;i++){ explosions[i].sprite.update(dt); if(explosions[i].done){ explosions.splice(i,1); i --; } }
對於碰撞事件的判斷咱們通常用的是矩形判斷。所謂的矩形判斷就是把兩個相判斷的對象裝在一個矩形裏,用四角的座標進行比較;具體咱們來看一個例子就顯而易見了: 假設有兩個矩形,一個在左,一個在右邊: var lastTime = Date.now(); var box1Speed = 100; var box2Speed = 200;
var box1 = document.getElementById('box1'); var box2 = document.getElementById('box2'); function main(){ var now = Date.now(); var dt = (now - lastTime) / 1000; var rectA = getDimensions(box1); var rectB = getDimensions(box2); var flag = collides(rectA,rectB); if(flag){ alert("撞上了!"); return; }else{ update(dt); } lastTime = now; setTimeout(main,1000/60); } function update(dt){ box1.style.left = parseInt(box1.style.left) + box1Speed * dt + "px"; box2.style.left = parseInt(box2.style.left) - box2Speed * dt + "px"; } function getDimensions(element){ return { x: element.offsetLeft, y: element.offsetTop, width: element.offsetWidth, height: element.offsetHeight }; } function collides(rectA,rectB){ return !(rectA.x + rectA.width < rectB.x || rectB.x + rectB.width < rectA.x || rectA.y + rectA.height < rectB.y || rectB.y + rectB.height < rectA.y) } main();
因而可知這個方法可使用在形狀比較太勻稱的圖片上,在這個遊戲中咱們也是這樣處理的: 先封裝判斷碰撞的方法:
function collides(x, y, r, b, x2, y2, r2, b2){ return !(r < x2 || r2 < x || b < y2 || b2< y); } function boxCollides(pos,size,pos2,size2){//傳入的是兩個對象的座標位置和大小; return collides(pos[0], pos[1], pos[0] + size[0], pos[1] + size[1], pos2[0], pos2[1], pos2[0] + size2[0], pos2[1] + size2[1]); }
封裝好了再結合循環使用:
function checkCollisions(){ for(var i=0;i<enemies.length;i++){ var pos = enemies[i].pos; var size = enemies[i].sprite.size; for(var j=0;j<bullets.length;j++){ var pos2 = bullets[j].pos; var size2 = bullets[j].sprite.size; //我方飛機的子彈與敵機相碰撞,而且相撞的敵機立刻刪除,數組長度減小一個 if(boxCollides(pos,size,pos2,size2)){ enemies.splice(i,1); i--; scores+=10; //我方飛機的子彈與敵機相碰撞後添加爆炸 explosions.push({ pos: [pos[0], pos[1]], sprite: new Sprite('img/sprites.png', [0, 117], [39,39], [0,1,2,3,4,5,6,7,8,9,10,11,12], 16, null, true) }); bullets.splice(j,1); j--; } } //我方飛機與敵機相碰撞 if(boxCollides(pos,size,player.pos,player.sprite.size)){ gameOver(); } } scoreDiv.innerHTML=scores; }
界面上的全部對象都在變化,因此要一直重複的畫界面,咱們把每一個對象的畫裝在sprite裏,就是
對象本身畫本身:
//重畫界面 function render(){ if(!isGameOver){ ctx.clearRect(0,0,canvas.width,canvas.height) //畫背景 ctx.fillStyle = terrainPattern; ctx.fillRect(0, 0, canvas.width, canvas.height); renderEntity(player); renderEntities(enemies) renderEntities(bullets); renderEntities(explosions); }else{ enemies=[]; bullets=[]; explosions=[]; bullets=[]; return false; } } function renderEntities(entities){ for(var i=0;i<entities.length;i++){ renderEntity(entities[i]) } } function renderEntity(entity){ ctx.save(); ctx.translate(entity.pos[0], entity.pos[1]); entity.sprite.render(ctx); ctx.restore(); }
遊戲的主循環用來控制遊戲流程。首先咱們要得到當前的時間,這樣咱們才能計算時間差(自上次循
環以來通過的時間)。而後計算modifier的值並交給update(須要將delta除以1000以將其轉換爲
毫秒)。最後調用render並更新記錄的時間。
遊戲主循環是遊戲中最重要的概念,不管遊戲怎麼變化,無非就是移動,消失。而移動消失,無非又
是畫布的重畫,因此把移動或者消失的位置放在update函數裏面,把畫布重畫放在render函數裏
面。而隨着時間的變化,無非就是這兩個函數函數一直在執行而已。
//遊戲主循環 //dt的做用組要是爲了經過得到一個靈活的速度(根據每臺電腦的當前的計算速度()不一樣來得到 一個靈活的速度) var lastTime = Date.now(); function main(){ var now = Date.now(); var dt = (now - lastTime) / 1000; update(dt);//改變對象的位置 render();//重畫界面 lastTime = now; requestAnimaFrame(main); }
在遊戲中咱們若是不把隨着時間變化的對象都寫在一個時間函數裏的話,那麼就要用不少歌時間函數
(setInterval),這樣既浪費資源,代碼又不簡潔,若是隻用一個時間函數的話那咱們就用
requestAnimationFrame,那咱們相比較(setInterval)以爲requestAnimationFrame要好些; 咱們來看一個經典的動畫函數:
function animate(element, name, from, to, time) { time = time || 800; //默認0.8秒 var style = element.style, latency = 60, // 每60ms一次變化 count = time / latency, //變化的次數 step = Math.round((to - from) / count), //每一步的變化量 now = from; function go() { count--; now = count ? now + step : to; style[name] = now + 'px'; if (count) { setTimeout(go, latency); } } style[name] = from + 'px'; setTimeout(go, latency); }
姑且不論這個函數的設計存在侷限性,如只能對以px爲單位的樣式進行修改。僅從函數的實現上看,
這能夠是一個很是經典的動畫理念,其基本邏輯由如下部分組成:
獲取起點值from和終點值to,經過動畫須要進行的時間time,以及每偵間隔latency的要求,計算
出值的改變次數count和每次改變的量step。 開啓setTimeout(fn, latency);來步進到下一偵。 在下一偵中,設置屬性步進一次,若是動畫還沒結束,再回到第2步繼續下一偵。 這個函數工做得很好,服務了千千萬萬的站點和系統,事實上jQuery的animate函數的核心也無非
是setInterval函數。
可是,隨着如今系統複雜度的穩步上升,動畫效果也愈來愈多,同時對動畫的流暢度也有了更多的重視,這致使上面的函數會出現一些問題。例如同時打開100個動畫效果,根據上面的函數,很明顯會有100個定時器在同時運行,這些定時器之間的調度會對性能有輕微的影響。雖然在正常的環境中,這些許的影響並不會有什麼關係,可是在動畫這種對流暢度有很高要求的環境下,任何細微的影響均可能產生出很差的用戶體驗。在這樣的狀況下,有一些開發者就發明了一種基於統一幀管理的動畫框架,他使用一個定時器觸發動
畫幀,不一樣的動畫來註冊這些幀,在每一幀上處理多個動畫的屬性變化。這樣的好處是減小了定時器調度的開銷,可是對於動畫框架的開發者來講,統一幀管理、提供監聽幀的API等,都是須要開發和維護的。瀏覽器的直接支持 最終,瀏覽器廠商們發現這件事其實能夠由他們來作,而且基於瀏覽器層面,還能夠有更多的優化,
好比:
對於一個偵中對DOM的全部操做,只進行一次Layout和Paint。 若是發生動畫的元素被隱藏了,那麼就再也不去Paint。 因而,瀏覽器開始推出一個API,叫作requestAnimationFrame,關於這個函數,MDC的相關頁面
有比較詳細的介紹,簡單來講,這個函數有2種使用方法:
調用requestAnimationFrame函數,傳遞一個callback參數,則在下一個動畫幀時,會調用
callback。 不傳遞參數地直接調用該函數,啓動動畫幀,下一個幀觸發時,會同時觸發
window.onmozbeforepaint事件,能夠經過註冊該事件來進行動畫。 第2種方法因爲依賴於Firefox本身的事件,且beforepaint事件還沒進入到標準中,因此不推薦使
用,仍是使用第1種方式比較好。此時,咱們的動畫邏輯能夠變成這樣:
記錄當前時間startTime,做爲動畫開始的時間。 請求下一幀,帶上回調函數。 下一幀觸發時,回調函數的第一個參數爲當前的時間,再與startTime進行比較,肯定時間間隔
ellapseTime。 判斷ellapseTime是否已經超過事先設定的動畫時間time,若是超過,則結束動畫。 計算動畫屬性變化的差值differ = to - from,再肯定在ellapseTime的時候應該變化多少step
= differ / time * ellapseTime。 計算出如今應該變化到的位置Math.round(from + step),並從新對樣式賦值。 繼續請求下一幀。
在遊戲的主循環中咱們用了一個新的時間函數 requestAnimaFrame(main):
//跨瀏覽器的RequestAnimationFrame函數(動畫接口) var requestAnimaFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame ||//Google Chromeo window.mozRequestAnimationFrame ||//Mozilla Firefox window.msRequestAnimationFrame ||//IE window.oRequestAnimationFrame || function(callback){ window.setTimeout(callback, 1000 / 60); } })();
requestAnimationFrame是什麼? 之前咱們作動畫須要一個定時器,每間隔多少毫秒就作出一些改變。如今有個好消息:瀏覽器廠商已 經決定提供一個專門作動畫的方法,即requestAnimationFrame(),並且基於瀏覽器的層面也能更
好的進行優化。可是呢,這只是一個作動畫的基礎API,即不基於DOM元素的style變化,也不基於
canvas,或者WebGL。因此,具體的動畫細節須要咱們本身寫。
在上面咱們用到一個方法巧妙的得到一個比較靈活的速度速度,咱們給定一個每秒的速度,而後得到
時間間隔,根據每一個機子當前的計算速度來決定那個對象的速度是快是慢;咱們看下面例子: 假設有兩個矩形,一個在左,一個在右邊:
var lastTime = Date.now(); var box1Speed = 100; var box2Speed = 200; var box1 = document.getElementById('box1'); var box2 = document.getElementById('box2'); function main(){ var now = Date.now(); var dt = (now - lastTime) / 1000; update(dt); lastTime = now; setTimeout(main,1000/60); } function update(dt){ box1.style.left = parseInt(box1.style.left) + box1Speed * dt + "px"; box2.style.left = parseInt(box2.style.left) - box2Speed * dt + "px"; } main();
經過上面的終結,咱們寫通常的小遊戲就有了必定的思路,不至於想到啥就寫啥,毫無頭緒了!