網(wǎng)上有很多關(guān)于盒子科技pos機(jī)教程,3D 俄羅斯方塊與 Three.js 教程的知識,也有很多人為大家解答關(guān)于盒子科技pos機(jī)教程的問題,今天pos機(jī)之家(www.rcqwhg.com)為大家整理了關(guān)于這方面的知識,讓我們一起來看下吧!
本文目錄一覽:
盒子科技pos機(jī)教程
這個簡單的教程將引導(dǎo)您了解如何使用 Three.js 創(chuàng)建俄羅斯方塊游戲。每天\u202c分享\u202c最新\u202c軟件\u202c開發(fā)\u202c,Devops,敏捷\u202c,測試\u202c以及\u202c項(xiàng)目\u202c管理\u202c最新\u202c,最熱門\u202c的\u202c文章\u202c,每天\u202c花\u202c3分鐘\u202c學(xué)習(xí)\u202c何樂而不為\u202c,希望\u202c大家\u202c點(diǎn)贊\u202c,評論,加\u202c關(guān)注\u202c,你的\u202c支持\u202c是我\u202c最大\u202c的\u202c動力\u202c。下方抖音有我介紹自動化測試,以及google cloud 相關(guān)視頻課程,歡迎觀看。
想想我們玩俄羅斯方塊的方式。當(dāng)塊移動時(shí),我們可以自由地變換和旋轉(zhuǎn)它。制作塊的立方體清晰地連接起來,并且它們在代碼中的表示也應(yīng)該是直觀的。另一方面,當(dāng)我們嘗試完成一個切片(在 2D 中,一行)并且我們成功時(shí),立方體被移除,此時(shí)作為它們的原點(diǎn)的塊并不重要。事實(shí)上,這無關(guān)緊要——一個塊中的一些盒子可能會被移除,而另一些則不會。
追蹤一個盒子的起源需要不斷地分割和合并幾何圖形,相信我,那將是一個瘋狂的混亂。在原始的 2D 俄羅斯方塊中,有時(shí)正方形的顏色是原始塊的指示符。然而,在 3D 中,我們需要一種快速的方式來顯示 z 軸,而顏色非常適合這一點(diǎn)。
在我們的游戲中,立方體在動態(tài)時(shí)將連接,而在非動態(tài)時(shí)將連接。
添加靜態(tài)塊讓我們從移動塊接觸地板(或另一個塊)的那一刻開始。移動塊(具有幾個立方體的合并幾何)被轉(zhuǎn)換為不再移動的靜態(tài)分離立方體。將這些立方體保存在 3D 數(shù)組中很方便。
Tetris.staticBlocks = [];Tetris.zColors = [ 0x6666ff, 0x66ffff, 0xcc68EE, 0x666633, 0x66ff66, 0x9966ff, 0x00ff66, 0x66EE33, 0x003399, 0x330099, 0xFFA500, 0x99ff00, 0xee1289, 0x71C671, 0x00BFFF, 0x666633, 0x669966, 0x9966ff];Tetris.addStaticBlock = function(x,y,z) { if(Tetris.staticBlocks[x] === undefined) Tetris.staticBlocks[x] = []; if(Tetris.staticBlocks[x][y] === undefined) Tetris.staticBlocks[x][y] = []; var mesh = THREE.SceneUtils.createMultiMaterialObject(new THREE.CubeGeometry( Tetris.blockSize, Tetris.blockSize, Tetris.blockSize), [ new THREE.MeshBasicMaterial({color: 0x000000, shading: THREE.FlatShading, wireframe: true, transparent: true}), new THREE.MeshBasicMaterial({color: Tetris.zColors[z]}) ] ); mesh.position.x = (x - Tetris.boundingBoxConfig.splitX/2)*Tetris.blockSize + Tetris.blockSize/2; mesh.position.y = (y - Tetris.boundingBoxConfig.splitY/2)*Tetris.blockSize + Tetris.blockSize/2; mesh.position.z = (z - Tetris.boundingBoxConfig.splitZ/2)*Tetris.blockSize + Tetris.blockSize/2; mesh.overdraw = true; Tetris.scene.add(mesh); Tetris.staticBlocks[x][y][z] = mesh;};
這里有很多要解釋的。
顏色和材料Tetris.zColors 保留了指示立方體在 z 軸上位置的顏色列表。我想要一個好看的立方體,所以它應(yīng)該有一個顏色和輪廓邊框。我將使用 Three.js 教程中不太流行的東西——multiMaterials。Three.js SceneUtils 中有一個函數(shù),它接受一個幾何體和一個材質(zhì)數(shù)組(注意括號 [])。如果您查看 Three.js 源代碼:
createMultiMaterialObject : function ( geometry, materials ) { var i, il = materials.length, group = new THREE.Object3D(); for ( i = 0; i < il; i ++ ) { var object = new THREE.Mesh( geometry, materials[ i ] ); group.add( object ); } return group;},
這是一個非常簡單的 hack,可以為每種材料創(chuàng)建一個網(wǎng)格。使用純 WebGL 有更好的方法來實(shí)現(xiàn)相同的結(jié)果(fe 調(diào)用 draw 兩次,一次使用 gl.LINES,第二次使用 gl.something),但此函數(shù)的通常用途是例如同時(shí)合并紋理和材質(zhì)時(shí)間 - 不是不同類型的繪圖。
在 3D 空間中的位置現(xiàn)在,為什么這個位置看起來像這樣?
mesh.position.x = (x - Tetris.boundingBoxConfig.splitX/2)*Tetris.blockSize + Tetris.blockSize/2;
我們在 init 上的板中心放置在 (0,0,0) 點(diǎn)。這不是一個很好的位置,因?yàn)檫@意味著一些立方體將具有負(fù)位置,而其他立方體將具有正位置。在我們的例子中,最好指定一個對象的角。此外,我們希望將框的位置視為從 1 到 6 或至少 0 到 5 的離散值。 Three.js(以及 WebGL、OpenGL 和其他所有內(nèi)容)使用其自己的單位,這些單位與米或像素。如果你還記得,在配置中,我們輸入了一個值
Tetris.blockSize = boundingBoxConfig.width="360px",height="auto" />
那是負(fù)責(zé)轉(zhuǎn)換的??偨Y(jié)一下:
// transform 0-5 to -3 - +2(x - Tetris.boundingBoxConfig.splitX/2) // scale to Three.js units*Tetris.blockSize // we specify cube center, not a corner - we have to shift position + Tetris.blockSize/2不錯的測試
我們的游戲仍然非常靜態(tài),但您可以打開控制臺并運(yùn)行:
var i = 0, j = 0, k = 0, interval = setInterval(function() {if(i==6) {i=0;j++;} if(j==6) {j=0;k++;} if(k==6) {clearInterval(interval); return;} Tetris.addStaticBlock(i,j,k); i++;},30)
它應(yīng)該用立方體填充板子的動畫。
保持分?jǐn)?shù)一個保持分?jǐn)?shù)的小實(shí)用函數(shù):
Tetris.currentPoints = 0;Tetris.addPoints = function(n) { Tetris.currentPoints += n; Tetris.pointsDOM.innerHTML = Tetris.currentPoints; Cufon.replace('#points');}準(zhǔn)備
首先,創(chuàng)建一個新文件來保存我們的塊對象并將其包含在 index.html 中。該文件應(yīng)以:
window.Tetris = window.Tetris || {}; // equivalent to if(!window.Tetris) window.Tetris = {};
這樣,即使文件解析順序受到某種干擾(這不太可能,順便說一句),您也永遠(yuǎn)不會覆蓋現(xiàn)有對象或使用未定義的變量。此時(shí),您可能還想替換var Tetris = {};我們主文件中的“”聲明。
在繼續(xù)之前,我們需要一個效用函數(shù)。
Tetris.Utils = {}; Tetris.Utils.cloneVector = function (v) { return {x: v.x, y: v.y, z: v.z};};
要理解為什么我們需要它,我們必須談?wù)?JS 中的變量。如果我們使用一個數(shù)字,它總是按值傳遞。這意味著寫作:
var a = 5;var b = a;
將數(shù)字 5 放在 b 中,但無論如何它都不會與 a 相關(guān)。但是,在使用對象時(shí):
var a = (x: 5};var b = a;
b 是對對象的引用。使用 bx = 6; 將寫入 a 引用的同一個對象。
這就是為什么我們需要一種方法來創(chuàng)建向量的副本。簡單的 v1 = v2 意味著我們的記憶中只有一個向量。但是,如果我們直接訪問向量的數(shù)字部分并進(jìn)行克隆,我們將有兩個向量并且操作它們將是獨(dú)立的。
最后的準(zhǔn)備工作是定義形狀。
Tetris.Block = {}; Tetris.Block.shapes = [ [ {x: 0, y: 0, z: 0}, {x: 1, y: 0, z: 0}, {x: 1, y: 1, z: 0}, {x: 1, y: 2, z: 0} ], [ {x: 0, y: 0, z: 0}, {x: 0, y: 1, z: 0}, {x: 0, y: 2, z: 0}, ], [ {x: 0, y: 0, z: 0}, {x: 0, y: 1, z: 0}, {x: 1, y: 0, z: 0}, {x: 1, y: 1, z: 0} ], [ {x: 0, y: 0, z: 0}, {x: 0, y: 1, z: 0}, {x: 0, y: 2, z: 0}, {x: 1, y: 1, z: 0} ], [ {x: 0, y: 0, z: 0}, {x: 0, y: 1, z: 0}, {x: 1, y: 1, z: 0}, {x: 1, y: 2, z: 0} ]];
請注意,每個形狀的第一個立方體都是 (0,0,0)。這非常重要,將在下一節(jié)中解釋。
形狀生成描述塊的三個值:基本形狀、位置和旋轉(zhuǎn)。在這一點(diǎn)上,我們應(yīng)該提前考慮我們想要如何檢測碰撞。
根據(jù)我的經(jīng)驗(yàn),我可以看出游戲中的碰撞檢測總是或多或少是假的。一切都與性能有關(guān)——幾何形狀被簡化,特定情況下的碰撞首先被排除在外,一些碰撞根本不考慮,碰撞響應(yīng)幾乎總是不準(zhǔn)確的。沒關(guān)系——如果它看起來很自然,沒人會注意到,我們節(jié)省了大量寶貴的 CPU 周期。
那么,俄羅斯方塊最簡單的碰撞檢測是什么?所有形狀都是軸對齊的立方體,中心位于指定的一組點(diǎn)中。我有 99% 的把握為棋盤上的每個位置保留一組值 [FREE, MOVING, STATIC] 是處理它的最佳方式。這樣,如果我們想要移動一個形狀并且它需要的空間已經(jīng)被占用了——我們就會發(fā)生碰撞。復(fù)雜性:O(形狀中的立方體數(shù))<=> O(1)。噓!
現(xiàn)在,我知道輪換非常復(fù)雜,我們應(yīng)該盡可能避免它。這就是我們將塊的基本形狀保持為旋轉(zhuǎn)形式的原因。這樣我們可以只應(yīng)用位置(這很簡單)并快速檢查我們是否有碰撞。在我們的情況下實(shí)際上并不重要,但在更復(fù)雜的游戲中會如此。沒有足夠小的游戲可以以懶惰的方式進(jìn)行編程。
關(guān)于位置和旋轉(zhuǎn)——這兩個都在 Three.js 中使用。然而,問題是我們在 Three.js 和我們的開發(fā)板上使用了不同的單元。為了使我們的代碼簡單,我們將單獨(dú)存儲位置。旋轉(zhuǎn)在任何地方都是相同的,所以我們將使用內(nèi)置的。
首先,我們隨機(jī)取一個形狀并創(chuàng)建一個副本。這就是我們需要 cloneVector 函數(shù)的原因。
Tetris.Block.position = {}; Tetris.Block.generate = function() { var geometry, tmpGeometry; var type = Math.floor(Math.random()*(Tetris.Block.shapes.length)); this.blockType = type; Tetris.Block.shape = []; for(var i = 0; i < Tetris.Block.shapes[type].length; i++) { Tetris.Block.shape[i] = Tetris.Utils.cloneVector(Tetris.Block.shapes[type][i]); }
現(xiàn)在我們需要連接所有的立方體以作為一個形狀。
有一個 Three.js 函數(shù) - 它需要一個幾何圖形和一個網(wǎng)格并將它們合并。這里實(shí)際發(fā)生的是內(nèi)部頂點(diǎn)數(shù)組的合并。它考慮了合并幾何的位置。這就是為什么我們需要第一個立方體為 (0,0,0) 的原因。網(wǎng)格有一個位置,但幾何沒有 - 它總是被認(rèn)為是 (0,0,0)??梢詾閮蓚€網(wǎng)格編寫合并函數(shù),但它比保持形狀更復(fù)雜,不是嗎?
geometry = new THREE.CubeGeometry(Tetris.blockSize, Tetris.blockSize, Tetris.blockSize);for(var i = 1 ; i < Tetris.Block.shape.length; i++) { tmpGeometry = new THREE.Mesh(new THREE.CubeGeometry(Tetris.blockSize, Tetris.blockSize, Tetris.blockSize)); tmpGeometry.position.x = Tetris.blockSize * Tetris.Block.shape[i].x; tmpGeometry.position.y = Tetris.blockSize * Tetris.Block.shape[i].y; THREE.GeometryUtils.merge(geometry, tmpGeometry);}
通過合并幾何,我們可以使用教程前面的雙材質(zhì)技巧。
Tetris.Block.mesh = THREE.SceneUtils.createMultiMaterialObject(geometry, [ new THREE.MeshBasicMaterial({color: 0x000000, shading: THREE.FlatShading, wireframe: true, transparent: true}), new THREE.MeshBasicMaterial({color: 0xff0000})]);
我們必須為我們的塊設(shè)置初始位置和旋轉(zhuǎn)(x,y 為棋盤中心,z 為任意數(shù)字)。
// initial position Tetris.Block.position = {x: Math.floor(Tetris.boundingBoxConfig.splitX/2)-1, y: Math.floor(Tetris.boundingBoxConfig.splitY/2)-1, z: 15}; Tetris.Block.mesh.position.x = (Tetris.Block.position.x - Tetris.boundingBoxConfig.splitX/2)*Tetris.blockSize/2; Tetris.Block.mesh.position.y = (Tetris.Block.position.y - Tetris.boundingBoxConfig.splitY/2)*Tetris.blockSize/2; Tetris.Block.mesh.position.z = (Tetris.Block.position.z - Tetris.boundingBoxConfig.splitZ/2)*Tetris.blockSize + Tetris.blockSize/2; Tetris.Block.mesh.rotation = {x: 0, y: 0, z: 0}; Tetris.Block.mesh.overdraw = true; Tetris.scene.add(Tetris.Block.mesh);}; // end of Tetris.Block.generate()
如果需要,可以從控制臺調(diào)用 Tetris.Block.generate()。
移動移動一個方塊其實(shí)很簡單。對于旋轉(zhuǎn),我們使用 Three.js 內(nèi)部,我們必須將角度轉(zhuǎn)換為弧度。
Tetris.Block.rotate = function(x,y,z) { Tetris.Block.mesh.rotation.x += x * Math.PI / 180; Tetris.Block.mesh.rotation.y += y * Math.PI / 180; Tetris.Block.mesh.rotation.z += z * Math.PI / 180;};
位置也很簡單: Three.js 需要一個考慮塊大小的位置,而我們的副本不需要。我們的娛樂有一個簡單的地板命中檢查;稍后將被刪除。
Tetris.Block.move = function(x,y,z) { Tetris.Block.mesh.position.x += x*Tetris.blockSize; Tetris.Block.position.x += x; Tetris.Block.mesh.position.y += y*Tetris.blockSize; Tetris.Block.position.y += y; Tetris.Block.mesh.position.z += z*Tetris.blockSize; Tetris.Block.position.z += z; if(Tetris.Block.position.z == 0) Tetris.Block.hitBottom();};再次點(diǎn)擊并創(chuàng)建
hitBottom 有什么用?記?。咳绻粋€塊生命周期已經(jīng)結(jié)束,我們應(yīng)該將其轉(zhuǎn)換為靜態(tài)立方體,將其從場景中移除并生成一個新的。
Tetris.Block.hitBottom = function() { Tetris.Block.petrify(); Tetris.scene.removeObject(Tetris.Block.mesh); Tetris.Block.generate();};
我們已經(jīng)有了 generate() 和 removeObject() 是一個 Three.js 用于刪除未使用的網(wǎng)格的函數(shù)。幸運(yùn)的是,之前我們?yōu)殪o態(tài)立方體編寫了一個函數(shù),現(xiàn)在我們將在 petrify() 中使用它。
Tetris.Block.petrify = function() { var shape = Tetris.Block.shape; for(var i = 0 ; i < shape.length; i++) { Tetris.addStaticBlock(Tetris.Block.position.x + shape[i].x, Tetris.Block.position.y + shape[i].y, Tetris.Block.position.z + shape[i].z); }};
使用了 Tetris.Block.shape 的簡寫 - 它提高了代碼的清晰度和性能,所以每次合適的時(shí)候都使用這種技術(shù)。在此函數(shù)中,您可以了解為什么保持旋轉(zhuǎn)形狀和分離位置是一個好主意。多虧了這一點(diǎn),我們的代碼閱讀起來會很愉快,并且通過碰撞檢測,它會變得更加重要。
將點(diǎn)連接好的,現(xiàn)在我們有了塊所需的所有功能,讓我們在需要的地方掛鉤它們。我們需要在開始時(shí)生成一個塊,因此將 Tetris.start() 更改為:
Tetris.start = function() { document.getElementById("menu").style.display = "none"; Tetris.pointsDOM = document.getElementById("points"); Tetris.pointsDOM.style.display = "block"; Tetris.Block.generate(); // add this line Tetris.animate();};
每走一步,我們都應(yīng)該將積木向前移動一步,所以在
Tetris.animate() 我們采取行動并將其更改為:
while(Tetris.cumulatedFrameTime > Tetris.gameStepTime) { Tetris.cumulatedFrameTime -= Tetris.gameStepTime; Tetris.Block.move(0,0,-1);鍵盤
老實(shí)說:我討厭鍵盤事件。鍵碼是無意義的,它們對于 keydown 和 keypress 是不同的。沒有很好的方法來輪詢鍵盤狀態(tài),在第二次按鍵事件重復(fù)比前兩次快 10 倍之后,等等。如果您考慮具有大量鍵盤交互的嚴(yán)肅游戲,您幾乎肯定會構(gòu)建某種包裝器所有這些廢話。你可以試試KeyboardJS,它看起來不錯。我將使用 vanilla JS 來展示總體思路。為了調(diào)試它,我使用了 console.log(keycode)。找到正確的代碼有很大幫助。
window.addEventListener('keydown', function (event) { var key = event.which ? event.which : event.keyCode; switch(key) { case 38: // up (arrow) Tetris.Block.move(0, 1, 0); break; case 40: // down (arrow) Tetris.Block.move(0, -1, 0); break; case 37: // left(arrow) Tetris.Block.move(-1, 0, 0); break; case 39: // right (arrow) Tetris.Block.move(1, 0, 0); break; case 32: // space Tetris.Block.move(0, 0, -1); break; case 87: // up (w) Tetris.Block.rotate(90, 0, 0); break; case 83: // down (s) Tetris.Block.rotate(-90, 0, 0); break; case 65: // left(a) Tetris.Block.rotate(0, 0, 90); break; case 68: // right (d) Tetris.Block.rotate(0, 0, -90); break; case 81: // (q) Tetris.Block.rotate(0, 90, 0); break; case 69: // (e) Tetris.Block.rotate(0, -90, 0); break; }}, false);
如果您現(xiàn)在嘗試玩游戲,您應(yīng)該能夠移動和旋轉(zhuǎn)一個方塊。不會有碰撞檢測,但是當(dāng)它撞到地面時(shí),它會被移除并且新的方塊會出現(xiàn)在船上。因?yàn)槲覀儾粚Υ鎯Φ男螤顟?yīng)用旋轉(zhuǎn),所以靜態(tài)版本可能會以不同的方式旋轉(zhuǎn)。
板對象我們將從一個新的類開始來存儲我們的 3D 空間信息。我們需要很少的“const”、“enum”值。它們實(shí)際上既不是 const 也不是 enum,因?yàn)?JS 中沒有這樣的東西,但是JS 1.8.5 中有一個新功能 - freeze。您可以創(chuàng)建一個對象并保護(hù)它免受任何進(jìn)一步的修改。它在所有可能運(yùn)行 WebGL 的瀏覽器中得到廣泛支持,并將為我們提供類似枚舉的對象。
window.Tetris = window.Tetris || {};Tetris.Board = {}; Tetris.Board.COLLISION = {NONE:0, WALL:1, GROUND:2};Object.freeze(Tetris.Board.COLLISION); Tetris.Board.FIELD = {EMPTY:0, ACTIVE:1, PETRIFIED:2};Object.freeze(Tetris.Board.FIELD);We will use field enum to store state of our board in fields array. On game start we need to initialize it as empty.Tetris.Board.fields = []; Tetris.Board.init = function(_x,_y,_z) { for(var x = 0; x < _x; x++) { Tetris.Board.fields[x] = []; for(var y = 0; y < _y; y++) { Tetris.Board.fields[x][y] = []; for(var z = 0; z < _z; z++) { Tetris.Board.fields[x][y][z] = Tetris.Board.FIELD.EMPTY; } } }};
Tetris.Board.init() 應(yīng)該在游戲中出現(xiàn)任何塊之前調(diào)用。我從 Tetris.init 中調(diào)用它,因?yàn)槲覀兛梢暂p松地提供板尺寸作為參數(shù):
// add anywhere in Tetris.initTetris.Board.init(boundingBoxConfig.splitX, boundingBoxConfig.splitY, boundingBoxConfig.splitZ);
我們還應(yīng)該修改 Tetris.Block.petrify 函數(shù),以便它將信息存儲在我們的新數(shù)組中。
Tetris.Block.petrify = function () { var shape = Tetris.Block.shape; for (var i = 0; i < shape.length; i++) { Tetris.addStaticBlock(Tetris.Block.position.x + shape[i].x, Tetris.Block.position.y + shape[i].y, Tetris.Block.position.z + shape[i].z); Tetris.Board.fields[Tetris.Block.position.x + shape[i].x][Tetris.Block.position.y + shape[i].y][Tetris.Block.position.z + shape[i].z] = Tetris.Board.FIELD.PETRIFIED; }};碰撞檢測
俄羅斯方塊中有兩種主要的碰撞類型。第一個是墻碰撞,當(dāng)一個活動塊在 x/y 軸上移動或旋轉(zhuǎn)時(shí)(例如在一個級別上)撞到墻或另一個塊。第二個是地面碰撞,當(dāng)一個塊在 z 軸上移動并撞擊地板或另一個塊并且其生命周期結(jié)束時(shí)發(fā)生。
我們將從板墻碰撞開始,這很容易。為了使代碼更好(更快),我再次使用了速記。
Tetris.Board.testCollision = function (ground_check) { var x, y, z, i; // shorthands var fields = Tetris.Board.fields; var posx = Tetris.Block.position.x, posy = Tetris.Block.position.y, posz = Tetris.Block.position.z, shape = Tetris.Block.shape; for (i = 0; i < shape.length; i++) { // 4 walls detection for every part of the shape if ((shape[i].x + posx) < 0 || (shape[i].y + posy) < 0 || (shape[i].x + posx) >= fields.length || (shape[i].y + posy) >= fields[0].length) { return Tetris.Board.COLLISION.WALL; }
現(xiàn)在,如何處理塊與塊碰撞?我們已經(jīng)在數(shù)組中存儲了石化塊,所以我們可以檢查塊是否與任何現(xiàn)有的立方體相交。您可能想知道為什么 testCollision 有 ground_check 作為參數(shù)。這是一個簡單觀察的結(jié)果,即以幾乎與地面和墻壁碰撞相同的方式檢測塊與塊碰撞。唯一的區(qū)別是 z 軸上的移動會導(dǎo)致地面撞擊。
if (fields[shape[i].x + posx][shape[i].y + posy][shape[i].z + posz - 1] === Tetris.Board.FIELD.PETRIFIED) { return ground_check ? Tetris.Board.COLLISION.GROUND : Tetris.Board.COLLISION.WALL;}
我們還將測試 z 軸上的位置是否不等于 0。這意味著我們的移動方塊下方?jīng)]有立方體,但它到達(dá)了地面,無論如何都應(yīng)該被石化。
if((shape[i].z + posz) <= 0) { return Tetris.Board.COLLISION.GROUND; } }};碰撞反應(yīng)
沒那么糟糕,是嗎?現(xiàn)在讓我們用我們擁有的信息做一些事情。我們將從最簡單的地方開始,檢測丟失的游戲。我們可以通過在創(chuàng)建新塊后立即測試是否存在碰撞來做到這一點(diǎn)。如果它撞到地面,則沒有進(jìn)一步玩的意義。
在計(jì)算塊位置后添加到 Tetris.Block.generate:
if (Tetris.Board.testCollision(true) === Tetris.Board.COLLISION.GROUND) { Tetris.gameOver = true; Tetris.pointsDOM.innerHTML = "GAME OVER"; Cufon.replace('#points');}
運(yùn)動也很簡單。在我們改變一個位置后,我們稱之為碰撞檢測,將有關(guān) z 軸運(yùn)動的信息作為參數(shù)傳遞。
如果發(fā)生墻壁碰撞,移動是不可能的,我們應(yīng)該撤消它。我們可以添加幾行來減去位置,但我很懶,我更喜歡再次調(diào)用 move 函數(shù),但使用倒置的參數(shù)。它永遠(yuǎn)不會與 z 軸移動一起使用,因此我們可以將零作為 z 傳遞。
如果形狀碰到地面,我們已經(jīng)有一個應(yīng)該調(diào)用的函數(shù) hitBottom()。它將從游戲中移除活動形狀,修改棋盤狀態(tài)并創(chuàng)建新形狀。
// add instead of ground level detection from part 3var collision = Tetris.Board.testCollision((z != 0));if (collision === Tetris.Board.COLLISION.WALL) { Tetris.Block.move(-x, -y, 0); // laziness FTW}if (collision === Tetris.Board.COLLISION.GROUND) { Tetris.Block.hitBottom();}
如果此時(shí)運(yùn)行游戲,您會注意到旋轉(zhuǎn)的形狀不是永久的。當(dāng)它撞到地面時(shí),它會返回初始旋轉(zhuǎn)。這是因?yàn)槲覀儗⑿D(zhuǎn)應(yīng)用于 Three.js 網(wǎng)格(如 Tetris.Block.mesh.rotation),但我們不使用它來獲取基于立方體的形狀表示的坐標(biāo)。為了解決這個問題,我們需要上一節(jié)快速的數(shù)學(xué)課。
3D 數(shù)學(xué)免責(zé)聲明:如果您害怕數(shù)學(xué)或沒有時(shí)間,您實(shí)際上可以跳過這部分。了解引擎內(nèi)部發(fā)生了什么很重要,但稍后我們將為此使用 Three.js 函數(shù)。
考慮一個三元素向量(表示 3D 空間中的位置)。為了在歐幾里得空間中變換這樣的向量,我們必須添加另一個向量。它可以表示為:
\\[\\begin{matrix}x\\\\y\\\\z\\\\\\end{matrix}\\ + \\begin{matrix}\\delta x\\\\\\delta y\\\\\\delta z\\\\\\end{matrix} = \\begin{matrix}x'\\\\ y'\\\\ z'\\\\\\end{matrix} \\]
這很簡單。當(dāng)我們想旋轉(zhuǎn)一個向量時(shí),問題就出現(xiàn)了。圍繞單個軸的旋轉(zhuǎn)會影響三個坐標(biāo)中的兩個(如果您不相信,請檢查),并且方程式并不是那么簡單。幸運(yùn)的是,幾乎所有計(jì)算機(jī)生成的圖形都使用了一種方法,包括 Three.js、WebGL、OpenGL 和 GPU 本身。
如果您還記得高中時(shí),將向量乘以矩陣將得到另一個向量。在此基礎(chǔ)上進(jìn)行了許多轉(zhuǎn)換。最簡單的一種是中性變換(使用單位矩陣),它只顯示一般概念,并用作其他變換的基礎(chǔ)。
\\[\\begin{matrix}x\\\\y\\\\z\\\\w\\\\\\end{matrix}\\ * \\begin{matrix} 1 & 0 & 0 & 0\\\\0 & 1 & 0 & 0\\\\0 & 0 & 1 & 0\\\\0 & 0 & 0 & 1\\end{matrix}\\ = \\begin{matrix}x\\\\y\\\\z\\\\w\\\\\\end{matrix}\\]
為什么我們使用 4×4 矩陣和 4 元素向量而不是 3×3 和 3 元素?它用于通過向量啟用翻譯:
\\[\\begin{matrix}x\\\\y\\\\z\\\\w\\\\\\end{matrix}\\ * \\begin{matrix} 1 & 0 & 0 & \\delta x\\\\0 & 1 & 0 & \\delta y\\\\0 & 0 & 1 & \\delta z\\\\0 & 0 & 0 & 1\\end{matrix}\\ = \\begin{matrix}x'\\\\y'\\\\z'\\\\w'\\\\\\end{matrix}\\]
這是一個很好的數(shù)學(xué)技巧,可以讓所有方程變得更容易。它還有助于解決數(shù)值錯誤,并使我們能夠使用更高級的概念,例如四元數(shù)。
縮放也很簡單:
\\[\\begin{matrix}x\\\\y\\\\z\\\\w\\\\\\end{matrix}\\ * \\begin{matrix} sx & 0 & 0 & 0\\\\ 0 & sy & 0 & 0\\\\ 0 & 0 & sz & 0\\\\ 0 & 0 & 0 & 1 \\end{matrix}= \\begin{matrix}x * sx\\\\y * sy\\\\z * sz\\\\w'\\\\\\end{matrix}\\]
旋轉(zhuǎn)有三個矩陣,每個軸一個。
對于 x 軸
\\[ \\begin{matrix} 1 & 0 & 0 & 0\\\\ 0 & cos \\alpha & -sin \\alpha & 0\\\\ 0 & sin \\alpha & cos \\alpha & 0\\\\ 0 & 0 & 0 & 1 \\end{matrix}\\]
對于 y 軸
\\[ \\begin{matrix} cos \\alpha & 0 & sin \\alpha & 0\\\\ 0 & 1 & 0 & 0\\\\ -sin \\alpha & 0 & cos \\alpha & 0\\\\ 0 & 0 & 0 & 1 \\end{matrix}\\]
對于 z 軸
\\[ \\begin{matrix} cos \\alpha & -sin \\alpha & 0 & 0\\\\ sin \\alpha & cos \\alpha & 0 & 0\\\\ 0 & 0 & 1 & 0\\\\ 0 & 0 & 0 & 1 \\end{matrix}\\]
矩陣變換的另一個好處是我們可以通過將兩個變換的矩陣相乘來輕松組合兩個變換。如果你想圍繞所有三個軸旋轉(zhuǎn),你可以將三個矩陣相乘,得到一個叫做變換矩陣的東西。它將很容易地轉(zhuǎn)換一個表示位置的向量。
幸運(yùn)的是,大多數(shù)時(shí)候您不必在數(shù)學(xué)庫上工作。Three.js 已經(jīng)有一個內(nèi)置的數(shù)學(xué)庫,我們將使用它。
返回旋轉(zhuǎn)要在 Three.js 中旋轉(zhuǎn)一個形狀,我們需要創(chuàng)建一個旋轉(zhuǎn)矩陣并將其與形狀的每個向量相乘。我們將再次使用 cloneVector 來確保創(chuàng)建的形狀獨(dú)立于存儲為模式的形狀。
// append to Tetris.Block.rotate()var rotationMatrix = new THREE.Matrix4();rotationMatrix.setRotationFromEuler(Tetris.Block.mesh.rotation); for (var i = 0; i < Tetris.Block.shape.length; i++) { Tetris.Block.shape[i] = rotationMatrix.multiplyVector3( Tetris.Utils.cloneVector(Tetris.Block.shapes[this.blockType][i]) ); Tetris.Utils.roundVector(Tetris.Block.shape[i]);}
旋轉(zhuǎn)矩陣和我們對棋盤的表示存在一個問題。字段表示為由整數(shù)索引的數(shù)組,而矩陣向量乘法的結(jié)果可能是浮點(diǎn)數(shù)。JavaScript 對浮點(diǎn)數(shù)不是很好,幾乎可以肯定它會產(chǎn)生像 1.000001 或 2.999998 這樣的位置。這就是為什么我們需要一個舍入函數(shù)。
Tetris.Utils.roundVector = function(v) { v.x = Math.round(v.x); v.y = Math.round(v.y); v.z = Math.round(v.z);};
當(dāng)我們旋轉(zhuǎn)我們的形狀時(shí),檢查是否發(fā)生碰撞非常簡單。我通過再次調(diào)用該函數(shù)使用了相同的技巧來撤消旋轉(zhuǎn),但使用了反轉(zhuǎn)的參數(shù)。請注意,撤消移動時(shí)永遠(yuǎn)不會發(fā)生碰撞。如果需要,您可以添加一個附加參數(shù),以便在不需要時(shí)不會再次檢查它。
// append to Tetris.Block.rotate()if (Tetris.Board.testCollision(false) === Tetris.Board.COLLISION.WALL) { Tetris.Block.rotate(-x, -y, -z); // laziness FTW}完成切片和計(jì)分
這個函數(shù)會很長但是很簡單。要檢查切片是否完成,我會計(jì)算占用字段的最大數(shù)量并檢查每個切片(在 z 軸上移動)是否已滿。這樣我可以改變電路板的大小,這個功能應(yīng)該仍然有效。試著以這樣的方式思考你所有的功能:如果有什么改變,讓你的代碼靈活。
Tetris.Board.checkCompleted = function() { var x,y,z,x2,y2,z2, fields = Tetris.Board.fields; var rebuild = false; var sum, expected = fields[0].length*fields.length, bonus = 0; for(z = 0; z < fields[0][0].length; z++) { sum = 0; for(y = 0; y < fields[0].length; y++) { for(x = 0; x < fields.length; x++) { if(fields[x][y][z] === Tetris.Board.FIELD.PETRIFIED) sum++; } }
當(dāng)切片已滿時(shí),我們應(yīng)該將其移除并移動所有后續(xù)切片。為了確保我們不會跳過移位的切片,我們將 z 減小一次。為了使游戲更有趣,如果一次完成多個切片,則會獲得獎勵積分。
if(sum == expected) { bonus += 1 + bonus; // 1, 3, 7, 15... for(y2 = 0; y2 < fields[0].length; y2++) { for(x2 = 0; x2 < fields.length; x2++) { for(z2 = z; z2 < fields[0][0].length-1; z2++) { Tetris.Board.fields[x2][y2][z2] = fields[x2][y2][z2+1]; // shift } Tetris.Board.fields[x2][y2][fields[0][0].length-1] = Tetris.Board.FIELD.EMPTY; } } rebuild = true; z--; }}if(bonus) { Tetris.addPoints(1000 * bonus);}
現(xiàn)在,即使我們處理了棋盤信息,我們?nèi)匀恍枰獙?Three.js 的幾何圖形進(jìn)行更改。我們不能在前面的循環(huán)中這樣做,因?yàn)槿绻淮瓮瓿啥鄠€切片,它可以重建幾何兩次甚至更多。此循環(huán)檢查每個 Tetris.Board.fields 與相應(yīng)的 Tetris.staticBlocks 在需要時(shí)添加和刪除幾何圖形。
if(rebuild) { for(var z = 0; z < fields[0][0].length-1; z++) { for(var y = 0; y < fields[0].length; y++) { for(var x = 0; x < fields.length; x++) { if(fields[x][y][z] === Tetris.Board.FIELD.PETRIFIED && !Tetris.staticBlocks[x][y][z]) { Tetris.addStaticBlock(x,y,z); } if(fields[x][y][z] == Tetris.Board.FIELD.EMPTY && Tetris.staticBlocks[x][y][z]) { Tetris.scene.removeObject(Tetris.staticBlocks[x][y][z]); Tetris.staticBlocks[x][y][z] = undefined; } } } } }};音頻 API
使用HTML5添加音頻非常簡單。讓我們從將 <audio> 元素添加到 index.html 開始。
<audio id="audio_theme" src="music/tetris.mp3" preload="auto"></audio><audio id="audio_move" src="music/move.mp3" preload="auto"></audio><audio id="audio_collision" src="music/collision.mp3" preload="auto"></audio><audio id="audio_gameover" src="music/gameover.mp3" preload="auto"></audio><audio id="audio_score" src="music/cash.mp3" preload="auto"></audio>
在 JS 中使用這些文件也很容易。首先創(chuàng)建一個對象來存儲你的聲音:
// before Tetris.init()Tetris.sounds = {};
要調(diào)用音頻 API,我們必須檢索這些 DOM 元素。
// in Tetris.init()Tetris.sounds["theme"] = document.getElementById("audio_theme");Tetris.sounds["collision"] = document.getElementById("audio_collision");Tetris.sounds["move"] = document.getElementById("audio_move");Tetris.sounds["gameover"] = document.getElementById("audio_gameover");Tetris.sounds["score"] = document.getElementById("audio_score");
有許多方法,您可以創(chuàng)建自己的音頻播放器,但就我們的目的而言,play() 和 pause() 就足夠了。您可能會猜到應(yīng)該在哪里添加音樂:
Tetris.sounds["theme"].play() – 在 Tetris.init() 中,在聲音對象初始化之后。Tetris.sounds["theme"].pause() – 在 Tetris.start() 中。else {Tetris.sounds["move"].play();} - 在 Tetris.Block.move() 中,如果沒有地面碰撞。俄羅斯方塊.sounds[“碰撞”].play(); – 在 Tetris.Block.move() 中,如果有地面碰撞。Tetris.sounds["score"].play(); – 在俄羅斯方塊.addPoints() 中。Tetris.sounds["gameover"].play(); – 在 Tetris.Block.generate() 中,我們測試輸?shù)舻挠螒?。結(jié)論這就是所有的人!我們的俄羅斯方塊現(xiàn)在功能齊全。我希望這是一種學(xué)習(xí) Three.js 的有趣方式。這里沒有涉及許多主題,例如高級幾何圖形、著色器、燈光、骨骼動畫等。我只是想表明創(chuàng)建游戲并不總是需要它們。
如果你想了解更多,你可能應(yīng)該從現(xiàn)在開始使用純 WebGL。您可以從本教程開始。另請查看Brandon Jones的“Building the Game” 。
以上就是關(guān)于盒子科技pos機(jī)教程,3D 俄羅斯方塊與 Three.js 教程的知識,后面我們會繼續(xù)為大家整理關(guān)于盒子科技pos機(jī)教程的知識,希望能夠幫助到大家!