本系列文已改編成書「甚麼?網頁也可以做派對遊戲?使用 Vue 和 babylon.js 打造 3D 派對遊戲吧!」
書中不只重構了程式架構、改善了介面設計,還新增了 2 個新遊戲呦!ˋ( ° ▽、° )
新遊戲分別使用了陀螺儀與震動回饋,趕快買書來研究研究吧!ლ(╹∀╹ლ)
在此感謝深智數位的協助,歡迎大家前往購書,鱈魚感謝大家 (。・∀・)。
助教:「所以到底差在哪啊?沒圖沒真相,被你坑了都不知道。(´。_。`)」
鱈魚:「你對我是不是有甚麼很深的偏見啊 (っ °Д °;)っ,來人啊,上連結!」
讓我們利用 babylon.js 打造 3D 遊戲吧!◝( •ω• )◟
babylon.js 產生遊戲畫面需要以下幾個步驟。
這樣才能產生一個看的到的畫面。
讓我們引入 babylon.js 並依序新增建立場景所需的 function 吧。
import { ArcRotateCamera, Engine, Scene, Vector3 } from '@babylonjs/core';
...
const canvas = ref<HTMLCanvasElement>();
let engine: Engine;
let scene: Scene;
function createEngine(canvas: HTMLCanvasElement) {
  const engine = new Engine(canvas, true);
  return engine;
}
function createScene(engine: Engine) {
  const scene = new Scene(engine);
  /** 使用預設光源 */
  scene.createDefaultLight();
  return scene;
}
function createCamera(scene: Scene) {
  const camera = new ArcRotateCamera(
    'camera',
    -Math.PI / 2,
    Math.PI / 4,
    34,
    new Vector3(0, 0, -2),
    scene
  );
  return camera;
}
參考資料:
接著增加 init() 內容,開始建立場景。
function init() {
  if (!canvas.value) {
    console.error('無法取得 canvas DOM');
    return;
  }
  engine = createEngine(canvas.value);
  scene = createScene(engine);
  createCamera(scene);
  /** 反覆渲染場景,這樣畫面才會持續變化 */
  engine.runRenderLoop(() => {
    scene.render();
  });
  loading.hide();
}
現在讓我們在 onMounted 中呼叫 init() 吧。
<template>
...
</template>
<script setup lang="ts">
import { ArcRotateCamera, Engine, Scene, Vector3 } from '@babylonjs/core';
import { onMounted, ref } from 'vue';
... 
function createEngine(canvas: HTMLCanvasElement) { ... }
function createScene(engine: Engine) { ... }
function createCamera(scene: Scene) { ... }
function init() { ... }
onMounted(() => {
  init();
});
</script>
熟悉 Vue 的讀者們一定都知道會在 onMounted 中才初始化,是因為這樣才取得到 canvas DOM。
接著是當組件銷毀前,需要關閉引擎。
onBeforeUnmount(() => {
  engine.dispose();
});
再加入考慮畫面隨視窗自動縮放的功能。
onMounted(() => {
  init();
  window.addEventListener('resize', handleResize);
});
onBeforeUnmount(() => {
  engine.dispose();
  window.removeEventListener('resize', handleResize);
});
function handleResize() {
  engine.resize();
}
最後暫時取消 game-console 自動跳轉回首頁的限制,以免每次需要重新整理網頁的時候一直跳回首頁。
src\views\game-console.vue
...
<script setup lang="ts">
...
function init() {
  // 房間 ID 不存在,跳回首頁
  // if (!gameConsoleStore.roomId) {
  //   router.push({
  //     name: RouteName.HOME
  //   });
  //   loading.hide();
  //   return;
  // }
  ...
  // 跳轉至遊戲大通
  // router.push({
  //   name: RouteName.GAME_CONSOLE_LOBBY
  // });
}
init();
</script>
現在讓我們按下「開始遊戲」產生 3D 場景吧!

鱈魚:「出現了!ᕕ( ゚ ∀。)ᕗ」
助教:「出個毛線!這坨深藍色我用 CSS 都能產生!⎝(° Д °⎝)」
鱈魚:「那是背景預設的顏色啦,讓我們慢慢來嘛。(´,,•ω•,,)」
組成一個完整 3D 物件最基本構成是 Mesh 與 Material,前者負責組成「骨架」,後者負責「皮膚」。
現在讓我們建立一片海洋(其實就是一片地板),過程如下:
建立海洋要用的網格
這裡使用 MeshBuilder.CreateGround,可以產生一個不具厚度的網格,因為地板不需要厚度。
建立材質
將顏色設定為藍色後附加於網格上。
以上步驟便可以完成海洋地板了,現在建立新增海洋的 function。
...
<script setup lang="ts">
import { ArcRotateCamera, BackgroundMaterial, Color3, Engine, MeshBuilder, Scene, Vector3 } from '@babylonjs/core';
...
function createEngine(canvas: HTMLCanvasElement) { ... }
function createScene(engine: Engine) { ... }
function createCamera(scene: Scene) { ... }
function createSea(scene: Scene) {
  const sea = MeshBuilder.CreateGround('sea', { height: 1000, width: 1000 });
  const material = new BackgroundMaterial("seaMaterial", scene);
  material.useRGBColor = false;
  material.primaryColor = new Color3(0.57, 0.70, 0.83);
  sea.material = material;
  return sea;
}
function init() {
  ...
  engine = createEngine(canvas.value);
  scene = createScene(engine);
  createCamera(scene);
  createSea(scene);
  ...
}
...
</script>

鱈魚:「現在是一片淺藍色了!◝( •ω• )◟」
助教:「所以我說那個 3D 呢?(○´・д・)ノ」
鱈魚:「那就讓我們建立浮冰吧!( •̀ ω •́ )✧」
...
<script setup lang="ts">
import {
  ArcRotateCamera, BackgroundMaterial, Color3,
  Engine, MeshBuilder, Scene,
  StandardMaterial, Vector3
} from '@babylonjs/core';
...
function createSea(scene: Scene) { ... }
function createIce(scene: Scene) {
  const ice = MeshBuilder.CreateBox('ice', {
    width: 30,
    depth: 30,
    height: 4,
  });
  ice.material = new StandardMaterial('iceMaterial', scene);
  return ice;
}
function init() {
  ...
  createSea(scene);
  createIce(scene);
  ...
}
...
</script>
冰塊出現了!

鱈魚:「是不是和吃冰一樣簡單啊!♪( ◜ω◝و(و」
助教:「所以我說企鵝哩?(。_。)」
鱈魚:「…( ◜ω◝و(و」
以上程式碼已同步至 GitLab,大家可以前往下載: