发布于 2026-01-06 3 阅读
0

使用 BabylonJS 构建 Web VR 游戏 DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

使用 BabylonJS 和 JavaScript 构建 Web VR 游戏

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

在本教程中,我将一步一步地向您展示如何使用 BabylonJS 构建 Web VR 游戏。

游戏图片

先决条件

- nodejs
- vs 代码

BabylonJS 和 CannonJS 是什么?

BabylonJS是一个完整的 JavaScript 框架,用于使用 HTML5、WebGL、WebVR 和 Web Audio 构建 3D 游戏和体验。

CannonJS是一个用 JavaScript 编写的物理引擎。你可能会问,什么是物理引擎?简单来说,它是“一种软件,用于对某些物理系统进行近似模拟,例如刚体动力学(包括碰撞检测)、软体动力学和流体动力学,可应用于计算机图形学、视频游戏和电影等领域。”

首先,我们需要使用 Babylon.js、Webpack 和 TypeScript 获取一个基础的启动项目。

运行入门项目的步骤和Git 仓库链接

  1. 克隆仓库git clone https://github.com/cassieview/babylonjs-webpack-typescript-starter-project.git cd babylonjs-webpack-typescript-starter-project
  2. 安装包npm install
  3. 建设项目npm run build
  4. 运行脚本测试项目npm start
  5. 在 VS Code 中打开code .

我们来谈谈启动项目。

简单的 index.html 模板。

<!DOCTYPE html>
<html>

    <head>
        <style>
            html,
            body {
                overflow: hidden;
                width: 100%;
                height: 100%;
                margin: 0;
                padding: 0;
                text-align: center;
            }

            #renderCanvas {
                width: 100%;
                height: 100%;
                touch-action: none;
            }
        </style>
    </head>

    <body>
        <canvas id="renderCanvas"></canvas>
        <script src="dist/index.js"></script>
    </body>

</html>
Enter fullscreen mode Exit fullscreen mode

index.ts TypeScript 文件

index.ts 文件是创建主场景的 TypeScript 文件。它是在 dist 文件夹中被转译成 JavaScript 的 TypeScript 文件。

游戏脚本源文件位于 dist 文件夹中。Webpack 是一个开源的 JavaScript 模块打包工具,它会生成代表这些模块的静态资源。这些静态资源就是从 dist 文件夹中加载的。Webpack 会将脚本编译成一个单独的源文件,并用它来运行游戏脚本。

首先,我们从 BabylonJS 导入创建游戏场景所需的包。创建 canvas 变量,并使用原生 JavaScript 从 HTML 的 body 部分获取 renderCanvas 画布标签。然后,我们创建引擎并传入 BabylonJS 引擎。


import { Engine, Scene, HemisphericLight, Vector3, MeshBuilder, Mesh } from "babylonjs";
var canvas: any = document.getElementById("renderCanvas");
var engine: Engine = new Engine(canvas, true);

Enter fullscreen mode Exit fullscreen mode

接下来是创建场景函数。在这里,我们定义场景,并传入引擎。然后我们创建一个摄像机。摄像机代表游戏玩家的视角。我们使用的是通用摄像机

接下来,我们在场景中添加一个简单的球体网格并设置其基本属性。VR 辅助程序会在屏幕右下角添加一个 VR 按钮,以便用户进入 VR 游戏。但这在浏览器中查看游戏和进行测试时会造成一些问题。为了方便测试,我建议注释掉这行代码。然后,当您想使用 VR 头显进行测试时,再取消注释即可进入 VR 游戏。

提示:您可以轻松测试所做的更改,只需运行命令,npm run build然后在浏览器中打开 index.html 文件即可C:/Code/babylonjs-webpack-typescript-starter-project/index.html。这是一个静态网站,因此您实际上无需运行命令npm start。只需运行构建并刷新浏览器,即可打开 index.html 文件。

function createScene(): Scene {
    // Create scene
    var scene: Scene = new Scene(engine);

    // Create camera
    var camera = new BABYLON.UniversalCamera("UniversalCamera", new BABYLON.Vector3(0, 0, -10), scene);

    // Create sphere
    var sphere1: Mesh = MeshBuilder.CreateSphere("sphere", { diameter: 1 }, scene);
    sphere1.position.y = 5;
    sphere1.material = new BABYLON.StandardMaterial("sphere material", scene)

    // Enable VR
    var vrHelper = scene.createDefaultVRExperience();
    vrHelper.enableInteractions();

    return scene;
}

var scene: Scene = createScene();

engine.runRenderLoop(() => {
    scene.render();
});


Enter fullscreen mode Exit fullscreen mode

开始构建游戏

现在你应该对入门项目的内容以及 Babylon.js 的功能有了基本的了解。接下来,我们要添加重力效果,所以需要用到上面提到的 Cannon.js 库。

import { Engine, Scene, ArcRotateCamera, HemisphericLight, Vector3, MeshBuilder, Mesh, CannonJSPlugin } from "babylonjs";

复制这段代码块并粘贴到场景变量下方。这里我们添加了地面网格,并赋予它一个属性physicsImpostor,使球体能够落下并落在地面上。

var gravityVector = new BABYLON.Vector3(0, -1, 0);
    scene.enablePhysics(gravityVector, new CannonJSPlugin);

    var light = new HemisphericLight("light",Vector3.Zero(),scene);

    // Parameters : name, position, scene
    var camera = new BABYLON.UniversalCamera("UniversalCamera", new BABYLON.Vector3(0, 0, -10), scene);
    camera.checkCollisions = true;
    camera.applyGravity = true;
    // Targets the camera to a particular position. In this case the scene origin
    camera.setTarget(BABYLON.Vector3.Zero());

    // Attach the camera to the canvas
    camera.attachControl(canvas, true);

    // Create Ground
    var ground = BABYLON.Mesh.CreatePlane("ground", 25.0, scene);
    ground.position = new BABYLON.Vector3(0, -10, 0);
    ground.rotation = new BABYLON.Vector3(Math.PI / 2, 0, 0);

    ground.material = new BABYLON.StandardMaterial("groundMat", scene);
    ground.material.backFaceCulling = false;
    ground.receiveShadows = true;
    ground.physicsImpostor = new BABYLON.PhysicsImpostor(ground, BABYLON.PhysicsImpostor.BoxImpostor, { mass: 0, friction: 1, restitution: 0 }, scene);

Enter fullscreen mode Exit fullscreen mode

为球体添加物理效果、阴影和光照:

import { Engine, Scene, ArcRotateCamera, HemisphericLight, Vector3, MeshBuilder, Mesh, CannonJSPlugin, ShadowGenerator, DirectionalLight } from "babylonjs";



// Create sphere
    var sphereLight = new DirectionalLight("dir02", new Vector3(0.2, -1, 0), scene);
    sphereLight.position = new Vector3(0, 80, 0);

    var sphere1: Mesh = MeshBuilder.CreateSphere("sphere", { diameter: 1 }, scene);
    sphere1.position.y = 5;
    sphere1.material = new BABYLON.StandardMaterial("sphere material", scene)
    sphere1.physicsImpostor = new BABYLON.PhysicsImpostor(sphere1, BABYLON.PhysicsImpostor.SphereImpostor, { mass: 1 }, scene);
    var shadowGenerator = new ShadowGenerator(2048, sphereLight);
    shadowGenerator.addShadowCaster(sphere1);

Enter fullscreen mode Exit fullscreen mode

现在我们看到一个球体落到我们制作的地面面板上。真令人兴奋。

项目架构

我们还有很多内容要添加到这个游戏中,虽然我们可以把所有内容都放在一个巨大的函数里,但出于各种原因,这不是最佳实践。让我们新建一个sphere.ts文件,把球体逻辑移到里面。


import { Scene, Vector3, MeshBuilder, Mesh, ShadowGenerator, DirectionalLight } from "babylonjs";


export function addSphere(scene: Scene) {

    // Create sphere
    var sphereLight = new DirectionalLight("dir02", new Vector3(0.2, -1, 0), scene);
    sphereLight.position = new Vector3(0, 80, 0);

    var sphere: Mesh = MeshBuilder.CreateSphere("sphere", { diameter: 1 }, scene);
    sphere.position.y = 5;
    sphere.material = new BABYLON.StandardMaterial("sphere material", scene)
    sphere.physicsImpostor = new BABYLON.PhysicsImpostor(sphere, BABYLON.PhysicsImpostor.SphereImpostor, { mass: 1 }, scene);
    var shadowGenerator = new ShadowGenerator(2048, sphereLight);
    shadowGenerator.addShadowCaster(sphere);
}

Enter fullscreen mode Exit fullscreen mode

然后返回index.ts并导入我们创建的文件,并调用之前逻辑所在的addSphere函数。addSphere


line 2: import { addSphere } from "./sphere";
line 35:  addSphere(scene);

Enter fullscreen mode Exit fullscreen mode

现在最好npm run build刷新一下浏览器,看看逻辑操作是否成功完成。

添加开始按钮

好吧,就像任何一款好游戏一样,你需要一个开始按钮,你知道,用来开始游戏。

导入 gui 库,以便我们可以使用 3D 按钮和面板。

import * as GUI from  "babylonjs-gui";
Enter fullscreen mode Exit fullscreen mode

startGameButton在现有函数下方添加该函数createScene。将addSphere函数调用移至button.onPointerUpObservable事件处理程序中。此事件用于在点击时触发其他事件。


var startGameButton = function (panel) {
    var button = new GUI.Button3D();
    panel.addControl(button);
    button.onPointerUpObservable.add(function () {
        addSphere(scene);
    });
    var text1 = new GUI.TextBlock();
    text1.text = "Start Game";
    text1.color = "white";
    text1.fontSize = 24;
    button.content = text1;
}
Enter fullscreen mode Exit fullscreen mode

更新createScene函数,将按钮添加到场景中。这将添加到addSphere之前所在的第 35 行。


    // Create the 3D UI manager
    var manager = new GUI.GUI3DManager(scene);
    // Create a horizontal stack panel
    var panel = new GUI.StackPanel3D();
    panel.margin = 0.02;
    manager.addControl(panel);
    startGameButton(panel);

Enter fullscreen mode Exit fullscreen mode

现在正是测试你所做的更改的好时机npm run build。点击按钮后,球体应该会从空中落到地面上。

点击使球体消失

为此,我们需要打开sphere.ts文件并添加代码,ActionManager这样sphere当我们点击球体时,它就会消失。从第 17 行开始添加以下逻辑。此外,您还需要更新文件顶部的导入语句,以包含ActionManagerExecuteCodeAction

import { Scene, Vector3, MeshBuilder, Mesh, ShadowGenerator, DirectionalLight, ActionManager, ExecuteCodeAction } from "babylonjs";
Enter fullscreen mode Exit fullscreen mode

    sphere.actionManager = new ActionManager(scene);

    //add click event to sphere
    sphere.actionManager.registerAction(new 
    ExecuteCodeAction(ActionManager.OnPickUpTrigger, function () {

        scene.removeMesh(sphere);

    }));

Enter fullscreen mode Exit fullscreen mode

点击“开始”按钮,即可添加循环以添加多个球体。

sphere.ts在函数上方添加以下代码addSphere。这样,点击按钮时就会添加 10 个球体,而不是一个。由于我们将不再直接从文件中调用该函数,addSphere因此请更新该函数var addSphere = function (scene: Scene) {index.ts

export function addSpheres(scene: Scene) {
    for (let index = 0; index < 10; index++) {
        addSphere(scene);
    }
}
Enter fullscreen mode Exit fullscreen mode

更新index.ts文件,导入该addSpheres函数并调用该函数,而不是调用addSphere.

line 3: import { addSpheres } from "./sphere";
line 54: addSpheres(scene);
Enter fullscreen mode Exit fullscreen mode

然后更新文件中的球体位置,sphere.ts避免在同一位置创建 10 个球体。删除sphere.position.y = 5;并添加

    line 17: sphere.position = new Vector3(Math.random() * 20 - 10, 10, Math.random() * 10 - 5);
Enter fullscreen mode Exit fullscreen mode

给球体添加粒子动画以模拟爆炸效果

球体消失的效果很酷,但为了让它更具戏剧性,我们可以添加一个particleSystem以卡通爆炸方式喷射粒子的效果。

添加一个名为 `<filename>` 的新文件particles.ts,并将以下代码粘贴到该文件中:

import { AbstractMesh, Texture, ParticleSystem, Scene, Vector3, Color4, Animation } from "babylonjs";
import { AdvancedDynamicTexture } from "babylonjs-gui";


let advancedTexture: AdvancedDynamicTexture;

export function addParticlesToMesh(mesh: AbstractMesh, scene: Scene): ParticleSystem {
    // Fountain object
    //var fountain = Mesh.CreateBox("foutain", 1.0, scene);

    var particleSystem = new ParticleSystem("particles", 2000, scene);

    //Texture of each particle
    particleSystem.particleTexture = new Texture("textures/flare.png", scene);

    // Where the particles come from
    particleSystem.emitter = mesh; // the starting object, the emitter
    particleSystem.minEmitBox = new Vector3(-1, 0, 0); // Starting all from
    particleSystem.maxEmitBox = new Vector3(1, 0, 0); // To...

    // Colors of all particles
    particleSystem.color1 = new Color4(0.7, 0.8, 1.0, 1.0);
    particleSystem.color2 = new Color4(0.2, 0.5, 1.0, 1.0);
    particleSystem.colorDead = new Color4(0, 0, 0.2, 0.0);

    // Size of each particle (random between...
    particleSystem.minSize = 0.1;
    particleSystem.maxSize = 0.5;

    // Life time of each particle (random between...
    particleSystem.minLifeTime = 0.3;
    particleSystem.maxLifeTime = 1.5;

    // Emission rate
    particleSystem.emitRate = 1500;

    // Blend mode : BLENDMODE_ONEONE, or BLENDMODE_STANDARD
    particleSystem.blendMode = ParticleSystem.BLENDMODE_ONEONE;

    // Set the gravity of all particles
    particleSystem.gravity = new Vector3(0, -9.81, 0);

    // Direction of each particle after it has been emitted
    particleSystem.direction1 = new Vector3(-7, 8, 3);
    particleSystem.direction2 = new Vector3(7, 8, -3);

    // Angular speed, in radians
    particleSystem.minAngularSpeed = 0;
    particleSystem.maxAngularSpeed = Math.PI;

    // Speed
    particleSystem.minEmitPower = 1;
    particleSystem.maxEmitPower = 3;
    particleSystem.updateSpeed = 0.005;

    // Start the particle system
    particleSystem.start();

    // Fountain's animation
    var keys = [];
    var animation = new Animation("animation", "rotation.x", 30, Animation.ANIMATIONTYPE_FLOAT,
        Animation.ANIMATIONLOOPMODE_CYCLE);
    // At the animation key 0, the value of scaling is "1"
    keys.push({
        frame: 0,
        value: 0
    });

    // At the animation key 50, the value of scaling is "0.2"
    keys.push({
        frame: 50,
        value: Math.PI
    });

    // At the animation key 100, the value of scaling is "1"
    keys.push({
        frame: 100,
        value: 0
    });

    // Launch animation
    animation.setKeys(keys);
    mesh.animations.push(animation);
    scene.beginAnimation(mesh, 0, 100, true);

    return particleSystem;
}

export function removeParticlesFromMesh(particleSystem: ParticleSystem): any {
    particleSystem.stop();
}


Enter fullscreen mode Exit fullscreen mode

particles.ts脚本导入到spheres.ts脚本中。

import { addParticlesToMesh, removeParticlesFromMesh } from "./particles";
Enter fullscreen mode Exit fullscreen mode

更新球体的点击事件,并添加休眠函数。这样,​​当球体被点击时,粒子会添加到球体上,等待 250 毫秒后停止添加粒子。如果不停止添加粒子,即使球体已从场景中移除,粒子仍然会遍布整个场景。

    sphere.actionManager.registerAction(new 
        ExecuteCodeAction(ActionManager.OnPickUpTrigger, function () {
        var particleSystem = addParticlesToMesh(sphere, scene);
        scene.removeMesh(sphere);
        sleep(250).then(() => {
            removeParticlesFromMesh(particleSystem);
        })

    }));

    const sleep = (milliseconds) => {
        return new Promise(resolve => setTimeout(resolve, milliseconds))
    }
Enter fullscreen mode Exit fullscreen mode

添加 score.ts 文件,因为每个游戏都需要一种计分方式。

创建score.ts脚本并将以下代码粘贴到脚本中。

import { AdvancedDynamicTexture, Rectangle, Control, TextBlock } from 'babylonjs-gui';

let advancedTexture: AdvancedDynamicTexture;
let scoreText: TextBlock = new TextBlock();
let score = 0;
function init(): void {
    if (!advancedTexture) {
        advancedTexture = AdvancedDynamicTexture.CreateFullscreenUI("ui1");
    }
}

export function addLabelToScene(): void {

    if (!advancedTexture) {
        init();
    }
    let label = new Rectangle("score");
    label.background = "black";
    label.height = "30px";
    label.alpha = 0.5;
    label.width = "100px";
    label.cornerRadius = 20;
    label.thickness = 1;
    label.linkOffsetY = 30;
    label.top = "10%";
    label.zIndex = 5;
    label.verticalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
    advancedTexture.addControl(label);


    scoreText.text = "score: 0"
    scoreText.color = "white";
    label.addControl(scoreText);
}
export function incrementScore(): void{
    score++;
    scoreText.text = "score: " + score.toString();

}


export function updateScore(newScore: number): void{
    score = newScore;
    scoreText.text = "score: " + score.toString();

}


Enter fullscreen mode Exit fullscreen mode

然后将脚本导入到index.ts脚本中。

import { addLabelToScene, updateScore } from "./score";
Enter fullscreen mode Exit fullscreen mode

我们希望在文件中添加按钮之后index.ts添加函数调用,并且希望在点击按钮时重置分数。addLabelToScene(panel)startGameButton(panel);startGameButton

var startGameButton = function (panel) {
    var button = new GUI.Button3D();
    panel.addControl(button);
    button.onPointerUpObservable.add(function () {
        //reset score
        updateScore(0);
        addSpheres(scene);
    });
    var text1 = new GUI.TextBlock();
    text1.text = "Start Game";
    text1.color = "white";
    text1.fontSize = 24;
    button.content = text1;
}
Enter fullscreen mode Exit fullscreen mode

sphere.ts我们需要从脚本import { incrementScore } from "./score";中获取信息score.ts,然后在点击球体时添加后缀以增加分数incrementScore();removeParticlesFromMesh(particleSystem);

将球从地面网格中取出PhysicsImpostor,使球落入地下而不是留在地面上。

我们不希望玩家能够击打地面上的球,所以我们需要删除PhysicsImpostor地面网格。

ground.physicsImpostor = new BABYLON.PhysicsImpostor(ground, BABYLON.PhysicsImpostor.BoxImpostor, { mass: 0, friction: 0, restitution: 0 }, scene);

Enter fullscreen mode Exit fullscreen mode

最后,我们将在球体上添加一些材料。

将巴比伦素材导入脚本sphere.ts

import {StandardMaterial, Texture, Color3} from "babylonjs-materials";
Enter fullscreen mode Exit fullscreen mode

然后使用以下代码将材质添加到球体网格中。


    // Material
    var materialAmiga = new StandardMaterial("amiga", scene);
    materialAmiga.diffuseTexture = new Texture("textures/amiga.jpg", scene);
    materialAmiga.emissiveColor = new Color3(0.5, 0.5, 0.5);
    sphere.material = materialAmiga;

Enter fullscreen mode Exit fullscreen mode

好,我们来npm run build看看行不行!

还可以添加更多酷炫功能

  • 质地
  • 背景
  • 自定义网格
  • 音效
  • 天空才是极限!

将网站作为静态网站部署到 Azure 存储,这样你的所有朋友都可以一起玩了。

请查看文档,了解如何在 Azure 上以低成本托管此网站。

项目结束时的完整 Git 仓库

特别感谢 Babylonjs 出色的文档和示例以及辛勤工作的开发者们,他们创建了这个优秀的库,使我们能够构建游戏

祝您游戏制作愉快!

文章来源:https://dev.to/azure/build-a-web-vr-game-with-javascript-using-the-babylonjs-framework-aek