在之前的教學中,你建立了一個完整的 88 鍵鋼琴鍵盤模型。 現在,你讓它能在 XR 領域中遊玩。
在這個教學中,你會學到如何:
- 透過指標事件新增互動式鋼琴功能
- 將網格縮放到不同大小
- 啟用 XR 中的傳送與多點指標支援
開始之前
務必完成 系列中之前的教學 ,並準備好繼續添加程式碼。
index.html
<html>
<head>
<title>Piano in BabylonJS</title>
<script src="https://cdn.babylonjs.com/babylon.js"></script>
<script src="scene.js"></script>
<style>
body,#renderCanvas { width: 100%; height: 100%;}
</style>
</head>
<body>
<canvas id="renderCanvas"></canvas>
<script type="text/javascript">
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
createScene(engine).then(sceneToRender => {
engine.runRenderLoop(() => sceneToRender.render());
});
// Watch for browser/canvas resize events
window.addEventListener("resize", function () {
engine.resize();
});
</script>
</body>
</html>
scene.js
const buildKey = function (scene, parent, props) {
if (props.type === "white") {
/*
Props for building a white key should contain:
note, topWidth, bottomWidth, topPositionX, wholePositionX, register, referencePositionX
As an example, the props for building the middle C white key would be
{type: "white", note: "C", topWidth: 1.4, bottomWidth: 2.3, topPositionX: -0.45, wholePositionX: -14.4, register: 4, referencePositionX: 0}
*/
// Create bottom part
const bottom = BABYLON.MeshBuilder.CreateBox("whiteKeyBottom", {width: props.bottomWidth, height: 1.5, depth: 4.5}, scene);
// Create top part
const top = BABYLON.MeshBuilder.CreateBox("whiteKeyTop", {width: props.topWidth, height: 1.5, depth: 5}, scene);
top.position.z = 4.75;
top.position.x += props.topPositionX;
// Merge bottom and top parts
// Parameters of BABYLON.Mesh.MergeMeshes: (arrayOfMeshes, disposeSource, allow32BitsIndices, meshSubclass, subdivideWithSubMeshes, multiMultiMaterials)
const key = BABYLON.Mesh.MergeMeshes([bottom, top], true, false, null, false, false);
key.position.x = props.referencePositionX + props.wholePositionX;
key.name = props.note + props.register;
key.parent = parent;
return key;
}
else if (props.type === "black") {
/*
Props for building a black key should contain:
note, wholePositionX, register, referencePositionX
As an example, the props for building the C#4 black key would be
{type: "black", note: "C#", wholePositionX: -13.45, register: 4, referencePositionX: 0}
*/
// Create black color material
const blackMat = new BABYLON.StandardMaterial("black");
blackMat.diffuseColor = new BABYLON.Color3(0, 0, 0);
// Create black key
const key = BABYLON.MeshBuilder.CreateBox(props.note + props.register, {width: 1.4, height: 2, depth: 5}, scene);
key.position.z += 4.75;
key.position.y += 0.25;
key.position.x = props.referencePositionX + props.wholePositionX;
key.material = blackMat;
key.parent = parent;
return key;
}
}
const createScene = async function(engine) {
const scene = new BABYLON.Scene(engine);
const alpha = 3*Math.PI/2;
const beta = Math.PI/50;
const radius = 220;
const target = new BABYLON.Vector3(0, 0, 0);
const camera = new BABYLON.ArcRotateCamera("Camera", alpha, beta, radius, target, scene);
camera.attachControl(canvas, true);
const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
light.intensity = 0.6;
const keyParams = [
{type: "white", note: "C", topWidth: 1.4, bottomWidth: 2.3, topPositionX: -0.45, wholePositionX: -14.4},
{type: "black", note: "C#", wholePositionX: -13.45},
{type: "white", note: "D", topWidth: 1.4, bottomWidth: 2.4, topPositionX: 0, wholePositionX: -12},
{type: "black", note: "D#", wholePositionX: -10.6},
{type: "white", note: "E", topWidth: 1.4, bottomWidth: 2.3, topPositionX: 0.45, wholePositionX: -9.6},
{type: "white", note: "F", topWidth: 1.3, bottomWidth: 2.4, topPositionX: -0.55, wholePositionX: -7.2},
{type: "black", note: "F#", wholePositionX: -6.35},
{type: "white", note: "G", topWidth: 1.3, bottomWidth: 2.3, topPositionX: -0.2, wholePositionX: -4.8},
{type: "black", note: "G#", wholePositionX: -3.6},
{type: "white", note: "A", topWidth: 1.3, bottomWidth: 2.3, topPositionX: 0.2, wholePositionX: -2.4},
{type: "black", note: "A#", wholePositionX: -0.85},
{type: "white", note: "B", topWidth: 1.3, bottomWidth: 2.4, topPositionX: 0.55, wholePositionX: 0},
]
// Transform Node that acts as the parent of all piano keys
const keyboard = new BABYLON.TransformNode("keyboard");
// Register 1 through 7
var referencePositionX = -2.4*14;
for (let register = 1; register <= 7; register++) {
keyParams.forEach(key => {
buildKey(scene, keyboard, Object.assign({register: register, referencePositionX: referencePositionX}, key));
})
referencePositionX += 2.4*7;
}
// Register 0
buildKey(scene, keyboard, {type: "white", note: "A", topWidth: 1.9, bottomWidth: 2.3, topPositionX: -0.20, wholePositionX: -2.4, register: 0, referencePositionX: -2.4*21});
keyParams.slice(10, 12).forEach(key => {
buildKey(scene, keyboard, Object.assign({register: 0, referencePositionX: -2.4*21}, key));
})
// Register 8
buildKey(scene, keyboard, {type: "white", note: "C", topWidth: 2.3, bottomWidth: 2.3, topPositionX: 0, wholePositionX: -2.4*6, register: 8, referencePositionX: 84});
// Transform node that acts as the parent of all piano components
const piano = new BABYLON.TransformNode("piano");
keyboard.parent = piano;
// Import and scale piano frame
BABYLON.SceneLoader.ImportMesh("frame", "https://raw.githubusercontent.com/MicrosoftDocs/mixed-reality/docs/mixed-reality-docs/mr-dev-docs/develop/javascript/tutorials/babylonjs-webxr-piano/files/", "pianoFrame.babylon", scene, function(meshes) {
const frame = meshes[0];
frame.parent = piano;
});
// Lift the piano keyboard
keyboard.position.y += 80;
const xrHelper = await scene.createDefaultXRExperienceAsync();
return scene;
}
讓鋼琴鍵盤變得可演奏
目前我們所建立的鋼琴鍵盤是一個靜態模型,不會對任何使用者互動做出反應。 在本節中,我們將設定按鍵向下移動,當有人按下鍵時會播放聲音。
Babylon.js 提供了不同類型的事件或 可觀察量,讓我們可以互動。 在我們的案例中,我們會處理這個
onPointerObservable問題,因為我們想把按鍵設定成當有人透過指標按下時執行動作,這些動作可以是滑鼠點擊、觸控、XR 控制器的按鈕點擊等等。以下是我們如何將任何行為加入
onPointerObservable的基本結構:scene.onPointerObservable.add((pointerInfo) => { // do something });雖然 Babylon.js 提供了 許多不同類型的指標事件,但我們只會用
POINTERDOWNandPOINTERUP事件來編程鋼琴鍵的行為,結構如下:scene.onPointerObservable.add((pointerInfo) => { switch (pointerInfo.type) { case BABYLON.PointerEventTypes.POINTERDOWN: // When the pointer is down on a piano key, // move the piano key downward (to show that it is pressed) // and play the sound of the note break; case BABYLON.PointerEventTypes.POINTERUP: // When the pointer is released, // move the piano key upward to its original position // and stop the sound of the note of the key that is released break; } });我們先練習在按下和放開琴鍵時,如何向下和向上移動鋼琴鍵。
在指標向下事件中,我們需要偵測被點擊的網格,確保是鋼琴鍵,並稍微改變網格的 y 座標,讓它看起來像是按下了鍵。
至於指向上方事件,情況會比較複雜,因為按下按鍵的指標可能無法放開。 舉例來說,有人可能會按下 C4 鍵,拖曳滑鼠到 E4,然後放開點擊。 在這種情況下,我們仍然想釋放 (C4) 按下的鍵,而不是在事件發生 (
pointerUpE4) 的位置。讓我們來看看以下程式碼如何達成我們的需求:
const pointerToKey = new Map(); scene.onPointerObservable.add((pointerInfo) => { switch (pointerInfo.type) { case BABYLON.PointerEventTypes.POINTERDOWN: if(pointerInfo.pickInfo.hit) { const pickedMesh = pointerInfo.pickInfo.pickedMesh; const pointerId = pointerInfo.event.pointerId; if (pickedMesh.parent === keyboard) { pickedMesh.position.y -= 0.5; // play the sound of the note pointerToKey.set(pointerId, { mesh: pickedMesh }); } } break; case BABYLON.PointerEventTypes.POINTERUP: const pointerId = pointerInfo.event.pointerId; if (pointerToKey.has(pointerId)) { pointerToKey.get(pointerId).mesh.position.y += 0.5; // stop the sound of the note of the key that is released pointerToKey.delete(pointerId); } break; } });每個
pointerId指標都是獨一無二的,當我們有多個控制器或使用觸控螢幕時,能幫助我們辨識指標。 這裡我們初始化了一個Map名為pointerToKeyObject,用來儲存哪個指標按下了哪個鍵的關係,這樣當指標被放開時,無論放開的位置,我們都知道該放開哪個鍵。以下是使用上述程式碼的互動方式:
現在讓我們練習在按鍵與放開鍵時如何演奏和停止聲音。 為此,我們將使用名為 soundfont-player 的 Javascript 函式庫,讓我們輕鬆播放自己選擇樂器的 MIDI 音色。
下載庫的最小化程式碼,儲存在與 index.html相同的資料夾,並包含
<header>在 index.html的標籤中:<head> <title>Babylon Template</title> <script src="https://cdn.babylonjs.com/babylon.js"></script> <script src="scene.js"></script> <script src="soundfont-player.min.js"></script> <style> body,#renderCanvas { width: 100%; height: 100%;} </style> </head>匯入音色庫後,我們可以如何初始化樂器並用音庫播放/停止 MIDI 音色:
const pianoSound = await Soundfont.instrument(new AudioContext(), 'acoustic_grand_piano'); const C4 = piano.play("C4"); // Play note C4 C4.stop(); // Stop note C4現在讓我們把這個納入指標事件,並完成本節的程式碼:
const pointerToKey = new Map() const piano = await Soundfont.instrument(new AudioContext(), 'acoustic_grand_piano'); scene.onPointerObservable.add((pointerInfo) => { switch (pointerInfo.type) { case BABYLON.PointerEventTypes.POINTERDOWN: if(pointerInfo.pickInfo.hit) { let pickedMesh = pointerInfo.pickInfo.pickedMesh; let pointerId = pointerInfo.event.pointerId; if (keys.has(pickedMesh)) { pickedMesh.position.y -= 0.5; // Move the key downward pointerToKey.set(pointerId, { mesh: pickedMesh, note: pianoSound.play(pointerInfo.pickInfo.pickedMesh.name) // Play the sound of the note }); } } break; case BABYLON.PointerEventTypes.POINTERUP: let pointerId = pointerInfo.event.pointerId; if (pointerToKey.has(pointerId)) { pointerToKey.get(pointerId).mesh.position.y += 0.5; // Move the key upward pointerToKey.get(pointerId).note.stop(); // Stop the sound of the note pointerToKey.delete(pointerId); } break; } });由於我們以每個鍵所代表的音符命名每個鍵的網格,我們可以輕鬆地透過將網格名稱傳入
pianoSound.play()函式來指示要彈哪個音符。 另外要注意的是,我們會把聲音存進地圖,pointerToKey這樣當放開按鍵時,就知道該停止哪個聲音。
為沉浸式 VR 模式調整鋼琴
到現在為止,你大概已經用滑鼠 (或觸控螢幕) 彈過鋼琴,因為你加入了互動功能。 在本節中,我們將進入沉浸式 VR 領域。
要在沉浸式 VR 頭戴裝置中開啟該頁面,您必須先將頭戴裝置連接到開發者機器,並確保它已設定為可用於 Windows Mixed Reality 應用程式。 如果你正在使用 Windows Mixed Reality 模擬器,請確保它已啟用。
你現在會在網頁右下角看到一個沉浸式 VR 按鈕。 點擊它,你就能看到你連接的 XR 裝置裡的鋼琴。
一旦進入虛擬空間,你可能會注意到我們打造的鋼琴非常巨大。 在 VR 世界裡,我們只能站在遊戲底部,用指標指向遠方的按鍵來玩。
我們來縮小鋼琴尺寸,讓它更像現實生活中的普通立式鋼琴。 為此,我們需要使用一個效用函數,允許我們相 對於空間中的某一點進行網格縮放。 將此函式加入 scene.js ()
createScene()之外:const scaleFromPivot = function(transformNode, pivotPoint, scale) { const _sx = scale / transformNode.scaling.x; const _sy = scale / transformNode.scaling.y; const _sz = scale / transformNode.scaling.z; transformNode.scaling = new BABYLON.Vector3(_sx, _sy, _sz); transformNode.position = new BABYLON.Vector3(pivotPoint.x + _sx * (transformNode.position.x - pivotPoint.x), pivotPoint.y + _sy * (transformNode.position.y - pivotPoint.y), pivotPoint.z + _sz * (transformNode.position.z - pivotPoint.z)); }此函數包含三個參數:
-
transformNode:待擴展的
TransformNode -
樞軸點:
Vector3一個表示縮放相對點的物件 - 規模:規模因子
-
transformNode:待擴展的
我們會用這個函數將鋼琴框架和鍵盤縮放成 0.015 倍,並在原點設置樞軸點。 將函式呼叫附加於函
createScene()式後方keyboard.position.y += 80;:// Put this line at the beginning of createScene() const scale = 0.015;// Put this function call after keyboard.position.y += 80; // Scale the entire piano scaleFromPivot(piano, new BABYLON.Vector3(0, 0, 0), scale);別忘了縮放相機位置:
const alpha = 3*Math.PI/2; const beta = Math.PI/50; const radius = 220*scale; // scale the radius const target = new BABYLON.Vector3(0, 0, 0); const camera = new BABYLON.ArcRotateCamera("Camera", alpha, beta, radius, target, scene); camera.attachControl(canvas, true);現在當我們再次進入虛擬實境空間時,鋼琴的大小會和普通的立式鋼琴差不多。
啟用 WebXR 功能
既然我們在 VR 領域已經將鋼琴調整到合適的尺寸,讓我們啟用一些酷炫的 WebXR 功能,來提升我們在該領域的鋼琴演奏體驗。
如果你一直用沉浸式 VR 控制器彈鋼琴,你可能會注意到一次只能使用一個控制器。 讓我們透過 Babylon.js 的 WebXR 功能管理器,啟用 XR 領域的多點指標支援。
在初始化行之後
xrHelper,將以下程式碼加入createScene()函式中:const featuresManager = xrHelper.baseExperience.featuresManager; const pointerSelection = featuresManager.enableFeature(BABYLON.WebXRFeatureName.POINTER_SELECTION, "stable", { xrInput: xrHelper.input, enablePointerSelectionOnAllControllers: true });此外,根據你的起點,你可能會覺得站在鋼琴前有點困難。 如果你熟悉這個沉浸式的 VR 環境,可能已經知道傳送 功能,這是一種讓你能透過指向空間中瞬間移動到另一個位置的功能。
要使用 Babylon.js 的 瞬間移動功能,我們首先需要有一個地面網格,可以在 VR 空間中「站立」。 在函數中加入以下程式碼
createScene()以建立接地:const ground = BABYLON.MeshBuilder.CreateGround("ground", {width: 400, height: 400});傳送支援還包含一個非常實用的功能,稱為 「吸附位置」。 簡言之,snap-to 位置是我們希望用戶能落在的特定位置。
例如,我們可以在鋼琴前設定一個吸附位置,讓使用者在指針指向鋼琴附近時,能輕鬆瞬間移動到該位置。
請在下方附加程式碼以啟用傳送功能並指定吸附點:
const teleportation = featuresManager.enableFeature(BABYLON.WebXRFeatureName.TELEPORTATION, "stable", { xrInput: xrHelper.input, floorMeshes: [ground], snapPositions: [new BABYLON.Vector3(2.4*3.5*scale, 0, -10*scale)], });現在,你應該能輕鬆地瞬間移動到鋼琴前的卡扣點,並且能同時用兩個控制器彈奏兩個鍵。
摘要
恭喜您! 你已經完成我們系列的 Babylon.js 鋼琴組裝教學,並學會了:
- 建立、定位並合併網格,建立鋼琴鍵盤的模型
- 匯入一個 Babylon.js 型的立式鋼琴框架
- 為每個鋼琴鍵新增指標互動
- 根據樞軸點縮放網格大小
- 啟用 WebXR 的關鍵功能,如傳送與多點指標支援
這是 scene.js 和 index.html的最終程式碼:
scene.js
const buildKey = function (scene, parent, props) {
if (props.type === "white") {
/*
Props for building a white key should contain:
note, topWidth, bottomWidth, topPositionX, wholePositionX, register, referencePositionX
As an example, the props for building the middle C white key would be
{type: "white", note: "C", topWidth: 1.4, bottomWidth: 2.3, topPositionX: -0.45, wholePositionX: -14.4, register: 4, referencePositionX: 0}
*/
// Create bottom part
const bottom = BABYLON.MeshBuilder.CreateBox("whiteKeyBottom", {width: props.bottomWidth, height: 1.5, depth: 4.5}, scene);
// Create top part
const top = BABYLON.MeshBuilder.CreateBox("whiteKeyTop", {width: props.topWidth, height: 1.5, depth: 5}, scene);
top.position.z = 4.75;
top.position.x += props.topPositionX;
// Merge bottom and top parts
// Parameters of BABYLON.Mesh.MergeMeshes: (arrayOfMeshes, disposeSource, allow32BitsIndices, meshSubclass, subdivideWithSubMeshes, multiMultiMaterials)
const key = BABYLON.Mesh.MergeMeshes([bottom, top], true, false, null, false, false);
key.position.x = props.referencePositionX + props.wholePositionX;
key.name = props.note + props.register;
key.parent = parent;
return key;
}
else if (props.type === "black") {
/*
Props for building a black key should contain:
note, wholePositionX, register, referencePositionX
As an example, the props for building the C#4 black key would be
{type: "black", note: "C#", wholePositionX: -13.45, register: 4, referencePositionX: 0}
*/
// Create black color material
const blackMat = new BABYLON.StandardMaterial("black");
blackMat.diffuseColor = new BABYLON.Color3(0, 0, 0);
// Create black key
const key = BABYLON.MeshBuilder.CreateBox(props.note + props.register, {width: 1.4, height: 2, depth: 5}, scene);
key.position.z += 4.75;
key.position.y += 0.25;
key.position.x = props.referencePositionX + props.wholePositionX;
key.material = blackMat;
key.parent = parent;
return key;
}
}
const scaleFromPivot = function(transformNode, pivotPoint, scale) {
const _sx = scale / transformNode.scaling.x;
const _sy = scale / transformNode.scaling.y;
const _sz = scale / transformNode.scaling.z;
transformNode.scaling = new BABYLON.Vector3(_sx, _sy, _sz);
transformNode.position = new BABYLON.Vector3(pivotPoint.x + _sx * (transformNode.position.x - pivotPoint.x), pivotPoint.y + _sy * (transformNode.position.y - pivotPoint.y), pivotPoint.z + _sz * (transformNode.position.z - pivotPoint.z));
}
const createScene = async function(engine) {
const scale = 0.015;
const scene = new BABYLON.Scene(engine);
const alpha = 3*Math.PI/2;
const beta = Math.PI/50;
const radius = 220*scale;
const target = new BABYLON.Vector3(0, 0, 0);
const camera = new BABYLON.ArcRotateCamera("Camera", alpha, beta, radius, target, scene);
camera.attachControl(canvas, true);
const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
light.intensity = 0.6;
const keyParams = [
{type: "white", note: "C", topWidth: 1.4, bottomWidth: 2.3, topPositionX: -0.45, wholePositionX: -14.4},
{type: "black", note: "C#", wholePositionX: -13.45},
{type: "white", note: "D", topWidth: 1.4, bottomWidth: 2.4, topPositionX: 0, wholePositionX: -12},
{type: "black", note: "D#", wholePositionX: -10.6},
{type: "white", note: "E", topWidth: 1.4, bottomWidth: 2.3, topPositionX: 0.45, wholePositionX: -9.6},
{type: "white", note: "F", topWidth: 1.3, bottomWidth: 2.4, topPositionX: -0.55, wholePositionX: -7.2},
{type: "black", note: "F#", wholePositionX: -6.35},
{type: "white", note: "G", topWidth: 1.3, bottomWidth: 2.3, topPositionX: -0.2, wholePositionX: -4.8},
{type: "black", note: "G#", wholePositionX: -3.6},
{type: "white", note: "A", topWidth: 1.3, bottomWidth: 2.3, topPositionX: 0.2, wholePositionX: -2.4},
{type: "black", note: "A#", wholePositionX: -0.85},
{type: "white", note: "B", topWidth: 1.3, bottomWidth: 2.4, topPositionX: 0.55, wholePositionX: 0},
]
// Transform Node that acts as the parent of all piano keys
const keyboard = new BABYLON.TransformNode("keyboard");
// Register 1 through 7
var referencePositionX = -2.4*14;
for (let register = 1; register <= 7; register++) {
keyParams.forEach(key => {
buildKey(scene, keyboard, Object.assign({register: register, referencePositionX: referencePositionX}, key));
})
referencePositionX += 2.4*7;
}
// Register 0
buildKey(scene, keyboard, {type: "white", note: "A", topWidth: 1.9, bottomWidth: 2.3, topPositionX: -0.20, wholePositionX: -2.4, register: 0, referencePositionX: -2.4*21});
keyParams.slice(10, 12).forEach(key => {
buildKey(scene, keyboard, Object.assign({register: 0, referencePositionX: -2.4*21}, key));
})
// Register 8
buildKey(scene, keyboard, {type: "white", note: "C", topWidth: 2.3, bottomWidth: 2.3, topPositionX: 0, wholePositionX: -2.4*6, register: 8, referencePositionX: 84});
// Transform node that acts as the parent of all piano components
const piano = new BABYLON.TransformNode("piano");
keyboard.parent = piano;
// Import and scale piano frame
BABYLON.SceneLoader.ImportMesh("frame", "https://raw.githubusercontent.com/MicrosoftDocs/mixed-reality/docs/mixed-reality-docs/mr-dev-docs/develop/javascript/tutorials/babylonjs-webxr-piano/files/", "pianoFrame.babylon", scene, function(meshes) {
const frame = meshes[0];
frame.parent = piano;
});
// Lift the piano keyboard
keyboard.position.y += 80;
// Scale the entire piano
scaleFromPivot(piano, new BABYLON.Vector3(0, 0, 0), scale);
const pointerToKey = new Map()
const pianoSound = await Soundfont.instrument(new AudioContext(), 'acoustic_grand_piano');
scene.onPointerObservable.add((pointerInfo) => {
switch (pointerInfo.type) {
case BABYLON.PointerEventTypes.POINTERDOWN:
// Only take action if the pointer is down on a mesh
if(pointerInfo.pickInfo.hit) {
let pickedMesh = pointerInfo.pickInfo.pickedMesh;
let pointerId = pointerInfo.event.pointerId;
if (pickedMesh.parent === keyboard) {
pickedMesh.position.y -= 0.5; // Move the key downward
pointerToKey.set(pointerId, {
mesh: pickedMesh,
note: pianoSound.play(pointerInfo.pickInfo.pickedMesh.name) // Play the sound of the note
});
}
}
break;
case BABYLON.PointerEventTypes.POINTERUP:
let pointerId = pointerInfo.event.pointerId;
// Only take action if the released pointer was recorded in pointerToKey
if (pointerToKey.has(pointerId)) {
pointerToKey.get(pointerId).mesh.position.y += 0.5; // Move the key upward
pointerToKey.get(pointerId).note.stop(); // Stop the sound of the note
pointerToKey.delete(pointerId);
}
break;
}
});
const xrHelper = await scene.createDefaultXRExperienceAsync();
const featuresManager = xrHelper.baseExperience.featuresManager;
featuresManager.enableFeature(BABYLON.WebXRFeatureName.POINTER_SELECTION, "stable", {
xrInput: xrHelper.input,
enablePointerSelectionOnAllControllers: true
});
const ground = BABYLON.MeshBuilder.CreateGround("ground", {width: 400, height: 400});
featuresManager.enableFeature(BABYLON.WebXRFeatureName.TELEPORTATION, "stable", {
xrInput: xrHelper.input,
floorMeshes: [ground],
snapPositions: [new BABYLON.Vector3(2.4*3.5*scale, 0, -10*scale)],
});
return scene;
}
index.html
<html>
<head>
<title>Babylon Template</title>
<script src="https://cdn.babylonjs.com/babylon.js"></script>
<script src="scene.js"></script>
<script src="soundfont-player.min.js"></script>
<style>
body,#renderCanvas { width: 100%; height: 100%;}
</style>
</head>
<body>
<canvas id="renderCanvas"></canvas>
<script>
const canvas = document.getElementById("renderCanvas"); // Get the canvas element
const engine = new BABYLON.Engine(canvas, true); // Generate the BABYLON 3D engine
// Register a render loop to repeatedly render the scene
createScene(engine).then(sceneToRender => {
engine.runRenderLoop(() => sceneToRender.render());
});
// Watch for browser/canvas resize events
window.addEventListener("resize", function () {
engine.resize();
});
</script>
</body>
</html>
後續步驟
欲了解更多關於 Mixed Reality JavaScript 開發的資訊,請參閱 JavaScript 開發概述。