html5+Canvas實現酷炫的小游戲

来源:http://www.cnblogs.com/jarson-7426/archive/2016/05/29/5539211.html
-Advertisement-
Play Games

最近除了做業務,也在嘗試學習h5和移動端,在這個過程中,學到了很多,利用h5和canvas做了一個愛心魚的小游戲。 "點這裡去玩一下" PS: 貌似有點閃屏,親測多刷新兩下就好了==。代碼在本地跑都不會閃,放到博客里就閃了,我也不知道為什麼。。。回頭我再看看是什麼問題。 另外,我把代碼放到githu ...


最近除了做業務,也在嘗試學習h5和移動端,在這個過程中,學到了很多,利用h5和canvas做了一個愛心魚的小游戲。點這裡去玩一下

PS: 貌似有點閃屏,親測多刷新兩下就好了==。代碼在本地跑都不會閃,放到博客里就閃了,我也不知道為什麼。。。回頭我再看看是什麼問題。

另外,我把代碼放到github上了,博友們也可以直接down代碼,不需要任務服務,本地就能跑起來。大家要是覺得還行,就給個star吧!源碼地址點這裡

首先截個圖來看看界面效果:

screenshot

下買我就做游戲的步驟來分享總結一下用到的h5API和一些常見的數學函數。(推薦你先去玩一玩游戲,才能更好的明白這些邏輯喲~)

序言

首先,一個游戲最重要的就是動畫,怎麼讓元素動起來呢?先來看一句話:

元素的位置移動,就形成了動畫。

一幀一幀的來渲染這個元素,而且這個元素每一幀的位置都不一樣,我們的眼睛看到的就是動畫了。OK,先來介紹requestAnimationFrame這個函數。

我們都知道,隔一段時間重新渲染,可以用到setTimeoutsetInterval這兩個函數,那這裡為什麼不用呢?

我來簡單舉個例子吧:

  • setInterval(myFun, 1); 意思是隔一毫秒執行一個myFun函數,但是這樣就有一個問題了,比如我myFun函數裡面繪製的東西比較耗時,而1ms之內還沒有完全繪製出來,但是這段代碼強制1ms之後又開始繪製下一幀了,所以就會出現丟幀的問題,而如果時間設置太長,就會出現視覺卡頓的問題。
  • requestAnimationFrame(myFun); 如果我們這樣寫,又是什麼意思呢?意思是根據一定的時間間隔,會自動執行myFun函數來進行繪製。這個“一定的時間間隔”就是根據瀏覽器的性能或者網速快慢來決定了,總之,它會保證你繪製完這一幀,才會繪製下一幀,保證性能的同時,也保證動畫的流暢。

動畫解決了,那麼用什麼來繪製每一幀的頁面呢?這時就要用到h5的神奇——canvas了,所以canvas畫布的API非常重要。

html文件

    <div class="page">
        <div class="content" id = "main">
            <canvas id = "canvas1" width="800" height="600">
            </canvas>
            <canvas id = "canvas2" width="800" height="600">
            </canvas>
        </div>
    </div>
  • 定義兩個畫布,分別在畫布上繪製相應的物體;
  • canvas2 上繪製,背景、海葵、果實;
  • canvas1 上繪製,大魚、小魚、顯示文字、圓圈特效;

js文件

    function init(){
        can1 = document.getElementById('canvas1');     //畫布
        ctx1 = can1.getContext('2d');   //畫筆
        can2 = document.getElementById('canvas2');
        ctx2 = can2.getContext('2d');   //下麵的canvas
    }
    function gameloop(){
        requestAnimFrame(gameLoop);
        //繪製物體...
    }
    var requestAnimFrame = (function() {
        return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
        function(callback, element) {
            return window.setTimeout(callback, 1000 / 60);
        };
})();
  • init函數初始化一些變數,比如海葵對象,大魚、小魚對象等等。
  • gameloop函數用於繪製每一幀的頁面。下麵所介紹的所有繪製函數都是在這裡執行。
  • requestAnimFrame函數是為了相容所有瀏覽器。

下麵我們就開始繪製游戲中出現的東西,順便看看都用到了哪些有趣的API函數。go!go!go!

繪製背景和海葵

背景是一張圖,而海葵是一個類,它有x坐標,y坐標,個數等等屬性,有初始化init和draw方法。

    drawImage(image, x, y, width, height)
    ctx2.save();
    ctx2.globalAlpha = 0.7;
    ctx2.lineWidth = 20;
    ctx2.lineCap = 'round';
    ctx2.strokeStyle = '#3b154e';
    ctx2.beginPath();
    ctx2.moveTo(this.rootx[i], canHei);     //起始點
    ctx2.lineTo(this.rootx[i], canHei - 220,);  //結束點   ctx2.stroke();
    ctx2.restore();
  • ctx2.drawImage(image, x, y, width, height) //x,y代表坐標,width和height代表寬高
  • ctx2.save(); //定義作用空間
  • ctx2.globalAlpha = 0.7; //定義線的透明度
  • ctx2.lineWidth = 20; // 寬度
  • ctx2.lineCap = ‘round’; // 圓角
  • ctx2.strokeStyle = '#3b154e'; //定義繪製線條的顏色
  • ctx2.beginPath(); //開始路徑
  • ctx2.moveTo(x,y); //線的起點,x,y代表坐標(坐標原點在左上角)
  • ctx2.lineTo(x,y); // 線條從起點連接到這個點
  • ctx2.stroke(); // 開始繪製線條
  • ctx2.restore(); //作用空間結束

海葵產生果實

果實也是一個類,他的屬性有:坐標、類型(黃色和藍色)、大小、狀態(顯示還是隱藏)、速度(向上漂浮的速度)等等屬性;他的方法有:初始化init、出生born和繪製draw。

draw方法:

    for(var i =0;i< this.num; i++){
        if(this.alive[i]){
            //find an ane, grow, fly up...
            if(this.size[i] <= 16){   //長大狀態
                this.grow[i] = false;
                this.size[i] += this.speed[i] * diffframetime * 0.8; 
            }else{   //已經長大,向上漂浮
                this.grow[i] = true;
                this.y[i] -= this.speed[i] * 5 * diffframetime;
            }
            var pic = this.orange;
            if(this.type[i] == 'blue')   pic = this.blue;
            ctx2.drawImage(pic, this.x[i] - this.size[i] * 0.5, this.y[i] - this.size[i] * 0.5, this.size[i], this.size[i]);
            if(this.y[i] < 8){
                this.alive[i] = false;
            }
        }
    }

born方法:隨機找到一個海葵的坐標,在海葵的坐標上出生一個果實。

繪製大魚和小魚

大魚和小魚都是一個類,它的屬性有:坐標、旋轉角度、尾巴擺動時間間隔、眨眼睛時間間隔、身體圖片數組....等等

先把大魚繪製出來,用canvas的drawImage方法。

比較難的是大魚的動畫,大魚會隨著滑鼠移動而移動的動畫,這裡定義了兩個函數:

function lerpAngle(a, b, t) {     //計算每一幀旋轉的角度
    var d = b - a;
    if (d > Math.PI) d = d - 2 * Math.PI;
    if (d < -Math.PI) d = d + 2 * Math.PI;
    return a + d * t;
}
function lerpDistance(aim, cur, ratio) {   //aim:目標 cur:當前 ratio:百分比  計算每一幀趨近的距離
    var delta = cur - aim;
    return aim + delta * ratio
}
this.momTailTimer += diffframetime;
if(this.momTailTimer > 50){
    this.momTailIndex = (this.momTailIndex + 1) % 8;      //根據時間間隔改變尾巴圖片
    this.momTailTimer %= 50; 
}
  • lerpDistance 是計算每一幀大魚趨緊到滑鼠的距離。
  • lerpAngle 用來計算大魚每一幀向滑鼠旋轉的角度。 定義這兩個函數,讓大魚動起來比較平滑。

獲得了一個角度之後,怎麼讓大魚旋轉起來呢?這裡又需要用到幾個API了。

  • ctx1.save(); //建議每次繪製都使用save和restore,可以避免定義樣式,發生衝突。
  • ctx1.translate(this.x, this.y); //把原點變成(this.x , this.y);
  • ctx1.rotate(this.angle); //根據原點順時針旋轉一個角度

繪製小魚跟大魚是一樣的,不做詳述。但是需要註意的是繪製小魚的時候有個判斷,當小魚的顏色變白的時候,游戲結束。

this.babyBodyTimer += diffframetime;
if(this.babyBodyTimer > 550){   //身體圖片變化的計數器 > 550ms
    this.babyBodyIndex += 1;     //身體圖片變淡
    this.babyBodyTimer %= 550;
    scoreOb.strength = ((20 - this.babyBodyIndex)/2).toFixed(0);
    if(this.babyBodyIndex > 19){   //如果身體變成白色,game over;
        this.babyBodyIndex = 19;
        scoreOb.gameOver = true;
        can1.style.cursor = "pointer";
    }
}

大魚吃果實

大魚吃果實是根據距離來判斷定的,如果大魚和果實的距離小於30,則讓果實消失,並且出現白色圓環,並且分值有一定的變化。

    jzk.momEatFruit = function(){     //判斷果實和大魚之間的距離,小於30說明被吃掉
        for(var i = 0;i < fruitOb.num; i++ ){
            if(fruitOb.alive[i] && fruitOb.grow[i]){
                var len = calLength2(fruitOb.x[i], fruitOb.y[i], momOb.x, momOb.y);
                if(len < 30){
                    fruitOb.dead(i);    //如果距離小於30,則被吃掉
                    waveOb.born(i);     //吃掉的時候,產生���圈
                    scoreOb.fruitNum ++;    //吃到的果實數量+1
                    momOb.momBodyIndex = momOb.momBodyIndex == 7 ? momOb.momBodyIndex : (momOb.momBodyIndex + 1);      //大魚的身體顏色紅
                    if(fruitOb.type[i] == 'blue'){
                        scoreOb.doubleNum ++;  //吃到藍色果實,倍數+1
                    }
                }
            }
        }
    }

其中有一個calLength2函數,使用來計算兩個點之間的距離的。

function calLength2(x1, y1, x2, y2) {    //計算兩個點之間的距離,,, 先求平方和,再開平方
    return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
}

大魚吃到果實的時候,會產生一個白色的圓圈,這個效果怎麼實現呢?

首先,我們定義一個waveObject類,它的屬性有:坐標、數量、半徑、使用狀態。它的方法有:初始化、繪製和出生。

我們來看一下繪製圓圈的方法:

for(var i = 0;i< this.num; i++){
    if(this.status[i]){     //如果圓圈是使用狀態,則繪製圓圈
        this.r[i] += diffframetime * 0.04;
        if(this.r[i] > 60){
            this.status[i] = false;
            return false;
        }
        var alpha = 1 - this.r[i] / 60;
        ctx1.strokeStyle = "rgba(255, 255, 255, "+ alpha +")";
        ctx1.beginPath();
        ctx1.arc(this.x[i], this.y[i], this.r[i], 0, 2 * Math.PI);   //畫圓,
        ctx1.stroke();  
    }
}

一幀一幀的畫每一個圓,圓的半徑逐漸增大,透明度逐漸減小,直到半徑大於60的時候,把狀態設為false,讓其回歸物體池中。

這裡又用到了一個新的方法:ctx1.arc(x,y,r,deg); //畫圓,x,y是中心圓點,r是半徑,deg是角度,360度就是一個整圓。

再來看一下出生的方法:

for(var i = 0; i< this.num; i++){
    if(!this.status[i]){
        this.status[i] = true;   //把圓圈狀態設為使用狀態
        this.x[i] = fruitOb.x[index];
        this.y[i] = fruitOb.y[index];
        this.r[i] = 10;
        return false;   //找到一個未使用的圓圈,就結束。
    }
}

圓圈出生的坐標就是被吃果實的坐標。

大魚喂小魚

大魚喂小魚同上,不再詳述,這裡喂小魚之後,大魚身體變白,小魚隨果實數量相應增多,另外需要註意的是,此時產生圓圈的坐標是小魚的坐標。

游戲分值計算

定義一個數據類,它的屬性有:吃到的果實數量、倍數、總分、力量值、游戲狀態(是否結束)等;方法有:初始化、繪製分數。

這裡我們需要在畫布上繪製文字,又用到了新的API:

  • ctx1.save();
  • ctx1.font = '40px verdana'; 定義文字的大小和字體;
  • ctx1.shadowBlur = 10; 定義文字的陰影寬度
  • ctx1.shadowColor = "white"; 定義文字陰影的顏色;
  • ctx1.fillStyle = "rgba(255, 255, 255, "+ this.alpha +")"; 定義文字的顏色(rgba,a代表透明度)
  • ctx1.fillText("GAME OVER", canWid * 0.5, canHei * 0.5 - 25); 繪製文字,第一個參數是字元串,支持表達式,後兩個參數是坐標值。
  • ctx1.font = '25px verdana';
  • ctx1.fillText("CLICK TO RESTART", canWid * 0.5, canHei * 0.5 + 25);
  • ctx1.restore();

總結

好啦,整個游戲的製作過程就分享完了,做的過程中有遇到過很多問題,不過都一一解決了,加深了很多以前模糊的概念,也學到了很多新的知識,比如使用rgba()來一起控制顏色和透明度,以前還真沒用到過。

這個游戲本身功能比較簡單,但是動畫還算比較酷炫。這也算是一個比較基本的動畫基礎框架了,而比較不容易理解的地方也有很多,比如求趨近的角度函數lerpAngle(a,b,c),還有Math.atan2()這個函數,等等。
歡迎大家提出bug或者改進建議~~~


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、表單(Form) 源碼文件:_form.scssmixins/_form.scss 1、按層次結構分:form-group -> form-control/input-group/form-static-control -> 各類標簽2、Form-group/form-control/input ...
  • 第1章 css和文檔 1,元素:替換元素(img input),非替換元素(大多數span)。 2,link:rel(代表關係:stylesheet,候選樣式表:alternate stylesheet);type(text/css);media:(all(所有表現媒體, screen,print) ...
  • 繼前面幾篇文章後再來說說老生常談的話題,怎麼樣提升前端性能。文中很多取材自網路及《High Performance Web Sites》,並根據自己工作中所接觸到的知識整理而成。 http://hovertree.com/menu/webfront/ 1. 減少HTTP請求 終端用戶響應時間80%消 ...
  • 對初學者來說應該學習的JavaScript編碼規範: 傳送門: http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=29292475&id=5019448 ...
  • 看到兩篇關於CSS的文章,總結的非常好。因為沒有那個網站的賬號,沒法收藏轉發,所以把鏈接貼在這裡,分享給大家。這兩篇文章對於初學CSS的人來說,總結得很精煉準確,而且通俗易懂。推薦~ 有關CSS的一些事--CSS和頁面佈局 http://blog.chinaunix.net/xmlrpc.php?r ...
  • 動畫 animate() 01.animate()方法的簡單使用 有些複雜的動畫通過之前學到的幾個動畫函數是不能夠實現,這時候就是強大的animate方法了。 操作一個元素執行3秒的淡入動畫,對比下一下2組動畫設置的區別。 顯而易見,animate方法更加靈活了,可以精確的控制樣式屬性從而執行動畫。 ...
  • LN :跟著W3School學HTML 007 內容參考自W3School [HTML 教程][1] HTML文本格式化 | 標簽 | 描述 | | | : | | `` | 定義粗體文本 | | `` | 定義大號字 | | `` | 定義著重文字 | | `` | 定義斜體字 | | `` | ...
  • 一.js的數據類型和變數 JavaScript 有六種數據類型。主要的類型有 number、string、object 以及 Boolean 類型,其他兩種類型為 null 和 undefined。 String 字元串類型:字元串是用單引號或雙引號來說明的。(使用單引號來輸入包含引號的字元串。)如 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...