成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

拼圖小游戲

svtter / 1663人閱讀

摘要:學(xué)習(xí)小游戲開(kāi)發(fā)中最常用的碰撞檢測(cè)狀態(tài)監(jiān)控刷新保持狀態(tài)的處理方法。保存縮略圖的信息是當(dāng)游戲結(jié)束后顯示源縮略圖時(shí),根據(jù)中的內(nèi)容展示圖片。

如果您想要綜合使用javascript中canvas、原生拖拽、本地存儲(chǔ)等多種技術(shù)完成一個(gè)有趣的項(xiàng)目,那么這篇博文將非常適合您,水平有限,還望感興趣的開(kāi)發(fā)人員給予更多代碼優(yōu)化建議。
1 簡(jiǎn)介和源碼

該項(xiàng)目中的拼圖小游戲使用javascript原創(chuàng),相比于網(wǎng)站上類(lèi)似的功能,它使用到的技術(shù)點(diǎn)更先進(jìn)豐富,功能更強(qiáng)大,還包含程序開(kāi)發(fā)中更多先進(jìn)的思想理念,從該項(xiàng)目中您將能學(xué)到:

FileReader、Image對(duì)象的配合canvas對(duì)圖片進(jìn)行壓縮,切割的技巧。

學(xué)習(xí)小游戲開(kāi)發(fā)中最常用的碰撞檢測(cè)、狀態(tài)監(jiān)控、刷新保持狀態(tài)的處理方法。

深入了解拖拽交換元素的細(xì)節(jié),學(xué)習(xí)到動(dòng)態(tài)元素綁定事件、回調(diào)函數(shù)的處理方式。

項(xiàng)目源碼-github

下面是游戲界面的示例圖:

2 實(shí)現(xiàn)思路

根據(jù)游戲界面圖我們可以將完成這么一個(gè)小游戲分為以下幾步來(lái)實(shí)現(xiàn):

1.拖拽圖片到指定區(qū)域,使用FileReader對(duì)象讀取到圖片的base64內(nèi)容,然后添加到Image對(duì)象中

2.當(dāng)Image對(duì)象加載完成后,使用canvas對(duì)圖片進(jìn)行等比縮放,然后取到縮略圖的base64內(nèi)容,添加到另外一個(gè)縮略圖Image對(duì)象中,并將該縮略圖base64的內(nèi)容保存到本地存儲(chǔ)(localStorage)中

3.當(dāng)縮略圖Image對(duì)象加載完成后,再次使用canvas對(duì)縮略圖進(jìn)行切割,該游戲中將縮略圖切割成3*4一共12等份,使用本地存儲(chǔ)保存每份切割縮略圖base64內(nèi)容,將縮略圖順序打亂,使用img標(biāo)簽顯示在web頁(yè)面上

4.當(dāng)縮略圖切片都添加到web界面上以后,為每一份縮略圖切片添加注冊(cè)拖拽事件,使得縮略圖切片可以相互交換,在這個(gè)過(guò)程當(dāng)中,添加對(duì)縮略圖切片順序狀態(tài)的監(jiān)控,一旦完成拼圖,就直接展示完整的縮略圖,完成游戲

從以上對(duì)小游戲制作過(guò)程的分析來(lái)看,第4步是程序功能實(shí)現(xiàn)的重點(diǎn)和難點(diǎn),在以上的每個(gè)步驟中都有很多小細(xì)節(jié)需要注意和探討,下面我就詳細(xì)分析一下每個(gè)步驟的實(shí)現(xiàn)細(xì)節(jié),說(shuō)的不好的地方,歡迎大家留言指正。

3 開(kāi)發(fā)細(xì)節(jié)詳解 3.1 圖片內(nèi)容讀取和加載

在游戲開(kāi)發(fā)第1步中,我們將圖片拖拽到指定區(qū)域后,程序是怎樣得到圖片內(nèi)容信息的呢?fileReader對(duì)象又是怎樣將圖片信息轉(zhuǎn)化為base64字符串內(nèi)容的?Image對(duì)象拿到圖片的base64內(nèi)容之后,又是怎樣初始化加載的?帶著這些疑問(wèn),我們來(lái)研究一下實(shí)現(xiàn)項(xiàng)目中實(shí)現(xiàn)了第一步的關(guān)鍵代碼。

var droptarget = document.getElementById("droptarget"),
            output = document.getElementById("ul1"),
            thumbImg = document.getElementById("thumbimg");
            
 //此處省略相關(guān)代碼........
           
function handleEvent(event) {
                var info = "",
                    reader = new FileReader(),
                    files, i, len;

                EventUtil.preventDefault(event);
                localStorage.clear();

                if (event.type == "drop") {
                    files = event.dataTransfer.files;
                    len = files.length;
                    if (!/image/.test(files[0].type)) {
                        alert("請(qǐng)上傳圖片類(lèi)型的文件");
                    }
                    if (len > 1) {
                        alert("上傳圖片數(shù)量不能大于1");
                    }

                    var canvas = document.createElement("canvas");
                    var context = canvas.getContext("2d");
                    var img = new Image(),          //原圖
                        thumbimg = new Image();     //等比縮放后的縮略圖

                    reader.readAsDataURL(files[0]);
                    reader.onload = function (e) {
                        img.src = e.target.result;
                    }

                    //圖片對(duì)象加載完畢后,對(duì)圖片進(jìn)行等比縮放處理??s放后最大寬度為三百像素
                    img.onload = function () {
                        var targetWidth, targetHeight;
                        targetWidth = this.width > 300 ? 300 : this.width;
                        targetHeight = targetWidth / this.width * this.height;
                        canvas.width = targetWidth;
                        canvas.height = targetHeight;
                        context.clearRect(0, 0, targetWidth, targetHeight);
                        context.drawImage(img, 0, 0, targetWidth, targetHeight);
                        var tmpSrc = canvas.toDataURL("image/jpeg");
                        //在本地存儲(chǔ)完整的縮略圖源
                        localStorage.setItem("FullImage", tmpSrc);
                        thumbimg.src = tmpSrc;
                    }
                    
        //此處省略相關(guān)代碼......
        
         EventUtil.addHandler(droptarget, "dragenter", handleEvent);
         EventUtil.addHandler(droptarget, "dragover", handleEvent);
         EventUtil.addHandler(droptarget, "drop", handleEvent);            
}

這段代碼的思路就是首先獲得拖拽區(qū)域目標(biāo)對(duì)象droptarget,為droptarget注冊(cè)拖拽監(jiān)聽(tīng)事件。代碼中用到的EventUtil是我封裝的一個(gè)對(duì)元素添加事件、事件對(duì)象的兼容處理等常用功能的簡(jiǎn)單對(duì)象,下面是其添加注冊(cè)事件的簡(jiǎn)單簡(jiǎn)單代碼,其中還有很多其他的封裝,讀者可自行查閱,功能比較簡(jiǎn)單。

var EventUtil = {

    addHandler: function(element, type, handler){
        if (element.addEventListener){
            element.addEventListener(type, handler, false);
        } else if (element.attachEvent){
            element.attachEvent("on" + type, handler);
        } else {
            element["on" + type] = handler;
        }
    },
    
    //此處省略代......
 }   

當(dāng)用戶(hù)將圖片文件拖放到區(qū)域目標(biāo)對(duì)象droptarget時(shí),droptarget的事件對(duì)象通過(guò)event.dataTransfer.files獲取到文件信息,對(duì)文件進(jìn)行過(guò)濾(限制只能為圖片內(nèi)容,并且最多只能有一張圖片)。拿到文件內(nèi)容以后,使用FileReader對(duì)象reader讀取文件內(nèi)容,使用其readAsDataURL方法讀取到圖片的base64內(nèi)容,賦值給Image對(duì)象img的src屬性,就可以等到img對(duì)象初始化加載完畢,使canvas對(duì)img進(jìn)行下一步的處理了。這里有一個(gè)重點(diǎn)的地方需要說(shuō)明:一定要等img加載完成后,再使用canvas進(jìn)行下一步的處理,不然可能會(huì)出現(xiàn)圖片損壞的情況。原因是:當(dāng)img的src屬性讀取圖片文件的base64內(nèi)容時(shí),可能還沒(méi)有將內(nèi)容加載到內(nèi)存中時(shí),canvas就開(kāi)始處理圖片(此時(shí)的圖片是不完整的)。所以我們可以看到canvas對(duì)圖片的處理是放在img.onload方法中進(jìn)行的,程序后邊還會(huì)有這種情況,之后就不再贅述了。

3.2 圖片等比縮放和本地存儲(chǔ)

在第一步中我們完成了對(duì)拖拽文件的內(nèi)容讀取,并將其成功加載到了Image對(duì)象img中。接下來(lái)我們使用canvas對(duì)圖片進(jìn)行等比縮放,對(duì)圖片進(jìn)行等比縮放,我們采取的策略是限制圖片的最大寬度為300像素,我們?cè)賮?lái)看一下這部分代碼吧:

 img.onload = function () {
                        var targetWidth, targetHeight;
                        targetWidth = this.width > 300 ? 300 : this.width;
                        targetHeight = targetWidth / this.width * this.height;
                        canvas.width = targetWidth;
                        canvas.height = targetHeight;
                        context.clearRect(0, 0, targetWidth, targetHeight);
                        context.drawImage(img, 0, 0, targetWidth, targetHeight);
                        var tmpSrc = canvas.toDataURL("image/jpeg");
                        //在本地存儲(chǔ)完整的縮略圖源
                        localStorage.setItem("FullImage", tmpSrc);
                        thumbimg.src = tmpSrc;
                    }

確定了縮放后的寬度targetWidth和高度targetHeight之后,我們使用canvas的drawImage方法對(duì)圖像進(jìn)行壓縮,在這之前我們最好先使用畫(huà)布的clearRect對(duì)畫(huà)布進(jìn)行一次清理。對(duì)圖片等比縮放以后,使用canvas的toDataURL方法,獲取到縮放圖的base64內(nèi)容,賦給新的縮放圖Image對(duì)象thumbimg的src屬性,待縮放圖加載完畢,進(jìn)行下一步的切割處理??s放圖的base64內(nèi)容使用localStorage存儲(chǔ),鍵名為"FullImage"。瀏覽器的本地存儲(chǔ)localStorage是硬存儲(chǔ),在瀏覽器刷新之后內(nèi)容不會(huì)丟失,這樣我們就可以在游戲過(guò)程中保持?jǐn)?shù)據(jù)狀態(tài),這點(diǎn)稍后再詳細(xì)講解,我們需要知道的是localStorage是有大小限制的,最大為5M。這也是為什么我們先對(duì)圖片進(jìn)行壓縮,減少存儲(chǔ)數(shù)據(jù)大小,保存縮放圖base64內(nèi)容的原因。關(guān)于開(kāi)發(fā)過(guò)程中存儲(chǔ)哪些內(nèi)容,下一小節(jié)會(huì)配有圖例詳細(xì)說(shuō)明。

3.3 縮略圖切割

生成縮略圖之后要做的工作就是對(duì)縮略圖進(jìn)行切割了,同樣的也是使用canvas的drawImage方法,而且相應(yīng)的處理必須放在縮略圖加載完成之后(即thumbimg.onload)進(jìn)行處理,原因前面我們已經(jīng)說(shuō)過(guò)。下面我們?cè)賮?lái)詳細(xì)分析一下源代碼吧:

thumbimg.onload = function () {
                        //每一個(gè)切片的寬高[切割成3*4格式]
                        var sliceWidth, sliceHeight, sliceBase64, n = 0, outputElement = "",
                            sliceWidth = this.width / 3,
                            sliceHeight = this.height / 4,
                            sliceElements = [];

                        canvas.width = sliceWidth;
                        canvas.height = sliceHeight;

                        for (var j = 0; j < 4; j++) {
                            for (var i = 0; i < 3; i++) {
                                context.clearRect(0, 0, sliceWidth, sliceHeight);
                                context.drawImage(thumbimg, sliceWidth * i, sliceHeight * j, sliceWidth, sliceHeight, 0, 0, sliceWidth, sliceHeight);
                                sliceBase64 = canvas.toDataURL("image/jpeg");
                                localStorage.setItem("slice" + n, sliceBase64);
                                //為了防止圖片三像素問(wèn)題發(fā)生,請(qǐng)為圖片屬性添加 display:block
                                newElement = "
  • "; //根據(jù)隨機(jī)數(shù)打亂圖片順序 (Math.random() > 0.5) ? sliceElements.push(newElement) : sliceElements.unshift(newElement); n++; } } //拼接元素 for (var k = 0, len = sliceElements.length; k < len; k++) { outputElement += sliceElements[k]; } localStorage.setItem("imageWidth", this.width + 18); localStorage.setItem("imageHeight", this.height + 18); output.style.width = this.width + 18 + "px"; output.style.height = this.height + 18 + "px"; (output.innerHTML = outputElement) && beginGamesInit(); droptarget.remove(); }

    上面的代碼對(duì)于大家來(lái)說(shuō)不難理解,就是將縮略圖分割成12個(gè)切片,這里我給大家解釋一下幾個(gè)容易困惑的地方:

    1.為什么我們?cè)偾懈顖D片的時(shí)候,代碼如下,先從列開(kāi)始循環(huán)?

     for (var j = 0; j < 4; j++) {
        for (var i = 0; i < 3; i++) {
            //此處省略邏輯代碼
        }
      }

    這個(gè)問(wèn)題大家仔細(xì)想一想就明白了,我們將圖片進(jìn)行切割的時(shí)候,要記錄下來(lái)每一個(gè)圖片切片的原有順序。在程序中我們使用 n 來(lái)表示圖片切片的原有順序,而且這個(gè)n記錄在了每一個(gè)圖片切片的元素的name屬性中。在后續(xù)的游戲過(guò)程中我們可以使用元素的getAttribute("name")方法取出 n 的值,來(lái)判斷圖片切片是否都被拖動(dòng)到了正確的位置,以此來(lái)判斷游戲是否結(jié)束,現(xiàn)在講起這個(gè)問(wèn)題可能還會(huì)有些迷惑,我們后邊還會(huì)再詳細(xì)探討,我給出一張圖幫助大家理解圖片切片位置序號(hào)信息n:

    序號(hào)n從零開(kāi)始是為了和javascript中的getElementsByTagName()選擇的子元素坐標(biāo)保持一致。

    2 我們第3步實(shí)現(xiàn)的目的不僅是將縮略圖切割成小切片,還要將這些圖片切片打亂順序,代碼程序中這一點(diǎn)是怎樣實(shí)現(xiàn)的?
    閱讀代碼程序我們知道,我們每生成一個(gè)切片,就會(huì)構(gòu)造一個(gè)元素節(jié)點(diǎn): newElement = "

  • "; 。我們?cè)谑窃谕獠肯嚷暶髁艘粋€(gè)放新節(jié)點(diǎn)的數(shù)組sliceElements,我們每生成一個(gè)新的元素節(jié)點(diǎn),就會(huì)把它放到sliceElements數(shù)組中,但是我們向sliceElements頭部還是尾部添加這個(gè)新節(jié)點(diǎn)則是隨機(jī)的,代碼是這樣的:

    (Math.random() > 0.5) ? sliceElements.push(newElement) : sliceElements.unshift(newElement);

    我們知道Math.random()生成一個(gè)[0, 1)之間的數(shù),所以再canvas將縮略圖裁切成切片以后,根據(jù)這些切片生成的web節(jié)點(diǎn)順序是打亂的。打亂順序以后重新組裝節(jié)點(diǎn):

    //拼接元素
    for (var k = 0, len = sliceElements.length; k < len; k++) {
        outputElement += sliceElements[k];
    }

    然后再將節(jié)點(diǎn)添加到web頁(yè)面中,也就自然而然出現(xiàn)了圖片切片被打亂的樣子了。

    3.我們根據(jù)縮略圖切片生成的DOM節(jié)點(diǎn)是動(dòng)態(tài)添加的元素,怎樣給這樣動(dòng)態(tài)元素綁定事件呢?我們的項(xiàng)目中為每個(gè)縮略圖切片DOM節(jié)點(diǎn)綁定的事件是“拖動(dòng)交換”,和其他節(jié)點(diǎn)都有關(guān)系,我們要保證所有的節(jié)點(diǎn)都加載后再對(duì)事件進(jìn)行綁定,我們又是怎樣做到的呢?

    下面的一行代碼,雖然簡(jiǎn)單,但是用的非常巧妙:

    (output.innerHTML = outputElement) && beginGamesInit();

    有開(kāi)發(fā)經(jīng)驗(yàn)的同學(xué)都知道 && 和 || 是短路運(yùn)算符,代碼中的含義是:只有當(dāng)切片元素節(jié)點(diǎn)都添加到
    WEB頁(yè)面之后,才會(huì)初始化為這些節(jié)點(diǎn)綁定事件。

    3.4 本地信息存儲(chǔ)

    代碼中多次用到了本地存儲(chǔ),下面我們來(lái)詳細(xì)解釋一下本游戲開(kāi)發(fā)過(guò)程中都有哪些信息需要存儲(chǔ),為什么要存儲(chǔ)?下面是我給出的需要存儲(chǔ)的信息圖示例(從瀏覽器控制臺(tái)獲?。?/p>

    瀏覽器本地存儲(chǔ)localStorage使用key:value形式存儲(chǔ),從圖中我們看到我們本次存儲(chǔ)的內(nèi)容有:

    FullImage:圖片縮略圖base64編碼。

    imageWidth:拖拽區(qū)域圖片的寬度。

    imageHeight:拖拽區(qū)域圖片的高度。

    slice*:每一個(gè)縮略圖切片的base64內(nèi)容。

    nodePos:保存的是當(dāng)前縮略圖的位置坐標(biāo)信息。

    保存FullImage縮略圖的信息是當(dāng)游戲結(jié)束后顯示源縮略圖時(shí),根據(jù)FullImage中的內(nèi)容展示圖片。而imageWidth,imageHeight,slice*,nodePos是為了防止瀏覽器刷新導(dǎo)致數(shù)據(jù)丟失所做的存儲(chǔ),當(dāng)刷新頁(yè)面的時(shí)候,瀏覽器會(huì)根據(jù)本地存儲(chǔ)的數(shù)據(jù)加載沒(méi)有完成的游戲內(nèi)容。其中nodePos是在為縮略圖切片發(fā)生拖動(dòng)時(shí)存入本地存儲(chǔ)的,并且它隨著切片位置的變化而變化,也就是它追蹤著游戲的狀態(tài),我們?cè)诮酉聛?lái)的代碼功能展示中會(huì)再次說(shuō)到它。

    3.5 拖拽事件注冊(cè)和監(jiān)控

    接下來(lái)我們要做的事才是游戲中最重要的部分,還是先來(lái)分析一下代碼,首先是事件注冊(cè)前的初始化工作:

    //游戲開(kāi)始初始化
    function beginGamesInit() {
        aLi = output.getElementsByTagName("li");
        for (var i = 0; i < aLi.length; i++) {
            var t = aLi[i].offsetTop;
            var l = aLi[i].offsetLeft;
            aLi[i].style.top = t + "px";
            aLi[i].style.left = l + "px";
            aPos[i] = {left: l, top: t};
            aLi[i].index = i;
            //將位置信息記錄下來(lái)
            nodePos.push(aLi[i].getAttribute("name"));
        }
        for (var i = 0; i < aLi.length; i++) {
            aLi[i].style.position = "absolute";
            aLi[i].style.margin = 0;
            setDrag(aLi[i]);
        }
    }

    可以看到這部分初始化綁定事件代碼所做的事情是:記錄每一個(gè)圖片切片對(duì)象的位置坐標(biāo)相關(guān)信息記錄到對(duì)象屬性中,并為每一個(gè)對(duì)象都注冊(cè)拖拽事件,對(duì)象的集合由aLi數(shù)組統(tǒng)一管理。這里值得一提的是圖片切片的位置信息index記錄的是切片現(xiàn)在所處的位置,而我們前邊所提到的圖片切片name屬性所保存的信息n則是圖片切片原本應(yīng)該所處的位置,在游戲還沒(méi)有結(jié)束之前,它們不一定相等。待所有的圖片切片name屬性所保存的值和其屬性index都相等時(shí),游戲才算結(jié)束(因?yàn)橛脩?hù)已經(jīng)正確完成了圖片的拼接),下面的代碼就是用來(lái)判斷游戲狀態(tài)是否結(jié)束的,看起來(lái)更直觀一些:

    //判斷游戲是否結(jié)束
    function gameIsEnd() {
        for (var i = 0, len = aLi.length; i < len; i++) {
            if (aLi[i].getAttribute("name") != aLi[i].index) {
                return false;
            }
        }
    
        //后續(xù)處理代碼省略......
    }

    下面我們還是詳細(xì)說(shuō)一說(shuō)拖拽交換代碼相關(guān)邏輯吧,拖拽交換的代碼如下圖所示:

    //拖拽
    function setDrag(obj) {
        obj.onmouseover = function () {
            obj.style.cursor = "move";
            console.log(obj.index);
        }
    
        obj.onmousedown = function (event) {
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
            obj.style.zIndex = minZindex++;
            //當(dāng)鼠標(biāo)按下時(shí)計(jì)算鼠標(biāo)與拖拽對(duì)象的距離
            disX = event.clientX + scrollLeft - obj.offsetLeft;
            disY = event.clientY + scrollTop - obj.offsetTop;
            document.onmousemove = function (event) {
                //當(dāng)鼠標(biāo)拖動(dòng)時(shí)計(jì)算div的位置
                var l = event.clientX - disX + scrollLeft;
                var t = event.clientY - disY + scrollTop;
                obj.style.left = l + "px";
                obj.style.top = t + "px";
    
                for (var i = 0; i < aLi.length; i++) {
                    aLi[i].className = "";
                }
                var oNear = findMin(obj);
                if (oNear) {
                    oNear.className = "active";
                }
            }
    
            document.onmouseup = function () {
                document.onmousemove = null;       //當(dāng)鼠標(biāo)彈起時(shí)移出移動(dòng)事件
                document.onmouseup = null;         //移出up事件,清空內(nèi)存
                //檢測(cè)是否普碰上,在交換位置
                var oNear = findMin(obj);
                if (oNear) {
                    oNear.className = "";
                    oNear.style.zIndex = minZindex++;
                    obj.style.zIndex = minZindex++;
    
                    startMove(oNear, aPos[obj.index]);
                    startMove(obj, aPos[oNear.index], function () {
                        gameIsEnd();
                    });
    
                    //交換index
                    var t = oNear.index;
                    oNear.index = obj.index;
                    obj.index = t;
    
                    //交換本次存儲(chǔ)中的位置信息
                    var tmp = nodePos[oNear.index];
                    nodePos[oNear.index] = nodePos[obj.index];
                    nodePos[obj.index] = tmp;
                    localStorage.setItem("nodePos", nodePos);
                } else {
                    startMove(obj, aPos[obj.index]);
                }
            }
            clearInterval(obj.timer);
    
            return false;//低版本出現(xiàn)禁止符號(hào)
        }
    }

    這段代碼所實(shí)現(xiàn)的功能是這樣子的:拖動(dòng)一個(gè)圖片切片,當(dāng)它與其它的圖片切片有碰撞重疊的時(shí)候,就和與其左上角距離最近的一個(gè)圖片切片交換位置,并交換其位置信息index,更新本地存儲(chǔ)信息中的nodePos。移動(dòng)完成之后判斷游戲是否結(jié)束,若沒(méi)有,則期待下一次用戶(hù)的拖拽交換。
    下面我來(lái)解釋一下這段代碼中比較難理解的幾個(gè)點(diǎn):

    1.圖片切片在被拖動(dòng)的過(guò)程中是怎樣判斷是否和其它圖片切片發(fā)生碰撞的?這就是典型的碰撞檢測(cè)問(wèn)題。
    程序中實(shí)現(xiàn)碰撞檢測(cè)的代碼是這樣的:

    //碰撞檢測(cè)
    function colTest(obj1, obj2) {
        var t1 = obj1.offsetTop;
        var r1 = obj1.offsetWidth + obj1.offsetLeft;
        var b1 = obj1.offsetHeight + obj1.offsetTop;
        var l1 = obj1.offsetLeft;
    
        var t2 = obj2.offsetTop;
        var r2 = obj2.offsetWidth + obj2.offsetLeft;
        var b2 = obj2.offsetHeight + obj2.offsetTop;
        var l2 = obj2.offsetLeft;
    
        `if (t1 > b2 || r1 < l2 || b1 < t2 || l1 > r2)` {
            return false;
        } else {
            return true;
        }
    }

    這段代碼看似信息量很少,其實(shí)也很好理解,判斷兩個(gè)圖片切片是否發(fā)生碰撞,只要將它們沒(méi)有發(fā)生碰撞的情形排除掉就可以了。這有點(diǎn)類(lèi)似與邏輯中的非是即否,兩個(gè)切片又確實(shí)只可能存在兩種情況:碰撞、不碰撞。圖中的這段代碼是判斷不碰撞的情況:if (t1 > b2 || r1 < l2 || b1 < t2 || l1 > r2),返回false, else 返回true。

    2.碰撞檢測(cè)完成了之后,圖片切片之間又是怎樣尋找左上角定點(diǎn)距離最近的元素呢?

    代碼是這個(gè)樣子的:

    //勾股定理求距離(左上角的距離)
    function getDis(obj1, obj2) {
        var a = obj1.offsetLeft - obj2.offsetLeft;
        var b = obj1.offsetTop - obj2.offsetTop;
        return Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
    }
    
    //找到距離最近的
    function findMin(obj) {
        var minDis = 999999999;
        var minIndex = -1;
        for (var i = 0; i < aLi.length; i++) {
            if (obj == aLi[i]) continue;
            if (colTest(obj, aLi[i])) {
                var dis = getDis(obj, aLi[i]);
                if (dis < minDis) {
                    minDis = dis;
                    minIndex = i;
                }
            }
        }
        if (minIndex == -1) {
            return null;
        } else {
            return aLi[minIndex];
        }
    }

    因?yàn)槎际蔷匦螀^(qū)塊,所以計(jì)算左上角的距離使用勾股定理,這點(diǎn)相信大家都能明白。查找距離最近的元素原理也很簡(jiǎn)單,就是遍歷所有已經(jīng)碰撞的元素,然后比較根據(jù)勾股定理計(jì)算出來(lái)的最小值,返回元素就可以了。代碼中也是使用了比較通用的方法,先聲明一個(gè)很大的值最為最小值,當(dāng)有碰撞元素比其小時(shí),再將更小的值最為最小值,遍歷完成后,返回最小值的元素就可以了。

    3.圖片區(qū)塊每次交換之后,是怎樣監(jiān)控判斷游戲是否已經(jīng)結(jié)束的呢?

    答案是回調(diào)函數(shù),圖片切片交換函數(shù)通過(guò)回調(diào)函數(shù)來(lái)判斷游戲是否已經(jīng)結(jié)束,游戲是否結(jié)束的判斷函數(shù)前面我們已經(jīng)說(shuō)過(guò)。圖片切片交換函數(shù)就是通過(guò)添加gameIsEnd作為回調(diào)函數(shù),這樣在每次圖片切片移動(dòng)交換完成之后,就判斷一下游戲是否結(jié)束。圖片切片的交換函數(shù)還是比較復(fù)雜的,有興趣的同學(xué)可以研究一下,下面是其實(shí)現(xiàn)代碼,大家重點(diǎn)理解其中添加了回調(diào)函數(shù)監(jiān)控游戲是否結(jié)束就好了。

    //通過(guò)class獲取元素
    function getClass(cls){
        var ret = [];
        var els = document.getElementsByTagName("*");
        for (var i = 0; i < els.length; i++){
            //判斷els[i]中是否存在cls這個(gè)className;.indexOf("cls")判斷cls存在的下標(biāo),如果下標(biāo)>=0則存在;
            if(els[i].className === cls || els[i].className.indexOf("cls")>=0 || els[i].className.indexOf(" cls")>=0 || els[i].className.indexOf(" cls ")>0){
                ret.push(els[i]);
            }
        }
        return ret;
    }
    function getStyle(obj,attr){//解決JS兼容問(wèn)題獲取正確的屬性值
        return obj.currentStyle?obj.currentStyle[attr]:getComputedStyle(obj,false)[attr];
    }
    
    function gameEnd() {
        alert("游戲結(jié)束!");
    }
    
    function startMove(obj,json,fun){
        clearInterval(obj.timer);
        obj.timer = setInterval(function(){
            var isStop = true;
            for(var attr in json){
                var iCur = 0;
                //判斷運(yùn)動(dòng)的是不是透明度值
                if(attr=="opacity"){
                    iCur = parseInt(parseFloat(getStyle(obj,attr))*100);
                }else{
                    iCur = parseInt(getStyle(obj,attr));
                }
                var ispeed = (json[attr]-iCur)/8;
                //運(yùn)動(dòng)速度如果大于0則向下取整,如果小于0想上取整;
                ispeed = ispeed>0?Math.ceil(ispeed):Math.floor(ispeed);
                //判斷所有運(yùn)動(dòng)是否全部完成
                if(iCur!=json[attr]){
                    isStop = false;
                }
                //運(yùn)動(dòng)開(kāi)始
                if(attr=="opacity"){
                    obj.style.filter = "alpha:(opacity:"+(json[attr]+ispeed)+")";
                    obj.style.opacity = (json[attr]+ispeed)/100;
                }else{
                    obj.style[attr] = iCur+ispeed+"px";
                }
            }
            //判斷是否全部完成
            if(isStop){
                clearInterval(obj.timer);
                if(fun){
                    fun();
                }
            }
        },30);
    }
    4 補(bǔ)充和總結(jié) 4.1 游戲中值得完善的功能

    我認(rèn)為該游戲中值得優(yōu)化的地方有兩個(gè):

    1.為拼圖小游戲添加縮略圖,因?yàn)榭s略圖有利于為玩游戲的用戶(hù)提供思路。我們又在瀏覽器本地存儲(chǔ)中保存了縮略圖的base64內(nèi)容,所以實(shí)現(xiàn)起來(lái)也很容易。

    2.緩存有的時(shí)候也讓人很痛苦,就比如說(shuō)在游戲中有些用戶(hù)就想要重新開(kāi)始,而我們的小游戲只有在游戲完成之后才清空緩存,刷新頁(yè)面,游戲才能夠重新開(kāi)始。這給用戶(hù)的體驗(yàn)很不好,我們可以加一個(gè)重置游戲按鈕,清空緩存并優(yōu)化游戲結(jié)束后的一些邏輯。

    這些功能感興趣的小伙伴可以嘗試一下。

    4.2 總結(jié)

    雖然花了周末幾乎一天的時(shí)間寫(xiě)了幾百行代碼才實(shí)現(xiàn)了一個(gè)功能不是很強(qiáng)大的小游戲,但是在這個(gè)過(guò)程中查閱了很多資料,總算把自己喜歡做的一件事情給完成了,還是很開(kāi)心的。寫(xiě)這篇博客的目的是為了和更多有相同興趣愛(ài)好的小伙伴分享一下自己的見(jiàn)解,筆者水平有限,希望大家對(duì)代碼有好的建議或者有更好的思路留言相告。感謝大家!

    文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

    轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/97584.html

    相關(guān)文章

    • 打造專(zhuān)屬自己的html5拼圖游戲

      摘要:最近公司剛好有個(gè)活動(dòng)是要做一版的拼圖小游戲,于是自己心血來(lái)潮,自己先實(shí)現(xiàn)了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小游戲,必須是更炫酷,更好玩來(lái)吧,大家一起加油。。。 最近公司剛好有個(gè)活動(dòng)是要做一版 html5的拼圖小游戲,于是自己心血來(lái)潮,自己先實(shí)現(xiàn)了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小...

      codeKK 評(píng)論0 收藏0
    • 打造專(zhuān)屬自己的html5拼圖游戲

      摘要:最近公司剛好有個(gè)活動(dòng)是要做一版的拼圖小游戲,于是自己心血來(lái)潮,自己先實(shí)現(xiàn)了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小游戲,必須是更炫酷,更好玩來(lái)吧,大家一起加油。。。 最近公司剛好有個(gè)活動(dòng)是要做一版 html5的拼圖小游戲,于是自己心血來(lái)潮,自己先實(shí)現(xiàn)了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小...

      JowayYoung 評(píng)論0 收藏0
    • 打造專(zhuān)屬自己的html5拼圖游戲

      摘要:最近公司剛好有個(gè)活動(dòng)是要做一版的拼圖小游戲,于是自己心血來(lái)潮,自己先實(shí)現(xiàn)了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小游戲,必須是更炫酷,更好玩來(lái)吧,大家一起加油。。。 最近公司剛好有個(gè)活動(dòng)是要做一版 html5的拼圖小游戲,于是自己心血來(lái)潮,自己先實(shí)現(xiàn)了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小...

      趙春朋 評(píng)論0 收藏0
    • [deviceone開(kāi)發(fā)]-拼圖游戲

      摘要:一簡(jiǎn)介九宮格小游戲,可從本地圖庫(kù)載入一張圖片,填充到個(gè),另涉及計(jì)時(shí)圖庫(kù)控件。每個(gè)格子都是相同的控件,動(dòng)態(tài)添加到首頁(yè)中的,在初始化后,響應(yīng)事件,之后通過(guò)多次消息傳遞,來(lái)完成整個(gè)拼圖過(guò)程。二效果圖三相關(guān)下載四相關(guān)討論五更多案例六關(guān)于 一、簡(jiǎn)介 九宮格小游戲,可從本地圖庫(kù)載入一張圖片,填充到9個(gè)ImageView,另涉及Timer計(jì)時(shí)、圖庫(kù)控件。 每個(gè)格子都是相同的控件,動(dòng)態(tài)添加到首頁(yè)中的,...

      YacaToy 評(píng)論0 收藏0

    發(fā)表評(píng)論

    0條評(píng)論

    最新活動(dòng)
    閱讀需要支付1元查看
    <