上一篇已經對繞非定軸轉動有所瞭解,這篇郭先生繼續說一說邏輯轉體遊戲的製作,這部分我們同樣會遇到一些小問題,首先是根據資料渲染陷阱和目標區域,然後是對可以轉動的判定,最後是獲勝的判定。
1. 根據資料渲染陷阱和目標區域
首先我們P一張底圖和陷阱圖,如下圖
就像這樣,然後就是根據資料渲染陷阱和目標區域了,首先陷阱的個數是固定的,而目標區域是隨小方塊的數量而定,先看資料
end: [[-1, -4], [-1, -5]],
trap: [[-1, -7], [-6, -2]],
這裡我們看一下Shader怎麼寫的
let texture1 = new THREE.TextureLoader().load('/static/images/base/luojizhuanti.png'); let texture2 = new THREE.TextureLoader().load('/static/images/base/stack.png'); let trapArray = []; let targetArray = new Array(7).fill('').map(() => new THREE.Vector2(0,0)); square[this.game].trap.forEach(d => { trapArray.push(new THREE.Vector2(d[0], d[1])); }) square[this.game].end.forEach((d,i) => { targetArray[i] = new THREE.Vector2(d[0], d[1]); }) uniforms = { texture1: { value: texture1 }, texture2: { value: texture2 }, point0: { value: trapArray[0] }, point1: { value: trapArray[1] }, target: { value: targetArray } } uniforms[ "texture2" ].value.wrapS = uniforms[ "texture2" ].value.wrapT = THREE.RepeatWrapping; let planeMate = new THREE.ShaderMaterial({ side: THREE.DoubleSide, uniforms: uniforms, vertexShader: ` varying vec2 vUv; varying vec3 pos; void main() { vUv = uv; pos = position; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); } `, fragmentShader: ` varying vec2 vUv; varying vec3 pos; uniform vec2 point0; uniform vec2 point1; uniform vec2 target[7]; uniform sampler2D texture1; uniform sampler2D texture2; void main() { int index = 0; vec2 newUv = vec2(vUv.x * 7.0, vUv.y * 8.0); vec4 tcolor1 = texture2D( texture1, vUv ); vec4 tcolor2 = texture2D( texture2, newUv ); vec4 resultColor = tcolor1; if (pos.x < point0.x * 10.0 + 45.0 && pos.x > point0.x * 10.0 + 35.0 && pos.y < - point0.y * 10.0 - 40.0 && pos.y > - point0.y * 10.0 - 50.0) { resultColor = tcolor2; } else if(pos.x < point1.x * 10.0 + 45.0 && pos.x > point1.x * 10.0 + 35.0 && pos.y < - point1.y * 10.0 - 40.0 && pos.y > - point1.y * 10.0 - 50.0) { resultColor = tcolor2; } else { for(int i=0; i < 7; i++) { if (pos.x < target[i].x * 10.0 + 45.0 && pos.x > target[i].x * 10.0 + 35.0 && pos.y < - target[i].y * 10.0 - 40.0 && pos.y > - target[i].y * 10.0 - 50.0) { resultColor = vec4(1.0, 0.5, 0.0, 1.0); } } } gl_FragColor = resultColor; } ` })
texture1和texture2是兩個紋理圖,trapArray是盛放陷阱的陣列,targetArray是目標區域,預設長度是7,且預設值都是new THREE.Vector2(0,0),然後我們將二維向量加到以上兩個陣列中,最後新增到uniforms中,最後傳到ShaderMaterial中,頂點著色器我們只需要將position和ui傳到片元著色器中,關鍵是片元著色器,首先我們先得到一個新uv,這個新uv是沿x方向重複7次,沿y方向重複8次,然後tcolor1和tcolor2分別是底圖的顏色和重複了7*8的陷阱的顏色。if中是渲染第一個陷阱,else if是渲染第二個陷阱,else中迴圈target陣列,渲染target區域,具體的判斷其實很簡單。這樣我們就根據關卡渲染了陷阱。
2. 對是否可以旋轉進行判定
因為小方塊是不可以超過底圖的邊緣的,而且也不可以直接覆蓋到陷阱上面,因為這個操作是在點選上下左右的時候就要先判斷可行性,但是此時我們還沒有轉,所以我們就要先拷貝一個boxes,先進行旋轉看看出沒出界或者壓沒壓到陷阱,我們是這樣實現的。
judge(num) { judgeGroup = new THREE.Group(); boxesCopy = []; for(let i=0; i<boxes.length; i++) { let geom = new THREE.BoxGeometry(ratio, ratio, ratio); let mate = new THREE.MeshBasicMaterial({color: 0xffaa00, transparent: true, opacity: .8}); let mesh = new THREE.Mesh(geom, mate); mesh.position.copy(boxes[i].position); boxesCopy[i] = mesh; } if(num == 1) { var offset = new THREE.Vector3(box3.max.x, 0, 0); judgeGroup.position.copy(offset); boxesCopy.forEach(d => { d.position.sub(offset); judgeGroup.add(d); }) judgeGroup.rotation.z = - Math.PI / 2; } else if(num == 2) { var offset = new THREE.Vector3(box3.min.x, 0, 0); judgeGroup.position.copy(offset); boxesCopy.forEach(d => { d.position.sub(offset); judgeGroup.add(d); }) judgeGroup.rotation.z = Math.PI / 2; } else if(num == 3) { var offset = new THREE.Vector3(0, 0, box3.min.z); judgeGroup.position.copy(offset); boxesCopy.forEach(d => { d.position.sub(offset); judgeGroup.add(d); }) judgeGroup.rotation.x = - Math.PI / 2; } else if(num == 4) { var offset = new THREE.Vector3(0, 0, box3.max.z); judgeGroup.position.copy(offset); boxesCopy.forEach(d => { d.position.sub(offset); judgeGroup.add(d); }) judgeGroup.rotation.x = Math.PI / 2; } judgeGroup.updateMatrixWorld(); let canPass = true; boxesCopy.forEach(d => { var trans = new THREE.Vector3(); d.matrixWorld.decompose(trans, new THREE.Quaternion(), new THREE.Vector3()); let x = Math.round((trans.x - 5) / 10); let z = Math.round((trans.z - 5) / 10); let y = Math.round((trans.y + 5) / 10); if(x > -1 || x < -7 || z > -1 || z < -8) { canPass = false; } else { square[this.game].trap.forEach(d => { if(d[0] == x && d[1] == z && y == 1) { canPass = false; } }) } }) return canPass; },
boxesCopy就是對boxes進行的拷貝,num就是我們的上下左右操作,最後一個迴圈就是判斷是否可翻轉,x,y,z值分別對應我們的格子,if判斷時候出界,因為x的界限就是[-1,-7],z的界限就是[-1,-8]。else是判斷是否壓到陷阱,只要有一個成立,canPass就會變成false。這就完成了簡單的旋轉判斷。
3. 獲勝的判定
獲勝的判定很簡單,在每一個旋轉之後,比較boxes和end陣列,如果兩個陣列一樣,那麼就說明勝利了,程式碼如下
computedWin() { let win = true; let temp = []; boxes.forEach(d => { let x = Math.round((d.position.x - 5) / 10); let z = Math.round((d.position.z - 5) / 10); temp.push([x, z]); }) square[this.game].end.forEach(d => { if(!temp.some(dd => dd[0] == d[0] && dd[1] == d[1])) { win = false; } }) if(win) { this.win(); } },
最後加上一點tween動畫,這樣我們就完成了一個邏輯轉體的遊戲,遊戲玩起來還是比較有意思的。
轉載請註明地址:郭先生的部落格