基於HTML5及WebGL開發的2D3D第一人稱漫遊進行碰撞檢測

hightopo發表於2019-02-16

為了實現一個基於HTML5的場景小遊戲,我採用了HT for Web來實現,短短200行程式碼,我就能實現用“第一人稱”來操作前進後退上下左右,並且實現了碰撞檢測。
先來看下實現的效果: http://hightopo.com/guide/gui…
或者http://v.youku.com/v_show/id_…視訊中出現的幀的問題是我螢幕錄製器的問題,真正操作的時候不會有,建議用上面的連結自己操作玩玩,滑鼠或者觸屏都可以,不過我覺得最方便的還是操作鍵盤wsad控制上下左右。圖片描述
我的想法是先把場景佈局好,程式碼如下:

createHT([100, -20, 100], `#E74C3C`);                  
createHT([-100, -20, 100], `#1ABC9C`);                    
createHT([100, -20, -100], `#3498DB`);                   
createHT([-100, -20, -100], `#9B59B6`);   
createCurve([0, -20, 0]);
createCircle();

這幾個都是自定義的函式,createHT為描繪HT形狀的圖,場景中有四個,所以呼叫了四次;createCurve是描繪場景中間的黃色的曲線;createCircle是描繪最外層的圓,因為不是全包的圓,所以也是描點畫的。
HT中封裝了一個元件,ht.Shape(以下簡稱Shape),能夠根據描點來自由描繪圖形,可以通過shape.setPoints(pointsArray)將所有的點新增進陣列中,並且設定到shape中,然後通過setSegments()設定線段陣列資訊,也就是用什麼樣的方式來連線兩點,在Shape手冊中有著重描寫,感興趣的可以參考HT for Web Shape 手冊。抽其中的一個描繪點的函式來看看:

function createHT(p3, color){
    shape = new ht.Shape();                
    shape.s({
        `shape.background`: null,
        `shape.border.width`: 10,
        `shape.border.color`: color,
        `all.color`: color
    });
    shape.setTall(40);
    shape.setThickness(5);
    shape.setPoints([                    
        // draw H
        {x: 20, y: 0},
        {x: 20, y: 100},
        {x: 20, y: 50},
        {x: 80, y: 50},
        {x: 80, y: 0},
        {x: 80, y: 100},

        // draw T
        {x: 120, y: 0},
        {x: 180, y: 0},
        {x: 150, y: 0},
        {x: 150, y: 100}                    
    ]);                                
    shape.setSegments([
        // draw H
        1, // moveTo
        2, // lineTo
        1, // moveTo
        2, // lineTo
        1, // moveTo
        2, // lineTo

        // draw T
        1, // moveTo
        2, // lineTo
        1, // moveTo
        2 // lineTo
    ]);                
    shape.p3(p3);
    dataModel.add(shape); 
    return shape;
}

因為“HT”這個字眼要描繪的點比較多,所以程式碼看起來有點大,如果你看到如何描繪一個不完全的圓用20行程式碼來完成,而且包括樣式,還是會驚訝的:

shape = new ht.Shape();                
shape.s({
    `shape.background`: null,
    `shape.border.width`: 10,
    `shape.border.color`: `#D26911`,
    `all.color`: `#D26911`
});
shape.setTall(40);
shape.p3(0, -20, 0);
shape.setThickness(10);
                
var r = 300;
for(var i=0; i<36; i++){
    var angle = Math.PI * 2 * i / 36;
    shape.addPoint({
        x: r * Math.cos(angle),
        y: r * Math.sin(angle)
    });
}
                
dataModel.add(shape); 
return shape;

場景設定完畢,接下來要將在3d中“我”處於的位置在2d中也顯示出來。首先我得先設定“我”是“第一人稱漫遊模式”,直接將g3d.setFirstPersonMode(true)即可。第一人稱漫遊模式本質是控制eye和center,如果沒有設定第一人稱漫遊模式,那麼滑鼠或者觸控板拖拽會繞著center旋轉。詳情參考HT for Web 3D手冊
因為HT 3D中封裝了兩個方法getEye和getCenter,這兩個方法分別是獲取camera的位置和目標中心點的位置,前者按照想象來說就比方你頭上有個攝像機,你走到哪裡它的中心點就拍攝到哪裡,可以很方便的記錄你的位置;後者就相當於你看出去的位置,但是這裡跟我們人不太一樣,因為人是可以廣度看到大範圍的,但是這個center相當於你眼球和都不能轉動,是正前方的某一點的位置就是你的視線聚焦位置。
瞭解了getEye和getCenter後我們就可以獲取當前位置和視線位置了:

g2d.addTopPainter(function(g){
    var eye = g3d.getEye(),
    center = g3d.getCenter();
                        
    g.fillStyle = `red`;
    g.strokeStyle = `black`;
    g.lineWidth = 1;
    g.beginPath();                    
    g.arc(eye[0], eye[2], 12, 0, Math.PI * 2, true); //繪製圓,而且還能實時獲取3d中“我”的位置
    g.fill();
    g.stroke();  
                    
    g.strokeStyle = `black`;
    g.lineWidth = 2;
    g.beginPath(); 
    g.moveTo(eye[0], eye[2]);
    g.lineTo(center[0], center[2]); //繪製線,能實時更改“我”和“我的視線位置” 之間的線段   
    g.stroke(); 
});

但是在程式碼中我們發現,這個方法只被繪製了一次,如果不一直重繪,那麼2d介面的“我”的位置和移動也是不會變的,所以我們又監聽了3d中屬性的變化:

g3d.mp(function(e){//根據3d上的“我”的位置和視線來實時更新2d介面
    if(e.property === `eye` || e.property === `center`){ //如果e屬性變化為get/setEye,get/setCenter,那麼重繪2d介面
        g2d.redraw();
    } 
});

在2D中,我可以編輯圖元,移動它的點,變化某個圖元的大小,等等功能,只要變化了圖元,那麼我的碰撞測試就得更新:

function updateBoundaries(){
    boundaries = [];
    dataModel.each(function(data){//HT curve circle              
        boundaries = boundaries.concat(ht.Default.toBoundaries(data.getPoints(), data.getSegments()));
        // ht.Default.toBoundaries將不連續曲線轉化成Graph3dView#setBoundaries(bs)需要的引數格式
    }); 
    g3d.setBoundaries(boundaries);//setBoundary()可指定碰撞邊界
}

那麼我們好奇的點在於,如何在拖拽圖元改變大小的時候還能保持碰撞檢測呢?
HT中有一個對於屬性變化的監聽事件addDataPropertyChangeListener(),可簡寫為md(),在我們拖拽圖元的時候,繪製這個圖元的基礎points就會被改變,所以我們只要監聽points有沒有被改變就行了,如何使用這個事件可以參考HT for Web 資料模型手冊

dataModel.md(function(e){//data屬性變化事件    
    if(e.property === `points`){//如果data屬性變化為getPoints/setPoints,那麼更新邊界
        updateBoundaries();
    }
});

相關文章