舉例闡述制作HTML5手機游戲的7個步驟
想用HTML5制作跨平臺的手機游戲?不必理會Java或Objective-C?也不用管應用商店?聽起來真不可思議!
現(xiàn)在有許多游戲開發(fā)者都在挖掘手機HTML手機游戲的潛能。如《Nutmeg》和《Lunch Bug》就是優(yōu)秀的案例。HTML5游戲的優(yōu)勢在于,使用相同的代碼就能讓游戲在手機和電腦上運行得一樣好。這是否意味著HTML5能夠讓游戲代碼編寫成為一件一勞永逸的事?
準備
在你開始編寫你自己的“神廟逃亡”或“憤怒的小鳥”以前,以下幾點可能會澆滅你的創(chuàng)作熱情:
表現(xiàn):
一般說來,手機瀏覽器的JavaScript引擎表現(xiàn)并不出眾。盡管從iOS 6和Chrome的Android測試版的情況上看,它進步得很快。
分辨率:
Android設備的分辨率已經(jīng)五花八門了,更別說iPhone 4和iPad 3的分辨率和像素密度也在不斷提高。
聲音:
但愿你不介意沒有聲音的游戲——手機瀏覽器的聲音支持很差。延遲是主要問題,大部分設備只支持一種聲道。iOS甚至要等到用戶主動開啟才會加載聲音。
現(xiàn)在,作為網(wǎng)頁開發(fā)者的你已經(jīng)習慣于處理瀏覽器的這些缺陷。所以,一些技術問題是難不倒你的,對吧?另外,這些表現(xiàn)和聲音問題都是暫時的。畢竟手機瀏覽器進步飛快,這些問題很快就會變成歷史。
在本教程中,你將通過制作一款比較簡單的游戲來了解這些基本問題以及解決辦法。
iphone(from smashingmagazine)
這是一款相當簡單的游戲:玩家要做的就是點擊從屏幕底部浮起來的白色圓形,不要讓它們通過。你可以把這些白色圓形想象成漂浮上升的泡泡,你要在它們飛上天以前刺破它們。所以,我把這款小游戲叫作《POP》。
我們的制作過程可以分成如下7個步驟:
1、設置視圖,以適合大多數(shù)手機屏幕的尺寸;
2、使用canvas API在屏幕上繪制圖形;
3、捕捉觸擊事件;
4、制作基本的游戲循環(huán);
5、引入游戲“實體”;
6、添加碰撞檢測和一些簡單的數(shù)學計算;
7、修飾外觀,即添加少量特效。
1、設置視圖
背景故事就隨便了。
正如前面提到的,不同的設備具有不同的分辨率和像素密度。這意味著我們必須調(diào)整畫布以適應不同的視圖。這就可能損失游戲的外觀質(zhì)量,但我們有一個小技巧,也就是先使用小畫布,然后按比例放大,這么做后的畫面效果就好多了。
我們先寫一段基本的HTML代碼:
- <!DOCTYPE HTML>
 - <html lang=”en”>
 - <head>
 - <meta charset=”UTF-8″>
 - <meta name=”viewport” content=”width=device-width,
 - user-scalable=no, initial-scale=1, maximum-scale=1, user-scalable=0″ />
 - <meta name=”apple-mobile-web-app-capable” content=”yes” />
 - <meta name=”apple-mobile-web-app-status-bar-style” content=”black-translucent” />
 - <style type=”text/css”>
 - body { margin: 0; padding: 0; background: #000;}
 - canvas { display: block; margin: 0 auto; background: #fff; }
 - </style>
 - </head>
 - <body>
 - <canvas> </canvas>
 - <script>
 - // all the code goes here
 - </script>
 - </body>
 - </html>
 
這個meta視圖標簽的作用是,命令瀏覽器禁止用戶縮放畫面,以及按完全尺寸渲染,而不是收縮頁面。隨后的帶apple-前綴的meta標簽允許游戲被加入書簽。在iPhone上,加入書簽的應用不會顯示頁面底部的在工具條上,因此節(jié)省了大片空間。
看看以下代碼:
- // namespace our game
 - var POP = {
 - // set up some initial values
 - WIDTH: 320,
 - HEIGHT: 480,
 - // we’ll set the rest of these
 - // in the init function
 - RATIO: null,
 - currentWidth: null,
 - currentHeight: null,
 - canvas: null,
 - ctx: null,
 - init: function() {
 - // the proportion of width to height
 - POPPOP.RATIO = POP.WIDTH / POP.HEIGHT;
 - // these will change when the screen is resized
 - POPPOP.currentWidth = POP.WIDTH;
 - POPPOP.currentHeight = POP.HEIGHT;
 - // this is our canvas element
 - POP.canvas = document.getElementsByTagName(‘canvas’)[0];
 - // setting this is important
 - // otherwise the browser will
 - // default to 320 x 200
 - POPPOP.canvas.width = POP.WIDTH;
 - POPPOP.canvas.height = POP.HEIGHT;
 - // the canvas context enables us to
 - // interact with the canvas api
 - POPPOP.ctx = POP.canvas.getContext(’2d’);
 - // we’re ready to resize
 - POP.resize();
 - },
 - resize: function() {
 - POP.currentHeight = window.innerHeight;
 - // resize the width in proportion
 - // to the new height
 - POPPOP.currentWidth = POP.currentHeight * POP.RATIO;
 - // this will create some extra space on the
 - // page, allowing us to scroll past
 - // the address bar, thus hiding it.
 - if (POP.android || POP.ios) {
 - document.body.style.height = (window.innerHeight + 50) + ‘px’;
 - }
 - // set the new canvas style width and height
 - // note: our canvas is still 320 x 480, but
 - // we’re essentially scaling it with CSS
 - POPPOP.canvas.style.width = POP.currentWidth + ‘px’;
 - POPPOP.canvas.style.height = POP.currentHeight + ‘px’;
 - // we use a timeout here because some mobile
 - // browsers don’t fire if there is not
 - // a short delay
 - window.setTimeout(function() {
 - window.scrollTo(0,1);
 - }, 1);
 - }
 - };
 - window.addEventListener(‘load’, POP.init, false);
 - window.addEventListener(‘resize’, POP.resize, false);
 
首先,我們要給游戲創(chuàng)建一個命名空間“POP”。優(yōu)秀的開發(fā)者不會污染整個命名空間的。比較好的做法是在程序的開頭部分就聲明所有變量。大部分變量 是很容易理解的:canvas表示HTML中的canvas元素;ctx使我們可以通過JavaScript canvas API來訪問它。
在POP.init,我們獲得canvas元素的引用,調(diào)整canvas元素的尺寸為480 × 320。resize函數(shù)會在調(diào)整大小和加載事件時啟用,從而按比例調(diào)整canvas的style屬性(游戲邦注:高度和寬度)。實際上,canvas仍 然是相同的尺寸,只是已經(jīng)通過CSS放大了。試一試調(diào)整你的瀏覽器大小,看看canvas的縮放效果。
如果你在你的手機上實驗,你會發(fā)現(xiàn)地址欄仍然可見。解決這個問題的做法是:對文件添加額外像素,然后向下滾動以隱藏地址欄,如下:
- // we need to sniff out Android and iOS
 - // so that we can hide the address bar in
 - // our resize function
 - POP.ua = navigator.userAgent.toLowerCase();
 - POPPOP.android = POP.ua.indexOf(‘android’) > -1 ? true : false;
 - POP.ios = ( POP.ua.indexOf(‘iphone’) > -1 || POP.ua.indexOf(‘ipad’) > -1 ) ?
 - true : false;
 
以上代碼搜索userAgent,如果存在則標記。在調(diào)用POP.resize()以前,把它添加到POP.init后面。
然后,在resize函數(shù)中,如果android或ios為true,我們就把文件的高度再增加50像素——這就足以隱藏地址欄了。
- // this will create some extra space on the
 - // page, enabling us to scroll past
 - // the address bar, thus hiding it.
 - if (POP.android || POP.ios) {
 - document.body.style.height = (window.innerHeight + 50) + ‘px’;
 - }
 
注意,我們的做法只適用于Android和iOS設備;否則,討厭的滾動條就會出現(xiàn)。另外,我們必須延遲scrollTo,以確保Safari設備不會忽略它。
2、在畫布上繪制
我們已經(jīng)根據(jù)視圖調(diào)整好畫布了,接下來我們該在上面畫點什么了。
注:在本教程中,我們只使用基本的幾何形狀。iOS 5和Chrome的Android測試版可以用很高的幀率處理大量子畫面。在Android 3.2或以下版本中實驗一下,你會發(fā)現(xiàn)前者的幀率確實大大提高了。幸運地是,繪制圓形并不需要占用太多內(nèi)存,所以我們的游戲中可以大量使用圓形,即使是在 老設備上,表現(xiàn)也不會太差。
以下,我們已經(jīng)添加了一個基本的Draw對象,使我們可以清除屏幕,繪制矩形和圓形,然后添加文本。
- // abstracts various canvas operations into
 - // standalone functions
 - POP.Draw = {
 - clear: function() {
 - POP.ctx.clearRect(0, 0, POP.WIDTH, POP.HEIGHT);
 - },
 - rect: function(x, y, w, h, col) {
 - POP.ctx.fillStyle = col;
 - POP.ctx.fillRect(x, y, w, h);
 - },
 - circle: function(x, y, r, col) {
 - POP.ctx.fillStyle = col;
 - POP.ctx.beginPath();
 - POP.ctx.arc(x + 5, y + 5, r, 0, Math.PI * 2, true);
 - POP.ctx.closePath();
 - POP.ctx.fill();
 - },
 - text: function(string, x, y, size, col) {
 - POP.ctx.font = ‘bold ‘+size+’px Monospace’;
 - POP.ctx.fillStyle = col;
 - POP.ctx.fillText(string, x, y);
 - }
 - };
 
我們的Draw對象有清除屏幕和繪制矩形、圓形及文本的方法。抽象這些操作的好處是,我們不必記憶確切的canvas API調(diào)用,而且繪制圓形的代碼簡單到只有一句。
代碼如下:
- // include this at the end of POP.init function
 - POP.Draw.clear();
 - POP.Draw.rect(120,120,150,150, ‘green’);
 - POP.Draw.circle(100, 100, 50, ‘rgba(255,0,0,0.5)’);
 - POP.Draw.text(‘Hello World’, 100, 100, 10, ‘#000′);
 
把上述代碼放在POP.init函數(shù)之后。你應該可以看到畫布上繪制出許多圖形。
3、觸擊事件
與click事件一樣,手機瀏覽器有捕捉觸擊事件的方法。
以下代碼的重點是touchstart、touchmove和touchend事件。對于標準的click事件,我們可以從e.pageX的 e.pageY中獲得座標。觸擊事件則稍有不同,它們有一個touches集合,其中的各個元素都包含觸擊座標和其他數(shù)據(jù)。我們只想要第一次觸擊,所以我 們要設置一個e.touches[0]。
注:只有版本4以后, Android才支持訪問多次觸擊動作的JavaScript。
當禁用滾動、縮放和其他會中斷游戲的活動時,我們還要調(diào)用e.preventDefault(); 。
添加以下代碼到POP.init函數(shù):
- // listen for clicks
 - window.addEventListener(‘click’, function(e) {
 - e.preventDefault();
 - POP.Input.set(e);
 - }, false);
 - // listen for touches
 - window.addEventListener(‘touchstart’, function(e) {
 - e.preventDefault();
 - // the event object has an array
 - // named touches; we just want
 - // the first touch
 - POP.Input.set(e.touches[0]);
 - }, false);
 - window.addEventListener(‘touchmove’, function(e) {
 - // we’re not interested in this,
 - // but prevent default behaviour
 - // so the screen doesn’t scroll
 - // or zoom
 - e.preventDefault();
 - }, false);
 - window.addEventListener(‘touchend’, function(e) {
 - // as above
 - e.preventDefault();
 - }, false);
 - 你可能已注意到,以上代碼把事件數(shù)據(jù)傳輸給Input對象。但我們現(xiàn)在要先定義一下它:
 - // + add this at the bottom of your code,
 - // before the window.addEventListeners
 - POP.Input = {
 - x: 0,
 - y: 0,
 - tapped :false,
 - set: function(data) {
 - this.x = data.pageX;
 - this.y = data.pageY;
 - this.tapped = true;
 - POP.Draw.circle(this.x, this.y, 10, ‘red’);
 - }
 - };
 
現(xiàn)在,測試一下。圓形沒有出現(xiàn)。這是為什么?有了!因為我們已經(jīng)縮放畫布了,當映射觸擊到屏幕的位置時,我們必須考慮到這一點。
首先,我們必須從座標中扣除偏移值。
- var offsetTop = POP.canvas.offsetTop,
 - offsetLeft = POP.canvas.offsetLeft;
 - this.x = data.pageX – offsetLeft;
 - this.y = data.pageY – offsetTop;
 
offset_diagram(from smashingmagazine)
然后,考慮到畫布已經(jīng)縮放過了,我們得計算一下實際畫布(游戲邦注:仍然是320 × 480)。
- var offsetTop = POP.canvas.offsetTop,
 - offsetLeft = POP.canvas.offsetLeft;
 - scale = POP.currentWidth / POP.WIDTH;
 - this.x = ( data.pageX – offsetLeft ) / scale;
 - this.y = ( data.pageY – offsetTop ) / scale;
 
scaled_canvas_diagram(from smashingmagazine)
你開始覺得頭疼了吧?那我就給你舉個例子。想象一下玩家輕擊500 × 750的畫布上的座標400,400。我們必須調(diào)整這個座標,因為畫布的實際尺寸是480 × 320。所以,真正的X座標是400除以比例,即400 ÷ 1.56 = 320.5。
我們當然不是在每一個觸擊事件發(fā)生時計算,而是在調(diào)整完畫布尺寸后計算座標。在程序的開頭部分添加如下代碼,以及其他變量聲明:
- // let’s keep track of scale
 - // along with all initial declarations
 - // at the start of the program
 - scale: 1,
 - // the position of the canvas
 - // in relation to the screen
 - offset = {top: 0, left: 0},
 
在我們的調(diào)整大小函數(shù)中,調(diào)整畫布的寬高后,我們要記錄一下當前的尺寸和偏移量:
- // add this to the resize function.
 - POPPOP.scale = POP.currentWidth / POP.WIDTH;
 - POPPOP.offset.top = POP.canvas.offsetTop;
 - POPPOP.offset.left = POP.canvas.offsetLeft;
 
現(xiàn)在,我們可以在POP.Input類的set方法中使用它們了:
- this.x = (data.pageX – POP.offset.left) / POP.scale;
 - this.y = (data.pageY – POP.offset.top) / POP.scale;
 
別走開,下頁內(nèi)容更精彩
#p#
4、循環(huán)
典型的游戲循環(huán)如下:
1、用戶輸入,
2、更新和處理碰撞,
3、在屏幕上渲染,
4、重復。
我們當然可以使用setInterval,但在requestAnimationFrame中有一個新玩意兒。它能保證動畫更流暢,并且能節(jié)省電池量。不幸地是,并非所有瀏覽器都支持它。但Paul Irish已經(jīng)想到一個方便的解決辦法。
我們也可以借鑒他的辦法,代碼如下:
- // http://paulirish.com/2011/requestanimationframe-for-smart-animating
 - // shim layer with setTimeout fallback
 - window.requestAnimFrame = (function(){
 - return window.requestAnimationFrame ||
 - window.webkitRequestAnimationFrame ||
 - window.mozRequestAnimationFrame ||
 - window.oRequestAnimationFrame ||
 - window.msRequestAnimationFrame ||
 - function( callback ){
 - window.setTimeout(callback, 1000 / 60);
 - };
 - })();
 
接著我們來制作初步的游戲循環(huán):
- // Add this at the end of POP.init;
 - // it will then repeat continuously
 - POP.loop();
 - // Add the following functions after POP.init:
 - // this is where all entities will be moved
 - // and checked for collisions, etc.
 - update: function() {
 - },
 - // this is where we draw all the entities
 - render: function() {
 - POP.Draw.clear();
 - },
 - // the actual loop
 - // requests animation frame,
 - // then proceeds to update
 - // and render
 - loop: function() {
 - requestAnimFrame( POP.loop );
 - POP.update();
 - POP.render();
 - }
 
我們在POP.init之后調(diào)用這個循環(huán)。POP.loop接著調(diào)用我們的POP.update和POP.render方法。 requestAnimFrame保證這個循環(huán)被再次調(diào)用,最好是以60幀每秒的速度。注意,我們不必擔心查看循環(huán)中的輸入,因為我們已經(jīng)在注意通過 POP.Input類可以訪問到的觸擊和點擊事件。
現(xiàn)在的問題是,我們的最后一次觸擊在屏幕上消失得太快了。我們必須想辦法讓屏幕更好地記憶和顯示觸擊位置。
5、觸擊
首先,我們添加一個實體集合。這個集合包含游戲中出現(xiàn)的所有觸擊點、泡泡、粒子和其他動態(tài)物品。
// put this at start of program
entities: [],
我們來做一個Touch類,它將在接觸點處繪制一個圓點,這個圓點之后會慢慢消褪。
- POP.Touch = function(x, y) {
 - this.type = ‘touch’; // we’ll need this later
 - this.x = x; // the x coordinate
 - this.y = y; // the y coordinate
 - this.r = 5; // the radius
 - this.opacity = 1; // initial opacity; the dot will fade out
 - this.fade = 0.05; // amount by which to fade on each game tick
 - this.remove = false; // flag for removing this entity. POP.update
 - // will take care of this
 - this.update = function() {
 - // reduce the opacity accordingly
 - this.opacity -= this.fade;
 - // if opacity if 0 or less, flag for removal
 - this.remove = (this.opacity < 0) ? true : false;
 - };
 - this.render = function() {
 - POP.Draw.circle(this.x, this.y, this.r, ‘rgba(255,0,0,’+this.opacity+’)');
 - };
 - };
 
Touch類具有一系列屬性。x和y座標是參數(shù),半徑this.r為5像素,初始不透明度為1,觸擊點消裉速率為0.05,remove標記告訴主游戲循環(huán)是否將觸擊圓點從該實體集合中移除。
關鍵是,這個類有兩個主要方法:update和render。我們將從游戲循環(huán)的相應部分調(diào)用它們。
我們先在游戲循環(huán)中刷出一個新的Touch實例,再通過update方法移除:
- // POP.update function
 - update: function() {
 - var i;
 - // spawn a new instance of Touch
 - // if the user has tapped the screen
 - if (POP.Input.tapped) {
 - POP.entities.push(new POP.Touch(POP.Input.x, POP.Input.y));
 - // set tapped back to false
 - // to avoid spawning a new touch
 - // in the next cycle
 - POP.Input.tapped = false;
 - }
 - // cycle through all entities and update as necessary
 - for (i = 0; i < POP.entities.length; i += 1) {
 - POP.entities[i].update();
 - // delete from array if remove property
 - // flag is set to true
 - if (POP.entities[i].remove) {
 - POP.entities.splice(i, 1);
 - }
 - }
 - },
 
基本上,如果POP.Input.tapped是true,那么我們就添加一個新的POP.Touch實例到我們的實體集合。循環(huán)這個實體集合,即調(diào)用各個實體的update方法。最后,如果實體被標記為移除,它就會從該集合中刪除。
接著,我們在POP.render函數(shù)中渲染它們。
- // POP.render function
 - render: function() {
 - var i;
 - POP.Draw.rect(0, 0, POP.WIDTH, POP.HEIGHT, ‘#036′);
 - // cycle through all entities and render to canvas
 - for (i = 0; i < POP.entities.length; i += 1) {
 - POP.entities[i].render();
 - }
 - },
 
類似于update函數(shù),循環(huán)實體和調(diào)用它們的render方法,在屏幕上繪制它們。
到目前為止,一切進展順利?,F(xiàn)在我們要添加Bubble類,它的作用是生產(chǎn)漂浮上升的泡泡。
- POP.Bubble = function() {
 - this.type = ‘bubble’;
 - this.x = 100;
 - this.r = 5; // the radius of the bubble
 - this.y = POP.HEIGHT + 100; // make sure it starts off screen
 - this.remove = false;
 - this.update = function() {
 - // move up the screen by 1 pixel
 - this.y -= 1;
 - // if off screen, flag for removal
 - if (this.y < -10) {
 - this.remove = true;
 - }
 - };
 - this.render = function() {
 - POP.Draw.circle(this.x, this.y, this.r, ‘rgba(255,255,255,1)’);
 - };
 - };
 
POP.Bubble類非常接近于Touch類,主要的區(qū)別是它并不是像觸擊點一樣消褪,而是向上移動。這個活動是通過在update函數(shù)中改變y位置即 this.y實現(xiàn)的。這里,我們也要查看泡泡是否離開屏幕;如果是,我們就要把它標記為移除。
注:我們已經(jīng)制作了基本Entity類,Touch和Bubble都包含在內(nèi)。但是,我現(xiàn)在不想比較JavaScript原型的繼承和類。
- // Add at the start of the program
 - // the amount of game ticks until
 - // we spawn a bubble
 - nextBubble: 100,
 - // at the start of POP.update
 - // decrease our nextBubble counter
 - POP.nextBubble -= 1;
 - // if the counter is less than zero
 - if (POP.nextBubble < 0) {
 - // put a new instance of bubble into our entities array
 - POP.entities.push(new POP.Bubble());
 - // reset the counter with a random value
 - POP.nextBubble = ( Math.random() * 100 ) + 100;
 - }
 
以上,我們已經(jīng)為游戲循環(huán)添加了隨機計時器。游戲循環(huán)會在隨機位置刷出Bubble實例。在游戲開始時,我們設置nextBubble(下一個泡泡)的出現(xiàn)間隔為100,即當100減少到0時,游戲就會刷出新泡泡,并重置nextBubble計數(shù)器。
6、整合
首先,我們還沒使用到任何碰撞檢測。我們可以用簡單地函數(shù)實現(xiàn)它。
- // this function checks if two circles overlap
 - POP.collides = function(a, b) {
 - var distance_squared = ( ((a.x – b.x) * (a.x – b.x)) +
 - ((a.y – b.y) * (a.y – b.y)));
 - var radii_squared = (a.r + b.r) * (a.r + b.r);
 - if (distance_squared < radii_squared) {
 - return true;
 - } else {
 - return false;
 - }
 - };
 - // at the start of POP.update, we set a flag for checking collisions
 - var i,
 - checkCollision = false; // we only need to check for a collision
 - // if the user tapped on this game tick
 - // and then incorporate into the main logic
 - if (POP.Input.tapped) {
 - POP.entities.push(new POP.Touch(POP.Input.x, POP.Input.y));
 - // set tapped back to false
 - // to avoid spawning a new touch
 - // in the next cycle
 - POP.Input.tapped = false;
 - checkCollision = true;
 - }
 - // cycle through all entities and update as necessary
 - for (i = 0; i < POP.entities.length; i += 1) {
 - POP.entities[i].update();
 - if (POP.entities[i].type === ‘bubble’ && checkCollision) {
 - hit = POP.collides(POP.entities[i],
 - {x: POP.Input.x, y: POP.Input.y, r: 7});
 - POP.entities[i].remove = hit;
 - }
 - // delete from array if remove property
 - // is set to true
 - if (POP.entities[i].remove) {
 - POP.entities.splice(i, 1);
 - }
 - }
 
現(xiàn)在的泡泡比較無趣,移動速度和軌跡都一樣。我們可以通過下面這段簡單的代碼來隨機化泡泡的運動:
- POP.Bubble = function() {
 - this.type = ‘bubble’;
 - this.r = (Math.random() * 20) + 10;
 - this.speed = (Math.random() * 3) + 1;
 - this.x = (Math.random() * (POP.WIDTH) – this.r);
 - this.y = POP.HEIGHT + (Math.random() * 100) + 100;
 - this.remove = false;
 - this.update = function() {
 - this.y -= this.speed;
 - // the rest of the class is unchanged
 
我們要讓泡泡左右擺,使玩家更難觸擊到它們:
- // the amount by which the bubble
 - // will move from side to side
 - this.waveSize = 5 + this.r;
 - // we need to remember the original
 - // x position for our sine wave calculation
 - thisthis.xConstant = this.x;
 - this.remove = false;
 - this.update = function() {
 - // a sine wave is commonly a function of time
 - var time = new Date().getTime() * 0.002;
 - this.y -= this.speed;
 - // the x coordinate to follow a sine wave
 - thisthis.x = this.waveSize * Math.sin(time) + this.xConstant;
 - // the rest of the class is unchanged
 
我們使用一些基本的幾何學知識就能達到這個效果,也就是正弦波。做游戲不一定要精通數(shù)學,基本的知識就非常夠用了。
游戲屏幕上還應該顯示計數(shù)。為此,我們要追蹤游戲過程的各種活動。
將以下代碼與所有其他變量聲明一起放在程序的開頭部分:
- // this goes at the start of the program
 - // to track players’s progress
 - POP.score = {
 - taps: 0,
 - hit: 0,
 - escaped: 0,
 - accuracy: 0
 - },
 
現(xiàn)在,在Bubble類,當泡泡離開屏幕,我們可以用POP.score.escaped記錄。
- // in the bubble class, when a bubble makes it to
 - // the top of the screen
 - if (this.y < -10) {
 - POP.score.escaped += 1; // update score
 - this.remove = true;
 - }
 
在主要更新循環(huán)中,我們相應地增加POP.score.hit:
- // in the update loop
 - if (POP.entities[i].type === ‘bubble’ && checkCollision) {
 - hit = POP.collides(POP.entities[i],
 - {x: POP.Input.x, y: POP.Input.y, r: 7});
 - if (hit) {
 - POP.score.hit += 1;
 - }
 - POP.entities[i].remove = hit;
 - }
 
為了得出命中率,我們必須記錄玩家的所有觸擊動作:
- // and record all taps
 - if (POP.Input.tapped) {
 - // keep track of taps; needed to
 - // calculate accuracy
 - POP.score.taps += 1;
 
命中率的算法就是,觸擊數(shù)乘上100。注意,~~(POP.score.accuracy)把約數(shù)變成整數(shù)。
- // Add at the end of the update loop
 - // to calculate accuracy
 - POP.score.accuracy = (POP.score.hit / POP.score.taps) * 100;
 - POP.score.accuracy = isNaN(POP.score.accuracy) ?
 - 0 :
 - ~~(POP.score.accuracy); // a handy way to round floats
 
最后,我們使用POP.Draw.text來顯示得分。
- // and finally in the draw function
 - POP.Draw.text(‘Hit: ‘ + POP.score.hit, 20, 30, 14, ‘#fff’);
 - POP.Draw.text(‘Escaped: ‘ + POP.score.escaped, 20, 50, 14, ‘#fff’);
 - POP.Draw.text(‘Accuracy: ‘ + POP.score.accuracy + ‘%’, 20, 70, 14, ‘#fff’);
 
7、修飾
我們都知道,制作一個可玩的demo只需要若干小時,但一款漂亮的游戲卻要耗費數(shù)天、數(shù)月甚至數(shù)年!
我們可以通過以下做法增加這款小游戲的視覺吸引力。
顆粒效果
大多數(shù)游戲都會使用顆粒效果,特別是對于爆炸。當玩家觸擊泡泡時,泡泡就碎成若干小泡泡,而不是立即消失,效果會不會更好呢?
看看我們的Particle類:
- POP.Particle = function(x, y,r, col) {
 - this.x = x;
 - this.y = y;
 - this.r = r;
 - this.col = col;
 - // determines whether particle will
 - // travel to the right of left
 - // 50% chance of either happening
 - this.dir = (Math.random() * 2 > 1) ? 1 : -1;
 - // random values so particles do not
 - // travel at the same speeds
 - this.vx = ~~(Math.random() * 4) * this.dir;
 - this.vy = ~~(Math.random() * 7);
 - this.remove = false;
 - this.update = function() {
 - // update coordinates
 - this.x += this.vx;
 - this.y += this.vy;
 - // increase velocity so particle
 - // accelerates off screen
 - this.vx *= 0.99;
 - this.vy *= 0.99;
 - // adding this negative amount to the
 - // y velocity exerts an upward pull on
 - // the particle, as if drawn to the
 - // surface
 - this.vy -= 0.25;
 - // off screen
 - if (this.y < 0) {
 - this.remove = true;
 - }
 - };
 - this.render = function() {
 - POP.Draw.circle(this.x, this.y, this.r, this.col);
 - };
 - };
 
以上代碼的作用顯而易見。當泡泡被擊中時,它會碎成若干加速上升到水面的顆粒。不過,本文不會探討這個效果的算術和物理學。
為了制作顆粒效果,我們我們要在entities集合中添加若干泡泡被擊中時會出現(xiàn)的顆粒:
- // modify the main update function like so:
 - if (hit) {
 - // spawn an explosion
 - for (var n = 0; n < 5; n +=1 ) {
 - POP.entities.push(new POP.Particle(
 - POP.entities[i].x,
 - POP.entities[i].y,
 - 2,
 - // random opacity to spice it up a bit
 - ‘rgba(255,255,255,’+Math.random()*1+’)’
 - ));
 - }
 - POP.score.hit += 1;
 - }
 
水波
考慮到游戲發(fā)生在水下,有必要在屏幕頂部添加水波效果。我們可以通過繪制大量重疊的圓形來制造水波的錯覺:
- // set up our wave effect;
 - // basically, a series of overlapping circles
 - // across the top of screen
 - POP.wave = {
 - x: -25, // x coordinate of first circle
 - y: -40, // y coordinate of first circle
 - r: 50, // circle radius
 - time: 0, // we’ll use this in calculating the sine wave
 - offset: 0 // this will be the sine wave offset
 - };
 - // calculate how many circles we need to
 - // cover the screen’s width
 - POP.wave.total = Math.ceil(POP.WIDTH / POP.wave.r) + 1;
 
把以上代碼添加到POP.init函數(shù)前面。POP.wave有許多值。
添加以下代碼到主要更新函數(shù)中。它作用正統(tǒng)波來調(diào)整水波的位置,從而產(chǎn)生水面運動的錯覺。
- // update wave offset
 - // feel free to play with these values for
 - // either slower or faster waves
 - POP.wave.time = new Date().getTime() * 0.002;
 - POP.wave.offset = Math.sin(POP.wave.time * 0.8) * 5;
 
最后要做的就是讓render函數(shù)繪制水波:
- // display snazzy wave effect
 - for (i = 0; i < POP.wave.total; i++) {
 - POP.Draw.circle(
 - POP.wave.x + POP.wave.offset + (i * POP.wave.r),
 - POP.wave.y,
 - POP.wave.r,
 - ‘#fff’);
 - }
 
這里,我們對泡泡重復使用正弦波,使水波活動更加溫和。
結(jié)語
終于完工了。希望你通過這個粗糙的教程能學習到一些制作HTML5游戲的技巧。我們已經(jīng)制作了一款非常簡單的游戲,它可以在大多數(shù)智能手機和瀏覽器上運行。你還可以考慮從以下幾個方面進一步改進游戲:
1、使用本地存儲器保存最高得分。
2、添加載入畫面和結(jié)束畫面。
3、添加增益道具。
4、添加聲音。與我在本文開頭部分所說的相反,這并非不可能,只是會有一點麻煩。技術之一是使用聲音精靈(相當于CSS中的圖像精靈)。
5、盡情發(fā)揮你的想像力!
如果你有興趣進一步探索手機HTML5游戲的潛力,我建議你多多測試框架,看看什么對你有用。如果你愿意花一點錢,Impact引擎是一個好起點,它附帶詳盡的說明文件和實用的論壇?!禭-Type》效果不錯吧?

















 
 
 

 
 
 
 