1. 程式人生 > >對一個H5射擊小遊戲的演算法理解

對一個H5射擊小遊戲的演算法理解

導言

這是一個初學者通過一個h5小遊戲教程中出現的一些演算法包括(物件池,髒矩形繪製,四叉樹的2D碰撞檢測)進行一個簡單介紹,用於自我理解的目的。

遊戲的DEMO,教程中的縱版改為橫版。

教程中分為5個部分,包括從背景的製作到最後規則判定。其中關於玩家飛機和子彈判定這兩個部分用到了物件池,髒矩形繪製,四叉樹的2D碰撞檢測演算法。比較有意思。

對於遊戲來說
物件池用於重複利用物件,這裡是子彈,而不是物件創造,使用,消失,再創造的迴圈。髒矩形繪製用於判定子彈是否可以再利用。四叉樹用來幫助子彈碰撞判定。

再介紹物件池之前,先想一下,在射擊遊戲中,通常會在短時間內,產生,執行,移除大量物件(子彈)。這通常需要大量的運算,可能會導致遊戲的卡頓。原因是因為後臺程式垃圾收集器(garbage collector)會讓系統資源清除已用記憶體(used memory)。

簡單介紹一下垃圾收集器,垃圾回收是一個自動資源分配程式,用於清除不再需要的已用記憶體空間。
比如下例:

for (int i = 1; i < 10; i++) {
  System.out.println(i);
} 

當這個for loop 執行時,會產生變數i ,記憶體會儲存變數i的所有資料。當loop結束時,變數i就會被清除。
這個例子當中,變數i會有足夠多的記憶體儲存。但是當變數i所需記憶體在巨量增加時,而可用記憶體又不夠時,垃圾回收機制會清除其他記憶體來保證變數i所需的記憶體。這會導致短時間內的卡頓。

因此,物件池的目的就是減少變數i所需的記憶體,從而增加遊戲流暢度。本質上就是對舊物件的重複利用,而不是不停的創造,刪除。

假象一下,一盒紙牌(紙牌盒代表記憶體容量),每次需要一個新紙牌(新物件,新子彈),你抽出一張使用,然後扔到垃圾桶。最終,紙牌會用完,或者垃圾桶滿了需要倒掉(垃圾收集)。
在一個物件池中,紙牌是空白的,當需要的時候,直接寫上需要的資訊,用完後擦乾淨,放回紙牌盒。這樣就可以迴圈。

一個標準的物件池包含兩個部分,初始化容器,以及函式getanimate )。

初始化容器,就是對設定一個包含n個物件的陣列,並且例項化為空值。
下圖設定了最大值maxSize為引數,並把子彈bullet的引數設定空值。也就是說子彈已經被載入了,只是引數為0而已(x和y的位置,和速度)。

function
ObjectPool(maxSize) {
var size = maxSize; var ObjectPool = []; this.init = function() { //例項化並空值每一個子彈 for (var i = 0; i < size; i++) { var bullet = new Bullet(); bullet.init(//); pool[i] = bullet; } };

函式部分由getanimate組成。
get 用於發射子彈。函式用於把檢測最後一個物件是否正在被使用(alive or dead),如果沒有被使用(dead),函式會初始化(這裡指的是賦值子彈的預設引數)並把它排到第一位(發射)。如果正在被使用(通常情況下不會,不然也可以調整最大值),可以再建立一個物件。

    this.get = function(x, y, speed) {
        if(!pool[size - 1].alive) {
            pool[size - 1].spawn(x, y, speed); 
            //賦予子彈的預設引數,也就是發射子彈
            pool.unshift(pool.pop());
        }
    };

animate 重複利用已經被創造物件。檢測物件是否被能被使用,如果為true,清除引數,並放置到容器中。其中檢測物件的方式依據專案而異。

    this.animate = function() {
        for (var i = 0; i < size; i++) {
        //通過alive和draw的返回值檢測
            if (pool[i].alive) {
                if (pool[i].draw()) {
                    pool[i].clear();
                    pool.push((pool.splice(i,1))[0]);
                }
            }
            else
                break;
        }
    };

本例中,用了髒矩形技術(Dirty rectangles)檢測。
首先alive的判定方式是看物件是否被賦值使用。draw 用的是髒矩形技術檢測。
髒矩形技術用於檢測畫面中變化的部分,這樣就不需要每一幀重繪全部影象。這裡就是判定目標物件。

    this.draw = function() {
        this.context.clearRect(this.x, this.y, this.width, this.height);
        this.y -= this.speed;
        //檢測目標有沒有移出螢幕
        if (this.y <= 0 - this.height) {
            return true;
        }
        else {
            this.context.drawImage(imageRepository.bullet, this.x, this.y);
        }
    };

上例當中,draw主要用於檢測目標是否移動到螢幕外,如果為true,。
之前的animate 函式中的意義就是,檢測子彈有沒有飛出去,有沒有飛到螢幕外,如果兩個都為true,則把飛出去的子彈移到容器中。

四叉數 QuadTree

四叉樹用於物體碰撞檢測,當物體只有兩個時,只需要直接計算就可以得出碰撞,當物體數量過多時,比如100個,那麼計算碰撞壓力就很大,因為每一個物體都要跟其餘99個計算一次碰撞。
四叉樹通過把螢幕劃分4個區域,只有區域內或者區域線上的物體才會有可能發射碰撞。四叉樹返回的是有可能發生碰撞的物體,只需要計算這些物體就可以。

四叉樹例子

上圖包含了4個區域,其中坐上區域又被劃分為4個子區域,因為物件數量過過多。
所以需要設定的是需要多少層區域劃分,每一層所承載的最大物件。

回到遊戲來說,四叉樹檢測可能過於小題大做,其他也有更方便的方法用於子彈檢測。主要有利於理解四叉樹原理。