[軍令CoS]技術-戰場建立與移動
以陣列生成戰場與並以滑鼠拖曳縮放
獨立開發遊戲[軍令Command of Strategy]技術文章,點連結了解遊戲企劃
本篇將介紹如何透過Javascript的二維陣列,事先定義戰場的形狀,並由迴圈搭配非同步函式載入,並由滑鼠事件拖曳、縮放整個場地。
接下來對本實驗lab02-MoveField.html做解說,建議點開連結對照參考,還未了解如何放入一格地磚,可參考前篇文章[軍令CoS]技術-放入一格地磚。
[陣列地圖]
var map=[
[1,1,1,1,1],
[1,1,1,1,1],
[1,1,1,1,1]]
先簡單定義一個陣列map為3*5個元素的Array。
for(var j=0;j<map.length;j++){
for(var i=0;i<map[0].length;i++){
addTile(i,j)
}
}
在初始函式init()當中,以雙層迴圈逐次加入一個地磚構成整個戰場,每次呼叫addTile(i,j)
函式時傳入i,i引數,作為設定地磚位置的依據。
var addTile=function(i,j){
new MTLLoader()
.load('./tiles/soil.mtl', function ( materials ) {
new OBJLoader()
.setMaterials( materials )
.load('./tiles/soil.obj', function ( tile ) {
tile.position.set(i,0,j)
tile.scale.set(.25,.25,.25)
scene.add(tile)
})
})
}
addTile()函式的主要結構在上一篇已經說明,其中 用於設定地磚位置,而 會將地磚縮小為原本的1/4,但為甚麼平白無故要縮小1/4呢?
這要從MagicaVoxel說起,MV當中的一個最小立方的邊長,在Three.js中只有0.1個長度單位,一個地磚的模型長寬為40格,進到Three.js後就是4個長度單位,接著為了要讓遊戲的一個長度單位(一格),等同於Three.js的一個單位,所以才要將地磚縮小1/4,這樣可以省去很多轉換上的麻煩。
[架構解說]
上述的程式架構從陣列到函式都不難理解,但仔細想想就會發現一些問題,1.為甚麼載入地磚的程式塊要獨立成函式,直接包在迴圈中不是比較明瞭嗎?2.為甚麼每生成一個地磚都要重讀一次檔案,不能只讀一次檔案之後同樣地磚的用複製的嗎?
這兩個問題的原因都跟載入模型的非同步函式,以及載入檔案的時間差有關,首先如果將載入地磚的程式塊包進迴圈,迴圈跑完i,j已經變成2,4了,但第一塊地磚都還沒讀完檔,等到他讀完檔開始載入並設定位置看到i,j=2,4,最後所有的地磚都擠到會圈最後設定的位置。
如果想要先載好素材,再用迴圈循序複製並擺放地磚,程序已經進入到迴圈,但素材還沒載入完成就會造成錯誤,而且之後要載入的模型也不只一種,不可能將迴圈放進載入某個模型的callback當中…,總之就是不太可行。
上述的幾個問題都是自己有嘗試過但不可行的,目前的寫法雖然要重複讀檔很多次,但在效果上以及程式架構的易讀上都還算可接受,由於是非同步函式因此所有地磚共用載入時間,而不是一塊載完再換下一塊,所以載入的總時間還算可接受,或許日後還會發現更簡潔的寫法也說不定。
[拖曳縮放]
說是移動戰場但其實真正在動的是相機
camera.position.set(0,20,-20)
camera.rotateY(-Math.PI)
camera.rotateX(-2*Math.PI/8)
相機初始位置調整為戰場後方斜上45度角,鏡頭的方向先從-z轉為+z方向,再往下轉俯角45度拍攝到戰場。
document.getElementById('cont').addEventListener('mousedown',onMouseDrag,false)document.getElementById('cont').addEventListener('mousemove',onMouseDrag,false)document.getElementById('cont').addEventListener('wheel',onMouseDrag,false)
在初始函式init()當中,捕捉三種滑鼠動作事件,統一丟給onMouseDrag
函式處理。
function onMouseDrag(event){
// zoom
if(event.deltaY){
camera.position.y+=event.deltaY/200
camera.position.z-=event.deltaY/200
}
// drag
if(event.which==3){
camera.position.x+=event.movementX/50
camera.position.z+=event.movementY/50
}
}
兩個判斷分別處理滑鼠滾輪時鏡頭縮放,以及按住右鍵拖曳鏡頭箭後左右移動,用圖片能更好了解鏡頭的移動方式。
[動畫效果]
最後產生動畫效果的函示也有所調整了
function animate() {
renderer.render( scene, camera );
requestAnimationFrame( animate );
}
animate()
前篇所使用的setInterval()函式,也能產生動畫效果,只是會有較嚴重的不連續感,而在此使用的requestAnimationFrame()是目前普遍推薦的作法,至於詳細原理我也還沒深入了解 XD。