three.js 製作邏輯轉體遊戲(下)

郭先生的部落格發表於2020-08-20

上一篇已經對繞非定軸轉動有所瞭解,這篇郭先生繼續說一說邏輯轉體遊戲的製作,這部分我們同樣會遇到一些小問題,首先是根據資料渲染陷阱和目標區域,然後是對可以轉動的判定,最後是獲勝的判定。

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動畫,這樣我們就完成了一個邏輯轉體的遊戲,遊戲玩起來還是比較有意思的。

 

轉載請註明地址:郭先生的部落格

相關文章