為了實現一個基於 HTML5 的場景小遊戲,我採用了 HT for Web 來實現,短短200行代碼,我就能實現用「第一人稱」來操作前進後退上下左右,並且實現了碰撞檢測。
先來看下實現的效果: http://www.hightopo.com/guide/guide/core/3d/examples/example_collision.html
或者 GIF 視頻中出現的幀的問題是我屏幕錄製器的問題,真正操作的時候不會有,建議用上面的鏈接自己操作玩玩,滑鼠或者觸屏都可以,不過我覺得最方便的還是操作鍵盤 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();
這幾個都是自定義的函數:
HT 中封裝了一個組件,ht.Shape(以下簡稱Shape),能夠根據描點來自由描繪圖形,可以通過 shape.setPoints(pointsArray) 將所有的點添加進數組中,並且設置到shape中,然後通過 setSegments() 設置線段數組信息,也就是用什麼樣的方式來連接兩點,在Shape手冊中有著重描寫,感興趣的可以參考 形狀手冊 - HT for Web。抽其中的一個描繪點的函數來看看:
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 旋轉。詳情參考 3D 手冊 - HT for Web。
因為 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 // ht.Default.toBoundaries 將不連續曲線轉化成 Graph3dView#setBoundaries(bs) 需要的參數格式 boundaries = boundaries.concat(ht.Default.toBoundaries(data.getPoints(), data.getSegments()));
}); 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(); } });
推薦閱讀: