iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0
佛心分享-IT 人自學之術

從空拍到3D展示系列 第 24 篇

Day 24. 放入三個彩蛋,在 Three.js 找到它們

  • 分享至 

  • xImage
  •  

今天任務

之前的遊戲只有 1 個彩蛋。今天把場景擴充成 3 個彩蛋:

  1. 在 Blender 裡埋三個彩蛋
  2. 匯出成 .glb
  3. 修改程式碼,把「找一個彩蛋」改成「找一組彩蛋」,並確認在 Three.js 裡找到的彩蛋,和我在 Blender 裡放的位置一致。

一、在 Blender 埋三個彩蛋

彩蛋的角色來自 Quaternius,授權條款請以官方 License 頁面為準。

匯入

角色是 .glb 檔,所以用 glTF 的匯入選項,不是 WebODM 地形用的 Wavefront (.obj):

File → Import → glTF 2.0 (.glb/.gltf)

這次挑了 3 個角色,每個角色各匯入一次。匯入後,新角色通常會出現在原點附近,大小也不一定和公園成比例,所以每匯入一個,就先把它移到位置、調整大小,再匯入下一個,不然新舊角色會疊在一起,不好分辨。

埋彩蛋的快捷鍵

先用滑鼠左鍵點一下角色,或在右上角的大綱(Outliner)點它的名字,再用下面的操作:

想做的事 操作
移動 按 G,移動滑鼠,點左鍵確定,按 Esc 取消
縮放 按 S,移動滑鼠,點左鍵確定
旋轉 按 R,移動滑鼠,點左鍵確定
限制方向 按 G 之後再按 X、Y 或 Z,就只會沿著那個軸移動
輸入精確數字 按 G 再按 Z,直接打數字,例如 2,按 Enter

操作說明可對照 Blender 官方手冊的 Move 頁面。

多次調整角度與高度後,就能進行匯出

匯出 .glb

選取地形和 3 個角色,File → Export → glTF 2.0 (.glb/.gltf),格式選 glTF Binary (.glb)。


二、Three.js 怎麼找到這 3 個彩蛋

原本的程式怎麼找彩蛋

載入模型後,程式用物件的名稱去找彩蛋,找到後在它身上做兩件事:貼上標籤(userData.isEgg),並記錄「有沒有被找到」(userData.found):

const characterArmature = gltf.scene.getObjectByName('CharacterArmature');
if (characterArmature) {
  characterArmature.userData.isEgg = true;
  characterArmature.userData.found = false;
  updateEgg()          // 彩蛋總數 +1,更新畫面
} else {
  console.warn('找不到 CharacterArmature,請確認匯出的模型裡名稱是否一致');
}

這裡有兩個重點:

  • getObjectByName('CharacterArmature') 只會回傳第一個名稱符合的物件。這次的 3 個角色名稱各不相同,所以要對每個名稱各找一次。
  • updateEgg() 每呼叫一次,彩蛋總數就加 1,所以找到 3 個彩蛋,就要呼叫 3 次。

點擊時,程式拿射線打到的物件,往上一層一層找,看祖先裡有沒有貼了 isEgg 的。這個函式只看標籤,不看名稱,所以這次不用改:

function findEggRoot(object) {
  if (object.userData.isEgg) return object;
  let root = null;
  object.traverseAncestors((ancestor) => {
    if (ancestor.userData.isEgg) root = ancestor;
  });
  return root;
}

怎麼知道模型裡的名稱

名稱要看載入後的結果。載入完成後,用 traverse 把每個物件的名稱和型別印到 Console:

gltf.scene.traverse((obj) => console.log(obj.name, `(${obj.type})`));

Blender 的名稱,和 Three.js 看到的名稱可能不同

Blender 裡重複匯入的物件,名稱會帶 .001、.002 這樣的尾巴。但 Three.js 載入 glTF 時,會先處理名稱:

  • 依 r160 版 GLTFLoader 的 createUniqueName,名稱會交給 PropertyBinding.sanitizeNodeName。
  • 依 r160 版 PropertyBinding 的 sanitizeNodeName,空白會換成底線,[、]、.、:、/ 這幾個字元會被移除。

我把同樣的規則放進 Node 跑了一次:

Blender 裡的名稱 經過規則處理後
EnemyArmature EnemyArmature
AnimalArmature.001 AnimalArmature001
AnimalArmature.002 AnimalArmature002

改成找一組彩蛋

★ 今天新加:把「找一個名稱」改成「對一組名稱各找一次」,取代上面原本那一段:

// ★ 3 個彩蛋在 Three.js 裡的名稱(以 Console 印出的為準)
const EGG_NAMES = ['EnemyArmature', 'AnimalArmature001', 'AnimalArmature002'];

EGG_NAMES.forEach((name) => {                         // ★ 對每個名稱各找一次
  const egg = gltf.scene.getObjectByName(name);
  if (egg) {
    egg.userData.isEgg = true;
    egg.userData.found = false;
    updateEgg();                                      // 每找到一個,總數 +1
  } else {
    console.warn(`找不到 ${name},請確認匯出的模型裡名稱是否一致`);
  }
});

這樣如果有名稱對不上,Console 會出現警告,左上角的總數也會少於 3,比較容易發現。


三、結果:實測遊戲功能

  • 數量計算,確實有動態調整成 3 個。
  • 點擊彩蛋也有跳出找到通知
  • 最後全部找到後,有正確顯示時間結果
    https://ithelp.ithome.com.tw/upload/images/20261008/20112674pTRGUV6Fg4.png

https://ithelp.ithome.com.tw/upload/images/20261008/20112674ddRPNLlE8k.png

小結

第二次操作後,可以感受到花最多時間的是在 blender,從去除周邊但放入彩蛋,都需要不斷操作角度、高度、甚至物件 (彩蛋) 大小。坦白說,Three.js 寫完一版後,就算是個框架,可以不斷換掉 Blender 輸出的 glb 檔案,整個作品的視覺關鍵,來自於 Blender,只要在這一步彩蛋放得好,模型處理得夠完整,就會是很高完成度的作品了!

Blender 再度展了好多時間,還好在前幾天程式碼沒有寫太死,也做過一版更新
所以一旦匯出 Blender 檔案後,很快就找到程式要調整的位置。

接下來

作品告一段落,大概還有 6 天時間,和 AI 討論,發現還可做像是進度條、還有測試手機上是否也能方便縮放、旋轉的操作,我認為是個很棒的想法
接下來就往這方面前進,再把最終完整版本放到 GitHub io 上面,分享給朋友試試~


上一篇
Day 23. 溫故整個流程: 3D 模型任務飛行,以及花掉大半天的 Blender 去邊
下一篇
Day 25. 進度條原理與手機測試準備
系列文
從空拍到3D展示 共 25 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言