別捲了,快來玩 | React+Three.js 實現一個超好玩的3D遊戲:美女與龍珠

語言: CN / TW / HK

點選上方  三分鐘學前端 ,關注公眾號

回覆 交流 ,加入前端程式設計面試演算法每日一題群

面試官也在看的前端面試資料

遊戲故事

遊戲講述的是一個小女生被惡魔詛咒找不到家了,她聽說收集七龍珠可以召喚神龍,神龍可是幫她實現回家的願望,於是她開啟了她的冒險故事

使用技術

這個遊戲使用了以下技術

  1. Vite + React
  2. 基於 Three.jsLingo3D [2]

以及使用了以下工具:

  1. sketchfab :3D模型下載
  2. mixamo :3D人物動作繫結及動畫
  3. readyplayer :3D角色生產工具
  4. gltf.report :模型壓縮
  5. polyhaven :hdr素材庫(環境貼圖)
  6. textures :材質貼圖素材

部分效果實現如下 :point_down:

20.gif
22.gif
24.gif
image.png
image.png
image.png
image.png
25.gif

開始

其實我也不知道從哪裡開始,也不知道寫什麼好?

但我好像需要一個角色,那就從建立角色開始

建立一個角色

下載角色

首先到 sketchfab [3] 中下載一個角色模型

當然如果你是首次使用 sketchfab ,記得先註冊和登入

註冊好之後,就會看到以下介面

image.png

箭頭指的輸入框輸入關鍵字,即可篩選出相關的模型,然後我們從中進行挑選

注意,挑選人物模型時,最好選擇 T-pose [4] 型別,比如 這樣可以方便之後繫結骨骼

我這裡選擇了這下面這個小妹妹,我覺得還不錯

image.png

然後點選右上角的下載按鈕,進入下載介面

image.png

一般fbx使用的比較多,但是有的時候不是fbx的格式,我們需要使用其他工具轉格式,或者處理模型

為了方便,我們這裡選擇第二個:glTF 格式,點選 DOWNLOAD

下載好之後是個壓縮包,我們進行解壓,解壓後目錄如下

image.png

處理角色模型

接著我們選擇使用模型處理軟體來處理我們的模型,比如我這裡選擇 blender

當然得先下載軟體,我們進入 官網 [5] ,然後點選如下按鈕進行下載

image.png

安裝就直接點選下一步下一步就行,之後開啟軟體,看到以下介面

image.png

開始進來中間是有一個立方體的,我們需要移除。步驟是滑鼠選中,按x刪除

之後按如下步驟匯入我們剛剛下載的模型

image.png
image.png

結果如下,表示匯入成功

image.png

但看不到人,可以按x或者Delete將繫結的骨骼刪除,即可展示人物

1.gif

但是人物怎麼沒有顏色,這時因為這時展現的是實體,我們可以按z 甩狙,選擇渲染即可

2.gif

選擇的模型比較正常,所以不用太多的處理,接著我們將它轉成fbx格式,但是在轉之前需要將燈光和相機移除,以防止模型匯入頁面時光線等問題

image.png

可以跟之前一樣選擇中,按x即可刪除

下面開始將它轉成fbx格式,直接按以下步驟匯出即可

image.png

需要注意,匯出時需要將路徑模式改成複製,並開啟內嵌紋理。否則匯出的模型將沒有顏色

image.png

製作動畫

模型有了之後,我們我們的讓模型動起來,這是我們需要使用動畫庫相關的軟體,這裡選擇使用我選擇 Mixamo ,他是Adobe公司出品的免費動畫庫,它提供了大量的人物動畫

進入 www.mixamo.com/#/ [6] 然後上傳剛剛我們自己的3d模型,步驟如下

image.png

之後等待上傳

上傳成功會顯示如下介面

image.png

然後點選 NEXT,繫結骨骼

3.gif

成功之後點選 NEXT,成功之後將看到如下介面

4.gif

這時,我們先將T-pose下載下來

之後,我們可以在左邊選擇我們需要的動畫,比如選個跳舞

5.gif

沒錯就是這麼簡單

之後可以 點選 DOWNLOAD 開始下載動畫

image.png

類似步驟,我們可以下載很多我們需要的動畫

準備好動畫等素材之後,開始準備搭建專案,然後將相關模型渲染到我們的頁面中

搭建專案

這是選擇使用vite來搭建一個react專案:

yarn create vite
複製程式碼

建立專案之後,進入專案、安裝依賴

cd [專案名稱]
yarn
yarn dev
複製程式碼

然後安裝一個lingo3d-react

yarn add lingo3d-react
複製程式碼

接著我們修改src\App.jsx檔案,將多餘的東西都去掉,同時新增 <World> 場景標籤。程式碼變成如下樣子:

import { World } from "lingo3d-react"

function App() {

  return (
    <World>

    </World>
  )
}

export default App
複製程式碼

接著啟動專案

yarn dev
複製程式碼

成功之後,顯示如下

vite v2.9.5 dev server running at:

> Local: http://localhost:3000/
> Network: use `--host` to expose

ready in 359ms.
複製程式碼

接著就可以進入 http://localhost:3000/ [7]

進去就可以看到一個預設的一個黑的場景,如下:

image.png

到這,說明專案已經構建成功!下面就是要載入我們的角色模型和場景模型

載入模型

載入角色

首先需要建立一個public資料夾,並將我們下載好的模型放進去

接著使用 <Model> 載入我們的模型

import { Model, World } from "lingo3d-react"

function App() {

  return (
    <World>
      <Model
        src="girl.fbx"
        animations={{ idle: "Standing Idle.fbx"}}
        animation="idle"
        scale={3}
      >
      </Model>
    </World >
  )
}

export default App
複製程式碼

屬性說明:

  • src 是我們的T-pose

  • animations 是所要用的動畫

  • animation 是當前動畫

  • scale 是縮放

這裡有個坑,就是src中一定得是T-pose,所以在最開始時一定要匯出T-pose,否則動畫載入不出來。

到這,我們的角色動畫就出來了

image.png

有了小妹妹,接下來就是給她一個家

載入房子

更上述類似的步驟,我們新增一個房子

import { Model, World } from "lingo3d-react"

function App() {

  return (
    <World>
      <Model
        src="girl.fbx"
        animations={{ idle: "Standing Idle.fbx"}}
        animation="idle"
        scale={4}
      >
      </Model>

      <Model
        src="Home.glb"
        scale={7}
      >
      </Model>
    </World >
  )
}

export default App
複製程式碼

結果如下

image.png

但大小需要調整,另外視角需要調整,可以使用第一人稱或者第三人稱相機來切換視角

使用第三人稱相機 ThirdPersonCamera

使用ThirdPersonCamera表示已第三人稱的角度看到人物,程式碼如下

import { Model, ThirdPersonCamera, World } from "lingo3d-react"

function App() {

  return (
    <World>

      <ThirdPersonCamera active mouseControl>
        <Model
          src="girl.fbx"
          animations={{ idle: "Standing Idle.fbx" }}
          animation="idle"
          scale={3}
        />
      </ThirdPersonCamera>

      <Model
        src="Home.glb"
        scale={5}
      />
    </World >
  )
}

export default App
複製程式碼

其中

  • active 屬性表示啟用

  • mouseControl 表示滑鼠的控制

效果如下:

6.gif

但是此時小妹妹和房子捏在一起了,這是可以設定模型的物理屬性

物理屬性 physics

物理屬性physics的值有很多,這裡設定角色physics="character",設定房子physics="map"

  • character 表示主角

  • map 表示地圖

程式碼如下:

import { Model, ThirdPersonCamera, World } from "lingo3d-react"

function App() {

  return (
    <World>

      <ThirdPersonCamera active mouseControl>
        <Model
          src="girl.fbx"
          physics="character"
          animations={{ idle: "Standing Idle.fbx" }}
          animation="idle"
          scale={1}
        />
      </ThirdPersonCamera>

      <Model
        src="Home.glb"
        scale={10}
        physics="map"
      />
    </World >
  )
}

export default App
複製程式碼

效果如下

image.png

可以看到人物就和房子分離開了,但還有一個問題是:天空是黑色的

設定天空我們需要用到天空盒 Skybox

新增 Skybox

查詢天空背景

查詢天空背景可以google或者百度輸關鍵字:equirectangular sky / skybox background

新增Skybox

然後再 <World > 中新增 <Skybox texture="xxx" /> ,比如:

<Skybox texture="skybox.jpeg" />
複製程式碼

這樣就有天空啦,效果如下:

image.png
7.gif

但我發現人物有點浮在空中,應該是模型的物理碰撞結構的原因

我們將map換成map-debug檢查一下

image.png
image.png

發現,這個模型裡好多小雪花,被計算成碰撞體積了。

所以得調好人物的初始位置

調好之後再來換個場景,比如我看到下面這個場景感覺不錯

image.png

接著我們將場景下載並匯入

import { Model, Skybox, ThirdPersonCamera, World } from "lingo3d-react"

function App() {

  return (
    <World>

      <ThirdPersonCamera active mouseControl>
        <Model
          src="girl.fbx"
          physics="character"
          animations={{ idle: "idle.fbx",walking:"walking.fbx" }}
          animation="idle"
          scale={1}
        />
      </ThirdPersonCamera>

      <Model
        src="map/scene.gltf"
        scale={30}
        physics="map"
      />
      <Skybox texture="skybox.jpg" />
    </World >
  )
}

export default App
複製程式碼

顯示結果如下:

image.png
image.png

接著我想讓角色可以用鍵盤wsad控制上、下、左、右的移動

角色移動

首先得在 mixamo 準備好移動相關的動畫,比如走路、跑步等

將人物模型的animation設定成對應的動畫,比如animation="walking",這是就會用走路的動畫了

8.gif

其他相關的動畫也是這樣設定

下面整理一下如何讓角色移動的思路

  1. 初始是站立狀態

  2. 當我們按下鍵盤wsad鍵時,觸發走路動畫,角色分別向前、後、左、右的移動;如果短時間連續按兩下,觸發跑步動畫

  3. 當鬆開按鍵時,角色進入初始站立狀態

實現前後移動

當按下w時,切換向前移動動畫,並向前移動;當按下s鍵時,切換倒退動畫,人物向後移動;

程式碼如下

import { Model, Skybox, ThirdPersonCamera, useKeyboard, useLoop, World } from "lingo3d-react"
import { createRef, useRef } from "react"

function App() {
  // useKeyboard用於監控當前按鍵
  const key = useKeyboard()
  const characterRef = createRef()
  //宣告motion,用於表示當前角色應該對應的動畫,預設為站立idle
  let motion = "idle";
  // 前
  if (key === "w") {
    motion = "walking"
  }
  // 後
  if (key === "s") {
    motion = "walking_backwards"
  }

  // useLoop 幀迴圈勾子
  useLoop(() => {
    characterRef.current.moveForward(-3)
  }, key === "w");

  useLoop(() => {
    characterRef.current.moveForward(3)
  }, key === "s");

  return (
    <World>
      <ThirdPersonCamera active>
        <Model
          ref={characterRef}
          src="girl.fbx"
          physics="character"
          animations={{ idle: "idle.fbx", walking: "walking.fbx", walking_backwards: "walking-backwards.fbx" }}
          animation={motion}
          scale={1}
        />
      </ThirdPersonCamera>

      <Model
        src="map/scene.gltf"
        scale={40}
        physics="map"
      />
      <Skybox texture="skybox.jpg" />
    </World >
  )
}

export default App
複製程式碼

實現效果如下

按w時,向前 :point_down:

9.gif

按a時,向後 :point_down:

11.gif

左轉、右轉

左轉右轉的改變,可以通過滑鼠來控制,即mouseControl屬性

<ThirdPersonCamera active mouseControl>
複製程式碼

效果如下

13.gif

新增跑步動畫

由於上面是用w表示走路,這裡就用w加e的時候實現跑步吧,程式碼如下:

import { Model, Skybox, ThirdPersonCamera, useKeyboard, useLoop, World } from "lingo3d-react"
import { createRef, useRef } from "react"

function App() {
  // useKeyboard用於監控當前按鍵
  const key = useKeyboard()
  console.log(key);
  const characterRef = createRef()
  //宣告motion,用於表示當前角色應該對應的動畫,預設為站立idle
  let motion = "idle";
  // 前
  if (key === "w") {
    motion = "walking"
  }
  // 後
  if (key === "s") {
    motion = "walkingBackwards"
  }
  // 按下w和e時,開始跑
  if (key === "w e") {
    motion = "running"
  }

  // useLoop 幀迴圈勾子
  useLoop(() => {
    characterRef.current.moveForward(-4)
  }, key === "w");
  useLoop(() => {
    characterRef.current.moveForward(1.8)
  }, key === "s");
  useLoop(() => {
    characterRef.current.moveForward(-10)
  }, key === "w e");

  return (
    <World>
      <Skybox texture="skybox.jpg" />

      <ThirdPersonCamera active mouseControl>
        <Model
          ref={characterRef}
          src="girl.fbx"
          physics="character"
          animations={{
            idle: "idle.fbx",
            walking: "walking.fbx",
            walkingBackwards: "walking-backwards.fbx",
            running: "running.fbx"
          }}
          animation={motion}
          scale={1}
        />
      </ThirdPersonCamera>

      <Model
        src="map/scene.gltf"
        scale={40}
        physics="map"
      />

      <Model
        src="dragon_ball/scene.gltf"
        scale={10}
        physics="character"
      />
    </World >
  )
}

export default App
複製程式碼

效果如下

15.gif

接著,要不找點什麼東西吧,比如收集七顆龍珠

收集七龍珠

引入7龍珠

首先得下載龍珠,步驟跟之前一樣,之後引入

image.png

移動角色和龍珠到合適的位置

下面我希望將角色和龍珠的初始位置移動到合適的地方

可以通過model身上的x、y、z來控制,但是具體多少呢?可以使用Editor元件開啟編輯模式,來測

xxx
return (
    <>
      <World>    
      </World >      
      //開啟編輯模式
      <Editor/> 
    </>
)
複製程式碼

於是就可以看到以下的編輯模式,可以用滑鼠進行移動

image.png

移動好之後可以記下右邊的資料,主要position和rotation

比如

<ThirdPersonCamera active mouseControl>
  <Model
    ref={characterRef}
    src="girl.fbx"
    physics="character"
    animations={{
      idle: "idle.fbx",
      walking: "walking.fbx",
      walkingBackwards: "walking-backwards.fbx",
      running: "running.fbx"
    }}
    animation={motion}
    scale={1}
    x={-221.30}
    y={-1300.07}
    z={-6722.07}
    rotationY={-180}
  />
</ThirdPersonCamera>

<Model
  ref={ballRef}
  src="dragon_ball.fbx"
  physics="character"
  x={516.29}
  y={-1198.63}
  z={173.60}
  scale={.5}
/>
複製程式碼

尋找龍珠

我們可以通過w和s和w+e以及配合滑鼠來控制角色移動來尋找龍珠,當尋找到龍珠時,即準心對準龍珠時,讓龍珠有變化,比如高亮或者啥的

所以,首先弄個準心吧

準心

通過blender找到龍珠對應的名字

image.png

然後在龍珠模型中新增一個Find標籤

<Model id="ball"
  ref={ballRef}
  src="dragon_ball.fbx"
  physics="character"
  x={516.29}
  y={-1198.63}
  z={173.60}
  scale={.5}
>
  <Find name="Two Star_02 - Default_0" outline></Find>
</Model>
複製程式碼

表示的是在模型中找到的東西,我們讓他outline,以示區分,效果可以之後調

展示如下

image.png

但是預設情況,我們應該是FALSE,不顯示的

這裡引入狀態來控制,如下程式碼

//...
const [mouseOver,setMouseOver] = useState(false);
//....

<Find name="Two Star_02 - Default_0"
  outline={mouseOver}
  onMouseOver={() => {
    setMouseOver(true)
  }} ></Find>
複製程式碼

這樣的話,預設就是不顯示發光特效,只有鼠標準心對準過後才發光,表示找到了的

接著我們需要新增準心,來方便找,通過Reticle

<World >
</World >

<Reticle color="white" variant={1}/>
//xxx
複製程式碼
  • color是準心的顏色;

  • variant是準心的形狀,可以通過不同數字選擇自己喜歡的形狀

上述程式碼效果如下 :point_down:

image.png

但是此時 瞄準器 在人物的屁股上,希望的是瞄準器在人物的頭頂上,相當於是人物的目光

所以我的調整相機的位置,讓相機升到人物的頭頂上去

調整相機位置

其實可以通過相機的innerX、innerY、innerZ來設定,如下程式碼

<ThirdPersonCamera active mouseControl innerY={66}>
複製程式碼

效果如下

image.png

找到龍珠

當找到龍珠並點選時,龍珠亮

程式碼如下

<Model id="ball"
  ref={ballRef}
  src="dragon_ball.fbx"
  physics="character"
  x={516.29}
  y={-1198.63}
  z={173.60}
  scale={.5}
>
<Find name="Two Star_02 - Default_0"
  outline={mouseOver}
  onClick={() => {
    setMouseOver(true)
  }} ></Find>
</Model>
複製程式碼

效果如下

16.gif

類似的方式將其他龍珠新增到合適的位置

另一種尋找方式

除了看到點選可以點亮龍珠以外,當角色和龍珠碰撞時也應點亮龍珠

那首先要檢測角色碰撞

步驟如下:

  1. 先給龍珠新增一個id

  2. 給角色新增intersectID屬性,值為一個數組,陣列的每一項是每個龍珠的id名

  3. 再給角色新增onIntersect屬性,值為一個回撥函式,這個回撥函式會在角色和龍珠發生碰撞時觸發

所以,需要做的事情都是這個回撥函式中執行。

這步之後加上吧

神龍出現

神龍出現,小龍消失

當所有龍珠都找到時,地圖上某個地方就會出現龍,你需要找到它

image.png

找到之後點選它,小龍消失,真神龍就會現身了

image.png

神龍許願

點選神龍,過一會就會送我回到家裡,並且回去之後可以看到7顆龍珠

完整的遊戲過程

1. 通過按鍵和滑鼠控制角色移動

w鍵:向前跑

20.gif

s鍵:向後走

21.gif

滑鼠:移動滑鼠可控制方向

23.gif

空格:跳躍

22.gif

2. 尋找七龍珠

當看到龍珠時,對準並按下滑鼠即可標記此龍珠已經找到,然後繼續找下一顆

24.gif

當所有龍珠被找到時,會提示地圖某處會出現龍

image.png

3. 尋找龍

當提示地圖某處出現龍時,就去尋找龍

此龍如圖所示

image.png

但是到這沒有結束,此龍非真的神龍

4. 真神龍出現

點選小龍,小龍會消失,真的神龍出現

image.png
image.png

點選神龍,一會就會實現回家的願望

5. 回到家

到這就會回到家了,如下

image.png

而且家附近會出現我們找到的龍珠

image.png

最後

最後能回到家,肯定開心啦

所以按住d鍵,開始跳舞吧

25.gif

參考

Lingo3D 官方地址(GitHub): github.com/lingo3d/lin… [8]

Lingo3D B站地址: b23.tv/R7T8md5 [9]

小紅旗 Hubert: b23.tv/0ewcIiw [10]

結語

以上就是本文的所有內容啦,原始碼這兩天完善後回上傳github,敬請期待~

關於本文

作者:LBJ

https://juejin.cn/post/7087730315531141128

最後

歡迎關注「 三分鐘學前端

號內回覆:

網路 」,自動獲取三分鐘學前端網路篇小書(90+頁)

JS 」,自動獲取三分鐘學前端 JS 篇小書(120+頁)

演算法 」,自動獲取 github 2.9k+ 的前端演算法小書

面試 」,自動獲取 github 23.2k+ 的前端面試小書

簡歷 」,自動獲取程式設計師系列的  120 套模版

》》面試官也在看的前端面試資料《《

“在看和轉發” 就是最大的