搜尋
首頁web前端H5教程2小時完成HTML5拼圖小遊戲代碼圖文介紹

初學lufylegend.js之日,我用lufylegend.js開發了第一個HTML5小遊戲-拼圖遊戲,也寫了篇文章來炫耀一下:HTML5小遊戲《智力大拼圖》發布,挑戰你的思維風暴。不過當時初學遊戲開發,經驗淺薄,所以沒有好好專研遊戲裡的演算法和程式碼的缺陷,導致遊戲出現了很多bug,甚至拼圖打亂後很可能無法復原。最近常有朋友問起這個遊戲,希望我能把程式碼裡的bug改一下方便初學者學習,順便我也打算測試一下自己寫這種小遊戲的速度,所以就抽出了一些時間將這個遊戲從頭到尾重新寫了一遍,計算了一下用時,從準備、修改素材到最後完成遊戲,一共用了大約2h的時間。

以下是遊戲網址:

http://yuehaowang.github.io/games/puzzle/

這是我的遊戲紀錄,歡迎各位挑戰:

2小時完成HTML5拼圖小遊戲代碼圖文介紹

接下來就來講講如何開發完成這款遊戲的。 (按「編年史」)

準備階段

準備lufylegend遊戲引擎,大家可以去官方網站下載:

lufylegend.com/lufylegend

引擎文件地址:

lufylegend.com/lufylegend/api

可以說,如果沒有強大的lufylegend引擎,這種html5小遊戲用原生canvas製作,少說要一天呢。

0~30min

準備素材(10min) + 修改素材(20min)。由於在下實在手殘,不善於P圖,修改圖片花了約20min,囧…

30~50min

開發開始介面。遊戲不能沒有開始介面所以我們先實現這部分程式碼。在此之前是index.html裡的程式碼,程式碼如下:

<!DOCTYPE html>
<html>
<head>
    <title>Puzzle</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <script type="text/javascript" src="./lib/lufylegend-1.10.1.simple.min.js"></script>
    <script type="text/javascript" src="./js/Main.js"></script>
</head>
<body style="margin: 0px; font-size: 0px; background: #F2F2F2;">
    <p id="mygame"></p>
</body>
</html>

主要是引入一些js文件,不多說。然後準備一個Main.js文件,在這個文件裡加入初始化介面和載入資源的程式碼:

/** 初始化游戏 */
LInit(60, "mygame", 390, 580, main);

var imgBmpd;
/** 游戏层 */
var stageLayer, gameLayer, overLayer;
/** 拼图块列表 */
var blockList;
/** 是否游戏结束 */
var isGameOver;
/** 用时 */
var startTime, time, timeTxt;
/** 步数 */
var steps, stepsTxt;

function main () {
    /** 全屏设置 */
    if (LGlobal.mobile) {
        LGlobal.stageScale = LStageScaleMode.SHOW_ALL;
    }
    LGlobal.screen(LGlobal.FULL_SCREEN);

    /** 添加加载提示 */
    var loadingHint = new LTextField();
    loadingHint.text = "资源加载中……";
    loadingHint.size = 20;
    loadingHint.x = (LGlobal.width - loadingHint.getWidth()) / 2;
    loadingHint.y = (LGlobal.height - loadingHint.getHeight()) / 2;
    addChild(loadingHint);

    /** 加载图片 */
    LLoadManage.load(
        [
            {path : "./js/Block.js"},
            {name : "img", path : "./images/img.jpg"}
        ],
        null,
        function (result) {
            /** 移除加载提示 */
            loadingHint.remove();

            /** 保存位图数据,方便后续使用 */
            imgBmpd = new LBitmapData(result["img"]);

            gameInit();
        }
    );
}

function gameInit (e) {
    /** 初始化舞台层 */
    stageLayer = new LSprite();
    stageLayer.graphics.drawRect(0, "", [0, 0, LGlobal.width, LGlobal.height], true, "#EFEFEF");
    addChild(stageLayer);

    /** 初始化游戏层 */
    gameLayer = new LSprite();
    stageLayer.addChild(gameLayer);

    /** 初始化最上层 */
    overLayer = new LSprite();
    stageLayer.addChild(overLayer);

    /** 添加2小時完成HTML5拼圖小遊戲代碼圖文介紹 */
    addBeginningUI();
}

以上程式碼有詳細註釋,大家可以對照引擎文件和註解進行閱讀。有些全域變數會在以後的程式碼中使用,大家可以先忽略。接下來是addBeginningUI函數裡的程式碼,用於實作開始介面:

function addBeginningUI () {
    var beginningLayer = new LSprite();
    beginningLayer.graphics.drawRect(0, "", [0, 0, LGlobal.width, LGlobal.height], true, "#EDEDED");
    stageLayer.addChild(beginningLayer);

    /** 游戏标题 */
    var title = new LTextField();
    title.text = "拼图游戏";
    title.size = 50;
    title.weight = "bold";
    title.x = (LGlobal.width - title.getWidth()) / 2;
    title.y = 160;
    title.color = "#FFFFFF";
    title.lineWidth = 5;
    title.lineColor = "#000000";
    title.stroke = true;
    beginningLayer.addChild(title);

    /** 开始游戏提示 */
    var hint = new LTextField();
    hint.text = "- 点击屏幕开始游戏 -";
    hint.size = 25;
    hint.x = (LGlobal.width - hint.getWidth()) / 2;
    hint.y = 370;
    beginningLayer.addChild(hint);

    /** 开始游戏 */
    beginningLayer.addEventListener(LMouseEvent.MOUSE_UP, function () {
        beginningLayer.remove();

        startGame();
    });
}

到此,運行程式碼,得到我們的開始介面:

2小時完成HTML5拼圖小遊戲代碼圖文介紹

看到這個畫面,其實我自己都想吐槽一下實在是太「樸素」了,囧…

不過我這次圖面製作速度,所以還望各位看官海量。

50~90min

這40分鐘的時間,是最關鍵時期,期間我們要完成整個遊戲的主體部分。首先,我們需要用程式碼來實現以下過程:

初始化游戏界面数据(如游戏时间、所用步数)和显示一些UI部件(如图样)
|
-> 获取随机的拼图块位置
|
-> 显示打乱后的拼图块

我們將這些步驟做成一個個的函數方便我們統一呼叫:

function startGame () {
    isGameOver = false;

    /** 初始化时间和步数 */
    startTime = (new Date()).getTime();
    time = 0;
    steps = 0;
    /** 初始化拼图块列表 */
    initBlockList();
    /** 打乱拼图 */
    getRandomBlockList();
    /** 显示拼图 */
    showBlock();
    /** 显示缩略图 */
    showThumbnail();
    /** 显示时间 */
    addTimeTxt();
    /** 显示步数 */
    addStepsTxt();

    stageLayer.addEventListener(LEvent.ENTER_FRAME, onFrame);
}

函數一開始,我們把 isGameOver變數設定為false代表遊戲未結束,在後期的程式碼裡,我們會看到這個變數的作用。接著我們初始化了用於表示時間和步數的timesteps這兩個全域變量,另外初始化變數startTime的值用於後面計算遊戲時間。
接下來,我們就要開始初始化拼圖塊了。請參閱initBlockList裡的程式碼:

function initBlockList () {
    blockList = new Array();

    for (var i = 0; i < 9; i++) {
        /** 根据序号计算拼图块图片显示位置 */
        var y = (i / 3) >>> 0, x = i % 3;

        blockList.push(new Block(i, x, y));
    }
}

這裡我們使用了一個Block類,這個類別用於顯示拼圖區塊和儲存拼圖區塊的數據,並提供了一些方法來操控拼圖塊,以下是其建構器的程式碼:

function Block (index, x, y) {
    LExtends(this, LSprite, []);

    var bmpd = imgBmpd.clone();
    bmpd.setProperties(x * 130, y * 130, 130, 130);
    this.bmp = new LBitmap(bmpd);
    this.addChild(this.bmp);

    var border = new LShape();
    border.graphics.drawRect(3, "#CCCCCC", [0, 0, 130, 130]);
    this.addChild(border);

    this.index = index;

    this.addEventListener(LMouseEvent.MOUSE_UP, this.onClick);
}

Block類別繼承自LSprite,屬於一個顯示對象,所以我們在這個類中新增了一個點陣圖物件用於顯示拼圖塊對應的圖片。除此之外,我們還為拼圖塊添加了一個邊框,在顯示時用於隔開周圍的拼圖塊。 Block類別有一個index屬性,代表拼圖區塊在拼圖區塊清單blockList中的正確位置。最後,我們為此添加了一個滑鼠按下事件,用於處理滑鼠按下後移動圖塊操作。

接下來我們還要介紹這個類別的一個方法setLocation

Block.prototype.setLocation = function (x, y) {
    this.locationX = x;
    this.locationY = y;

    this.x = x * 130;
    this.y = y * 130;
};

這個方法用來設定拼圖區塊物件的顯示位置以及儲存拼圖區塊的「陣列位置」。什麼是「數組位置」呢?各位看官可以透過下面的圖片加以了解:

2小時完成HTML5拼圖小遊戲代碼圖文介紹

可以看到,「數組位置」就類似於二維數組中的元素下標。儲存這個位置的作用在於可以很方便地從blockList中取得到附近的其他拼圖塊。這個方法在我們顯示拼圖時有調用到,在顯示拼圖之前,我們得先打亂拼圖,見如下程式碼:

function getRandomBlockList () {
    /** 随机打乱拼图 */
    blockList.sort(function () {
        return 0.5 - Math.random();
    });

    /** 计算逆序和 */
    var reverseAmount = 0;

    for (var i = 0, l = blockList.length, preBlock = null; i < l; i++) {
        if (!preBlock) {
            preBlock = blockList[0];

            continue;
        }

        var currentBlock = blockList[i];

        if (currentBlock.index < preBlock.index) {
            reverseAmount++;
        }

        preBlock = currentBlock;
    }

    /** 检测打乱后是否可还原 */
    if (reverseAmount % 2 != 0) {
        /** 不合格,重新打乱 */
        getRandomBlockList();
    }
}

打亂拼圖部分直接用數組的sort##。進行隨機打亂:

blockList.sort(function () {
    return 0.5 - Math.random();
});

其实打乱算法有很多种,我这里采用最粗暴的方法,也就是随机打乱。这种算法简单是简单,坏在可能出现无法复原的现象。针对这个问题,就有配套的检测打乱后是否可还原的算法,具体的算法理论我摘用lufy大神的评论:

此类游戏能否还原关键是看它打乱后的逆序次数之和是否为偶数
假设你打乱后的数组中的每一个小图块为obj0obj1obj2,…它们打乱之前的序号分别为obj0.numobj1.num
接下来循环数组,如果前者的序号比后者大,如obj0.num > obj1.num,这表示一个逆序
当全部的逆序之和为奇数时表示不可还原,重新打乱即可,打乱后重新检测,直到逆序之和为偶数为止

上面我给出的getRandomBlockList里的代码就是在实现打乱算法和检测是否可还原算法。

还有一种打乱方式,大家可以尝试尝试:和复原拼图一样,将空白块一步一步地与周围的拼图随机交换顺序。这个打乱算法较上一种而言,不会出现无法复原的现象,而且可以根据打乱的步数设定游戏难度。

在完成打乱拼图块后,如期而至的是显示拼图块:

function showBlock() {
    for (var i = 0, l = blockList.length; i < l; i++) {
        var b = blockList[i];

        /** 根据序号计算拼图块位置 */
        var y = (i / 3) >>> 0, x = i % 3;

        b.setLocation(x, y);

        gameLayer.addChild(b);
    }
}

显示了拼图块后,我们要做的就是添加操作拼图块的功能。于是需要拓展Block类,为其添加事件监听器onClick方法:

Block.prototype.onClick = function (e) {
    var self = e.currentTarget;

    if (isGameOver) {
        return;
    }

    var checkList = new Array();

    /** 判断右侧是否有方块 */
    if (self.locationX > 0) {
        checkList.push(Block.getBlock(self.locationX - 1, self.locationY));
    }

    /** 判断左侧是否有方块 */
    if (self.locationX < 2) {
        checkList.push(Block.getBlock(self.locationX + 1, self.locationY));
    }

    /** 判断上方是否有方块 */
    if (self.locationY > 0) {
        checkList.push(Block.getBlock(self.locationX, self.locationY - 1));
    }

    /** 判断下方是否有方块 */
    if (self.locationY < 2) {
        checkList.push(Block.getBlock(self.locationX, self.locationY + 1));
    }

    for (var i = 0, l = checkList.length; i < l; i++) {
        var checkO = checkList[i];

        /** 判断是否是空白拼图块 */
        if (checkO.index == 8) {
            steps++;
            updateStepsTxt();

            Block.exchangePosition(self, checkO);

            break;
        }
    }
};

首先,我们在这里看到了isGameOver全局变量的作用,即在游戏结束后,阻断点击拼图块后的操作。

在点击了拼图块后,我们先获取该拼图块周围的拼图块,并将它们装入checkList,再遍历checkList,当判断到周围有空白拼图块后,即周围有index属性等于8的拼图块后,先更新操作步数,然后将这两个拼图块交换位置。具体交换拼图块位置的方法详见如下代码:

Block.exchangePosition = function (b1, b2) {
    var b1x = b1.locationX, b1y = b1.locationY,
        b2x = b2.locationX, b2y = b2.locationY,
        b1Index = b1y * 3 + b1x,
        b2Index = b2y * 3 + b2x;

    /** 在地图块数组中交换两者位置 */
    blockList.splice(b1Index, 1, b2);
    blockList.splice(b2Index, 1, b1);

    /** 交换两者显示位置 */
    b1.setLocation(b2x, b2y);
    b2.setLocation(b1x, b1y);

    /** 判断游戏是否结束 */
    Block.isGameOver();
};

还有就是Block.getBlock静态方法,用于获取给定的“数组位置”下的拼图块:

Block.getBlock = function (x, y) {
    return blockList[y * 3 + x];
};

Block.exchangePosition中,我们通过Block.isGameOver判断玩家是否已将拼图复原:

Block.isGameOver = function () {
    var reductionAmount = 0, l = blockList.length;

    /** 计算还原度 */
    for (var i = 0; i < l; i++) {
        var b = blockList[i];

        if (b.index == i) {
            reductionAmount++;
        }
    }

    /** 计算是否完全还原 */
    if (reductionAmount == l) {
        /** 游戏结束 */
        gameOver();
    }   
};

到这里,我们就实现了打乱和操作拼图块部分。

90~120min

最后30min用于细枝末节上的处理,如显示拼图缩略图、显示&更新时间和步数,以及添加游戏结束画面,这些就交给如下冗长而简单的代码来完成吧:

function showThumbnail() {
    var thumbnail = new LBitmap(imgBmpd);
    thumbnail.scaleX = 130 / imgBmpd.width;
    thumbnail.scaleY = 130 / imgBmpd.height;
    thumbnail.x = (LGlobal.width - 100) /2;
    thumbnail.y = 410;
    overLayer.addChild(thumbnail);
}

function addTimeTxt () {
    timeTxt = new LTextField();
    timeTxt.stroke = true;
    timeTxt.lineWidth = 3;
    timeTxt.lineColor = "#54D9EF";
    timeTxt.color = "#FFFFFF";
    timeTxt.size = 18;
    timeTxt.x = 20;
    timeTxt.y = 450;
    overLayer.addChild(timeTxt);

    updateTimeTxt();
}

function updateTimeTxt () {
    timeTxt.text = "时间:" + getTimeTxt(time);
}

function getTimeTxt () {
    var d = new Date(time);

    return d.getMinutes() + " : " + d.getSeconds();
};

function addStepsTxt () {
    stepsTxt = new LTextField();
    stepsTxt.stroke = true;
    stepsTxt.lineWidth = 3;
    stepsTxt.lineColor = "#54D9EF";
    stepsTxt.color = "#FFFFFF";
    stepsTxt.size = 18;
    stepsTxt.y = 450;
    overLayer.addChild(stepsTxt);

    updateStepsTxt();
}

function updateStepsTxt () {
    stepsTxt.text = "步数:" + steps;

    stepsTxt.x = LGlobal.width - stepsTxt.getWidth() - 20;
}

function onFrame () {
    if (isGameOver) {
        return;
    }

    /** 获取当前时间 */
    var currentTime = (new Date()).getTime();

    /** 计算使用的时间并更新时间显示 */
    time = currentTime - startTime;
    updateTimeTxt();
}

function gameOver () {
    isGameOver = true;

    var resultLayer = new LSprite();
    resultLayer.filters = [new LDropShadowFilter()];
    resultLayer.graphics.drawRoundRect(3, "#BBBBBB", [0, 0, 350, 350, 5], true,"#DDDDDD");
    resultLayer.x = (LGlobal.width - resultLayer.getWidth()) / 2;
    resultLayer.y = LGlobal.height / 2;
    resultLayer.alpha = 0;
    overLayer.addChild(resultLayer);

    var title = new LTextField();
    title.text = "游戏通关"
    title.weight = "bold";
    title.stroke = true;
    title.lineWidth = 3;
    title.lineColor = "#555555";
    title.size = 30;
    title.color = "#FFFFFF";
    title.x = (resultLayer.getWidth() - title.getWidth()) / 2;
    title.y = 30;
    resultLayer.addChild(title);

    var usedTimeTxt = new LTextField();
    usedTimeTxt.text = "游戏用时:" + getTimeTxt(time);
    usedTimeTxt.size = 20;
    usedTimeTxt.stroke = true;
    usedTimeTxt.lineWidth = 2;
    usedTimeTxt.lineColor = "#555555";
    usedTimeTxt.color = "#FFFFFF";
    usedTimeTxt.x = (resultLayer.getWidth() - usedTimeTxt.getWidth()) / 2;
    usedTimeTxt.y = 130;
    resultLayer.addChild(usedTimeTxt);

    var usedStepsTxt = new LTextField();
    usedStepsTxt.text = "所用步数:" + steps;
    usedStepsTxt.size = 20;
    usedStepsTxt.stroke = true;
    usedStepsTxt.lineWidth = 2;
    usedStepsTxt.lineColor = "#555555";
    usedStepsTxt.color = "#FFFFFF";
    usedStepsTxt.x = usedTimeTxt.x;
    usedStepsTxt.y = 180;
    resultLayer.addChild(usedStepsTxt);

    var hintTxt = new LTextField();
    hintTxt.text = "- 点击屏幕重新开始 -";
    hintTxt.size = 23;
    hintTxt.stroke = true;
    hintTxt.lineWidth = 2;
    hintTxt.lineColor = "#888888";
    hintTxt.color = "#FFFFFF";
    hintTxt.x = (resultLayer.getWidth() - hintTxt.getWidth()) / 2;
    hintTxt.y = 260;
    resultLayer.addChild(hintTxt);

    LTweenLite.to(resultLayer, 0.5, {
        alpha : 0.7,
        y : (LGlobal.height - resultLayer.getHeight()) / 2,
        onComplete : function () {
            /** 点击界面重新开始游戏 */
            stageLayer.addEventListener(LMouseEvent.MOUSE_UP, function () {
                gameLayer.removeAllChild();
                overLayer.removeAllChild();

                stageLayer.removeAllEventListener();

                startGame();
            });
        }
    });
}

Ok,2h下来,整个游戏就搞定咯~不得不表扬一下lufylegend这个游戏引擎,实在是可以大幅提升开发效率。

以上就是2小时完成HTML5拼图小游戏代码图文介绍的内容,更多相关内容请关注PHP中文网(www.php.cn)!


陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
HTML中的H5標籤是什麼?HTML中的H5標籤是什麼?May 09, 2025 am 12:11 AM

HTML中的H5標籤是第五級標題,用於標記較小的標題或子標題。 1)H5標籤幫助細化內容層次,提升可讀性和SEO。 2)結合CSS可定製樣式,增強視覺效果。 3)合理使用H5標籤,避免濫用,確保內容結構邏輯性。

H5代碼:Web結構的初學者指南H5代碼:Web結構的初學者指南May 08, 2025 am 12:15 AM

HTML5構建網站的方法包括:1.使用語義化標籤定義網頁結構,如、、等;2.嵌入多媒體內容,使用和標籤;3.應用表單驗證和本地存儲等高級功能。通過這些步驟,你可以創建一個結構清晰、功能豐富的現代網頁。

H5代碼結構:組織內容以實現可讀性H5代碼結構:組織內容以實現可讀性May 07, 2025 am 12:06 AM

通過合理的H5代碼結構可以讓頁面在眾多內容中脫穎而出。 1)使用語義化標籤如、、等組織內容,使結構清晰。 2)通過CSS佈局如Flexbox或Grid控制頁面在不同設備上的呈現效果。 3)實現響應式設計,確保頁面在不同屏幕尺寸上自適應。

H5與較舊的HTML版本:比較H5與較舊的HTML版本:比較May 06, 2025 am 12:09 AM

HTML5(H5)與舊版本HTML的主要區別包括:1)H5引入了語義化標籤,2)支持多媒體內容,3)提供離線存儲功能。 H5通過新標籤和API增強了網頁的功能和表現力,如和標籤,提高了用戶體驗和SEO效果,但需注意兼容性問題。

H5與HTML5:澄清術語和關係H5與HTML5:澄清術語和關係May 05, 2025 am 12:02 AM

H5和HTML5的區別在於:1)HTML5是網頁標準,定義結構和內容;2)H5是基於HTML5的移動網頁應用,適用於快速開發和營銷。

HTML5特徵:H5的核心HTML5特徵:H5的核心May 04, 2025 am 12:05 AM

HTML5的核心特性包括語義化標籤、多媒體支持、表單增強和離線存儲與本地存儲。 1.語義化標籤如、等提高了代碼可讀性和SEO效果。 2.多媒體支持通過和標籤簡化了嵌入媒體內容的過程。 3.表單增強引入了新的輸入類型和驗證屬性,簡化了表單開發。 4.離線存儲和本地存儲通過ApplicationCache和localStorage等提高了網頁性能和用戶體驗。

H5:探索最新版本的HTMLH5:探索最新版本的HTMLMay 03, 2025 am 12:14 AM

html5isamajorrevisionofthehtmlStandardThatRevolutionsWebDevelopmentBybyIntroDucingNewSemanticeLementSemelementsandAndCapabilities.1)itenhancesCodereAdabilityAndSeowitability andSeowithelientsLike,and.2)

超越基礎:H5代碼中的高級技術超越基礎:H5代碼中的高級技術May 02, 2025 am 12:03 AM

H5的高級技巧包括:1.利用進行複雜圖形繪製,2.使用WebWorkers提升性能,3.通過WebStorage增強用戶體驗,4.實現響應式設計,5.利用WebRTC實現實時通信,6.進行性能優化和最佳實踐。這些技巧幫助開發者構建更動態、互動和高效的Web應用。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)