別捲了,快來玩 | React+Three.js 實現一個超好玩的3D遊戲:美女與龍珠
點選上方 三分鐘學前端 ,關注公眾號
回覆 交流 ,加入前端程式設計面試演算法每日一題群
面試官也在看的前端面試資料
遊戲故事
遊戲講述的是一個小女生被惡魔詛咒找不到家了,她聽說收集七龍珠可以召喚神龍,神龍可是幫她實現回家的願望,於是她開啟了她的冒險故事
使用技術
這個遊戲使用了以下技術
-
Vite
+React
-
基於
Three.js
的 Lingo3D [2]
以及使用了以下工具:
-
sketchfab
:3D模型下載 -
mixamo
:3D人物動作繫結及動畫 -
readyplayer
:3D角色生產工具 -
gltf.report
:模型壓縮 -
polyhaven
:hdr素材庫(環境貼圖) -
textures
:材質貼圖素材
部分效果實現如下 :point_down:
開始
其實我也不知道從哪裡開始,也不知道寫什麼好?
但我好像需要一個角色,那就從建立角色開始
建立一個角色
下載角色
首先到 sketchfab [3] 中下載一個角色模型
當然如果你是首次使用 sketchfab
,記得先註冊和登入
註冊好之後,就會看到以下介面
箭頭指的輸入框輸入關鍵字,即可篩選出相關的模型,然後我們從中進行挑選
注意,挑選人物模型時,最好選擇 T-pose [4] 型別,比如 這樣可以方便之後繫結骨骼
我這裡選擇了這下面這個小妹妹,我覺得還不錯
然後點選右上角的下載按鈕,進入下載介面
一般fbx使用的比較多,但是有的時候不是fbx的格式,我們需要使用其他工具轉格式,或者處理模型
為了方便,我們這裡選擇第二個:glTF 格式,點選 DOWNLOAD
下載好之後是個壓縮包,我們進行解壓,解壓後目錄如下
處理角色模型
接著我們選擇使用模型處理軟體來處理我們的模型,比如我這裡選擇 blender
當然得先下載軟體,我們進入 官網 [5] ,然後點選如下按鈕進行下載
安裝就直接點選下一步下一步就行,之後開啟軟體,看到以下介面
開始進來中間是有一個立方體的,我們需要移除。步驟是滑鼠選中,按x刪除
之後按如下步驟匯入我們剛剛下載的模型
結果如下,表示匯入成功
但看不到人,可以按x或者Delete將繫結的骨骼刪除,即可展示人物
但是人物怎麼沒有顏色,這時因為這時展現的是實體,我們可以按z 甩狙,選擇渲染即可
選擇的模型比較正常,所以不用太多的處理,接著我們將它轉成fbx格式,但是在轉之前需要將燈光和相機移除,以防止模型匯入頁面時光線等問題
可以跟之前一樣選擇中,按x即可刪除
下面開始將它轉成fbx格式,直接按以下步驟匯出即可
需要注意,匯出時需要將路徑模式改成複製,並開啟內嵌紋理。否則匯出的模型將沒有顏色
製作動畫
模型有了之後,我們我們的讓模型動起來,這是我們需要使用動畫庫相關的軟體,這裡選擇使用我選擇 Mixamo
,他是Adobe公司出品的免費動畫庫,它提供了大量的人物動畫
進入 www.mixamo.com/#/ [6] 然後上傳剛剛我們自己的3d模型,步驟如下
之後等待上傳
上傳成功會顯示如下介面
然後點選 NEXT,繫結骨骼
成功之後點選 NEXT,成功之後將看到如下介面
這時,我們先將T-pose下載下來
之後,我們可以在左邊選擇我們需要的動畫,比如選個跳舞
沒錯就是這麼簡單
之後可以 點選 DOWNLOAD 開始下載動畫
類似步驟,我們可以下載很多我們需要的動畫
準備好動畫等素材之後,開始準備搭建專案,然後將相關模型渲染到我們的頁面中
搭建專案
這是選擇使用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]
進去就可以看到一個預設的一個黑的場景,如下:
到這,說明專案已經構建成功!下面就是要載入我們的角色模型和場景模型
載入模型
載入角色
首先需要建立一個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,否則動畫載入不出來。
到這,我們的角色動畫就出來了
有了小妹妹,接下來就是給她一個家
載入房子
更上述類似的步驟,我們新增一個房子
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 複製程式碼
結果如下
但大小需要調整,另外視角需要調整,可以使用第一人稱或者第三人稱相機來切換視角
使用第三人稱相機 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 表示滑鼠的控制
效果如下:
但是此時小妹妹和房子捏在一起了,這是可以設定模型的物理屬性
物理屬性 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 複製程式碼
效果如下
可以看到人物就和房子分離開了,但還有一個問題是:天空是黑色的
設定天空我們需要用到天空盒 Skybox
新增 Skybox
查詢天空背景
查詢天空背景可以google或者百度輸關鍵字:equirectangular sky / skybox background
新增Skybox
然後再 <World >
中新增 <Skybox texture="xxx" />
,比如:
<Skybox texture="skybox.jpeg" /> 複製程式碼
這樣就有天空啦,效果如下:
但我發現人物有點浮在空中,應該是模型的物理碰撞結構的原因
我們將map換成map-debug檢查一下
發現,這個模型裡好多小雪花,被計算成碰撞體積了。
所以得調好人物的初始位置
調好之後再來換個場景,比如我看到下面這個場景感覺不錯
接著我們將場景下載並匯入
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 複製程式碼
顯示結果如下:
接著我想讓角色可以用鍵盤wsad控制上、下、左、右的移動
角色移動
首先得在 mixamo
準備好移動相關的動畫,比如走路、跑步等
將人物模型的animation設定成對應的動畫,比如animation="walking",這是就會用走路的動畫了
其他相關的動畫也是這樣設定
下面整理一下如何讓角色移動的思路
-
初始是站立狀態
-
當我們按下鍵盤wsad鍵時,觸發走路動畫,角色分別向前、後、左、右的移動;如果短時間連續按兩下,觸發跑步動畫
-
當鬆開按鍵時,角色進入初始站立狀態
實現前後移動
當按下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:
按a時,向後 :point_down:
左轉、右轉
左轉右轉的改變,可以通過滑鼠來控制,即mouseControl屬性
<ThirdPersonCamera active mouseControl> 複製程式碼
效果如下
新增跑步動畫
由於上面是用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 複製程式碼
效果如下
接著,要不找點什麼東西吧,比如收集七顆龍珠
收集七龍珠
引入7龍珠
首先得下載龍珠,步驟跟之前一樣,之後引入
移動角色和龍珠到合適的位置
下面我希望將角色和龍珠的初始位置移動到合適的地方
可以通過model身上的x、y、z來控制,但是具體多少呢?可以使用Editor元件開啟編輯模式,來測
xxx return ( <> <World> </World > //開啟編輯模式 <Editor/> </> ) 複製程式碼
於是就可以看到以下的編輯模式,可以用滑鼠進行移動
移動好之後可以記下右邊的資料,主要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找到龍珠對應的名字
然後在龍珠模型中新增一個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,以示區分,效果可以之後調
展示如下
但是預設情況,我們應該是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:
但是此時 瞄準器 在人物的屁股上,希望的是瞄準器在人物的頭頂上,相當於是人物的目光
所以我的調整相機的位置,讓相機升到人物的頭頂上去
調整相機位置
其實可以通過相機的innerX、innerY、innerZ來設定,如下程式碼
<ThirdPersonCamera active mouseControl innerY={66}> 複製程式碼
效果如下
找到龍珠
當找到龍珠並點選時,龍珠亮
程式碼如下
<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> 複製程式碼
效果如下
類似的方式將其他龍珠新增到合適的位置
另一種尋找方式
除了看到點選可以點亮龍珠以外,當角色和龍珠碰撞時也應點亮龍珠
那首先要檢測角色碰撞
步驟如下:
-
先給龍珠新增一個id
-
給角色新增intersectID屬性,值為一個數組,陣列的每一項是每個龍珠的id名
-
再給角色新增onIntersect屬性,值為一個回撥函式,這個回撥函式會在角色和龍珠發生碰撞時觸發
所以,需要做的事情都是這個回撥函式中執行。
這步之後加上吧
神龍出現
神龍出現,小龍消失
當所有龍珠都找到時,地圖上某個地方就會出現龍,你需要找到它
找到之後點選它,小龍消失,真神龍就會現身了
神龍許願
點選神龍,過一會就會送我回到家裡,並且回去之後可以看到7顆龍珠
完整的遊戲過程
1. 通過按鍵和滑鼠控制角色移動
w鍵:向前跑
s鍵:向後走
滑鼠:移動滑鼠可控制方向
空格:跳躍
2. 尋找七龍珠
當看到龍珠時,對準並按下滑鼠即可標記此龍珠已經找到,然後繼續找下一顆
當所有龍珠被找到時,會提示地圖某處會出現龍
3. 尋找龍
當提示地圖某處出現龍時,就去尋找龍
此龍如圖所示
但是到這沒有結束,此龍非真的神龍
4. 真神龍出現
點選小龍,小龍會消失,真的神龍出現
點選神龍,一會就會實現回家的願望
5. 回到家
到這就會回到家了,如下
而且家附近會出現我們找到的龍珠
最後
最後能回到家,肯定開心啦
所以按住d鍵,開始跳舞吧
參考
Lingo3D 官方地址(GitHub): github.com/lingo3d/lin… [8]
Lingo3D B站地址: b23.tv/R7T8md5 [9]
小紅旗 Hubert: b23.tv/0ewcIiw [10]
結語
以上就是本文的所有內容啦,原始碼這兩天完善後回上傳github,敬請期待~
關於本文
作者:LBJ
http://juejin.cn/post/7087730315531141128
最後
歡迎關注「 三分鐘學前端 」
號內回覆:
「 網路 」,自動獲取三分鐘學前端網路篇小書(90+頁)
「 JS 」,自動獲取三分鐘學前端 JS 篇小書(120+頁)
「 演算法 」,自動獲取 github 2.9k+ 的前端演算法小書
「 面試 」,自動獲取 github 23.2k+ 的前端面試小書
「 簡歷 」,自動獲取程式設計師系列的 120
套模版
》》面試官也在看的前端面試資料《《
“在看和轉發” 就是最大的
- Vue 視覺化大屏適配外掛之過程篇
- Vue3生命週期Hooks的原理及其與排程器(Scheduler)的關係
- 10 個不錯的 CSS 小技巧
- 教你使用 koa2 vite ts vue3 pinia 構建前端 SSR 企業級專案
- 別捲了,快來玩 | React Three.js 實現一個超好玩的3D遊戲:美女與龍珠
- 手動實現Vue3 & 原理解析:setup環境 & reactive函式 & effect函式(一)
- 前端程式碼的三種設計模式
- 覺得自己的頁面不夠花哨嗎,試試clip-path吧
- 簡易版 useState 實現
- 回溯演算法彙總一
- CSS 的 Filter屬性竟然如此好玩
- 輕輕鬆鬆拿下 JS 淺拷貝、深拷貝
- 2022 前端應該掌握的 10 個 JS 小技巧
- 一文搞懂 Vue3.0 為什麼採用 Proxy
- 位元組飛書面試——請實現 Promise.all
- 我把 Vue3 專案中的 Vuex 去除了,改用 Pinia
- 從0到1400star,從阮一峰週刊到尤雨溪推薦,小透明開源專案的2021年總結
- type 和 interface的區別知多少?
- 當webpack有了vite的速度你會喜歡嗎?
- 前端面試百問(含解答)