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

語言: CN / TW / HK

theme: cyanosis

我正在參加掘金社區遊戲創意投稿大賽個人賽,詳情請看:遊戲創意投稿大賽

這兩天我想寫了個小遊戲,參加這個徵文活動 👆

遊戲故事

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

使用技術

這個遊戲使用了以下技術 1. vite + React 2. 基於 Three.jslingo3d

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

部分效果實現如下 👇

20.gif

22.gif

24.gif

image.png

image.png

image.png

image.png

25.gif

開始

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

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

創建一個角色

下載角色

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

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

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

image.png

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

注意,挑選人物模型時,最好選擇 T-pose 類型,比如 這樣可以方便之後綁定骨骼

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

image.png

然後點擊右上角的下載按鈕,進入下載界面

image.png

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

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

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

image.png

處理角色模型

接着我們選擇使用模型處理軟件來處理我們的模型,比如我這裏選擇 blender

當然得先下載軟件,我們進入官網,然後點擊如下按鈕進行下載

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公司出品的免費動畫庫,它提供了大量的人物動畫

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

image.png

之後等待上傳

上傳成功會顯示如下界面

image.png

然後點擊 NEXT,綁定骨骼

3.gif

成功之後點擊 NEXT,成功之後將看到如下界面

4.gif

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

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

5.gif

沒錯就是這麼簡單

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

image.png

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

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

搭建項目

這是選擇使用vite來搭建一個react項目: yarn create vite 創建項目之後,進入項目、安裝依賴 cd [項目名稱] yarn yarn dev 然後安裝一個lingo3d-react yarn add lingo3d-react 接着我們修改src\App.jsx文件,將多餘的東西都去掉,同時添加<World>場景標籤。代碼變成如下樣子:

```jsx import { World } from "lingo3d-react"

function App() {

return (

</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/

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

image.png

到這,説明項目已經構建成功!下面就是要加載我們的角色模型和場景模型

加載模型

加載角色

首先需要創建一個public文件夾,並將我們下載好的模型放進去

接着使用 <Model> 加載我們的模型

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

function App() {

return ( ) }

export default App ``` 屬性説明: - src 是我們的T-pose - animations 是所要用的動畫 - animation 是當前動畫 - scale 是縮放

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

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

image.png

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

加載房子

更上述類似的步驟,我們添加一個房子

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

function App() {

return (

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

) }

export default App ```

結果如下

image.png

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

使用第三人稱相機 ThirdPersonCamera

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

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

function App() {

return (

  <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 表示地圖

代碼如下:

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

function App() {

return (

  <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" />,比如: html <Skybox texture="skybox.jpeg" />

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

image.png

7.gif

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

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

image.png

image.png

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

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

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

image.png

接着我們將場景下載並導入

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

function App() {

return (

  <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鍵時,切換倒退動畫,人物向後移動;

代碼如下

```jsx 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 (

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

) }

export default App ``` 實現效果如下

按w時,向前 👇

9.gif

按a時,向後 👇

11.gif

左轉、右轉

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

html <ThirdPersonCamera active mouseControl> 效果如下

13.gif

添加跑步動畫

由於上面是用w表示走路,這裏就用w加e的時候實現跑步吧,代碼如下: ```jsx 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 (

  <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組件開啟編輯模式,來測 jsx xxx return ( <> <World> </World > //開啟編輯模式 <Editor/> ) 於是就可以看到以下的編輯模式,可以用鼠標進行移動

image.png

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

比如

```jsx

```

尋找龍珠

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

所以,首先弄個準心吧

準心

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

image.png

然後在龍珠模型中添加一個Find標籤 ```jsx <Model id="ball" ref={ballRef} src="dragon_ball.fbx" physics="character" x={516.29} y={-1198.63} z={173.60} scale={.5}

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

展示如下

image.png

但是默認情況,我們應該是FALSE,不顯示的

這裏引入狀態來控制,如下代碼 ```jsx //... const [mouseOver,setMouseOver] = useState(false); //....

{ setMouseOver(true) }} > ``` 這樣的話,默認就是不顯示發光特效,只有鼠標準心對準過後才發光,表示找到了的

接着我們需要添加準心,來方便找,通過Reticle ```jsx

//xxx ``` - color是準心的顏色; - variant是準心的形狀,可以通過不同數字選擇自己喜歡的形狀

上述代碼效果如下 👇

image.png

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

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

調整相機位置

其實可以通過相機的innerX、innerY、innerZ來設置,如下代碼

jsx <ThirdPersonCamera active mouseControl innerY={66}>

效果如下

image.png

找到龍珠

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

代碼如下

```jsx <Model id="ball" ref={ballRef} src="dragon_ball.fbx" physics="character" x={516.29} y={-1198.63} z={173.60} scale={.5}

{ setMouseOver(true) }} > ``` 效果如下

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

結語

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