用 Three.js, React 和 WebGL 開發遊戲 — SitePoint

本文由陳龍20155在眾成翻譯平台上翻譯。

我正在製作一款名為 「Charisma The Chameleon」 的遊戲,它使用 Three.js,React 和 WebGL 開發。這是一篇使用 react-three-renderer (簡稱 R3R) 結合這些框架的介紹。

SitePoint 上有關於 React 和 WebGL 的介紹。查閱請訪問: WebGL 入門手冊 和 React + JSX 起步。這兩篇文章及附帶的源碼使用的是 ES6 語法.

如何開始

前段時間, Pete Hunt 在 IRC #reactjs 板塊說了一個關於 React 開發遊戲的笑話:

我打賭,我們能用 React 開發第一人稱射擊遊戲!

敵人有 `` 之類.

我和他都笑了,每個人都很開心。「到底誰會那樣做?」我想知道。

一年後,這就是我正在做的事情。

魅力變色龍 ,是一款通過收集「能量電源」,使自己收縮以解決無盡的分形迷宮的遊戲。作為一個有多年經驗的 React 開發者,我很好奇是否有一種方式能夠很好的在 React 中使用 Three.js。這時候,R3R 吸引了我的眼球。

為什麼用 React?

我知道你在想什麼:為什麼?讓我幽默一會。下面是考慮使用 React 驅動 3D 場景的幾個理由:

  • 「聲明「視圖讓你能很清晰的把場景渲染從遊戲邏輯里分離出來。
  • 組件設計起來很容易, 比如 ,, ``, 之類。
  • 遊戲資源熱載入。實時更新場景中紋理和模型的變化!
  • 使用瀏覽器工具(如:Chrome inspector),像標記一樣檢查和調試 3D 場景。
  • 使用 Webpack 依賴管理遊戲資源,例: ``

讓我們來搭建一個場景,搞明白它們是如何工作的。

推薦課程

初學者學習 React 最好的方式 Wes Bos 一個循序漸進的訓練課程,讓你花幾個下午的時間,就能夠使用 React.js + Firebase 搭建真實的網站和應用。付款時使用優惠碼SITEPOINT 獲得 25% 的折扣。

React 和 WebGL

伴隨這篇文章,我建了一個 GitHub 示例倉庫。克隆倉庫,參照 README.md 里的指令運行代碼並跟著來。它啟動了 SitePointy 3D 機器人!

警告: R3R 還是測試版,它的 API 還不太穩定,而且將來可能會更改;目前只處理 Three.js 的子集。在我看來它完全足夠去開發一個完整的遊戲,但是因個人可能會有差異。

組織視圖代碼

使用 React 驅動 WebGL 最大的好處,是能夠講我們的視圖代碼與遊戲邏輯 解耦,這意味著我們所呈現的實體可以是很方便導出的小的組件。

R3R 通過包裹 Three.js,暴露了一個聲明的 API。舉個例子,我們可以這樣寫:

<scene>n <perspectiveCameran position={ new THREE.Vector3( 1, 1, 1 )n />n</scene>n

現在,我們有了一個空的 3D 場景和相機。在場景里添加網格,就像引入 component, and give it和 `` 一樣簡單。

<scene>n …n <mesh>n <boxGeometryn width_={ 1 }n height={ 1 }n depth={ 1 }n />n <meshBasicMaterialn color={ 0x00ff00 }n />n</mesh>n

在代碼底層,它創建了一個 THREE.Scene(場景),並通過 THREE.BoxGeometry 自動添加了網格。 R3R 會處理場景的變化。如果你添加一個網格到場景中,原網格不會被重建。就像普通的 React DOM 一樣,3D 場景 只更新有差異的地方。

因為使用 React 開發,我們可以把遊戲整體分離至組件文件。示例倉庫里的 Robot.js 文件 演示了如何用純 React 視圖代碼去表示主角。它是一個 「無狀態組件」, 意味著它沒有自己的狀態要管理:

const Robot = ({ position, rotation }) => <groupn position={ position }n rotation={ rotation }n>n <mesh rotation={ localRotation }>n <geometryResourcen resourceId="robotGeometry"n />n <materialResourcen resourceId="robotTexture"n />n </mesh>n</group>;n

現在,我們將 `` 載入到 3D 場景中來!

<scene>n …n <mesh>…</mesh>n <Robotn position={…}n rotation={…}n />n</scene>n

您可以在 R3R GitHub 倉庫 中看到更多關於 API 的示例,或者在 附帶工程 中查看完整的示例代碼。

組織代碼邏輯

問題的第二部分是處理遊戲邏輯。咱們來給 SitePointy 機器人加一些簡單的動畫。

傳統遊戲是如何工作的?它們接收用戶輸入、分析現有的 「遊戲世界的狀態」,然後返回新的狀態用來渲染。為了方便起見,咱們將「遊戲狀態」對象保存到組件里。在更成熟的工程中,建議將遊戲狀態放到 Redux 或 Flux 的 store 里。

我們使用瀏覽器的 requestAnimationFrame API 回調作為遊戲循環的驅動函數,並在 GameContainer.js 中運行。為了讓機器人動起來,我們要基於傳給requestAnimationFrame的時間戳來計算新的位置, 然後將新位置保存到狀態里。

// …ngameLoop( time ) {n this.setState({n robotPosition: new THREE.Vector3(n Math.sin( time * 0.01 ), 0, 0n )n });n}n

調用 setState() 觸發子組件重繪,並且 3D 場景會更新。我們將狀態從容器組件傳給展示組件:

render() {n const { robotPosition } = this.state;n return <Gamen robotPosition={ robotPosition }n />;n}n

咱們可以使用一個非常有用的模式來幫助組織這些代碼。更新機器人的位置是很簡單的基於時間的計算,將來,也會考慮用來從前一個遊戲狀態里記錄之前的機器人位置。一個函數接收數據並處理,然後返回新的數據,通常被稱為 reducer。我們可以把移動位置的代碼抽象至 reducer 函數中!

現在我們可以寫一個乾淨、簡單的遊戲循環,只有函數的調用在裡面:

import robotMovementReducer from ./game-reducers/robotMovementReducer.js;nn// …nngameLoop() {n const oldState = this.state;n const newState = robotMovementReducer( oldState );n this.setState( newState );n}n

為了給遊戲循環添加更多的邏輯(比如處理物理現象),需要創建另外一個 reducer 函數,然後將它的結果傳給前一個函數:

`const newState = physicsReducer( robotMovementReducer( oldState ) );`n

由於遊戲引擎不斷的變得複雜,將遊戲邏輯組織到分離的函數中很關鍵。這種組織在 reducer 模式里會很簡單。

資源管理

這仍然是 R3R 不斷進化的部分。紋理組件需要在 JSX 標籤上指定 url 屬性,使用 webpack,可以直接通過本地路徑引入圖片:

`<texture url={ require( ../local/image/path.png ) } />`n

基於這種方式,如果修改了本地圖片,您的 3D 場景將會熱更新!對於快速迭代遊戲設計和內容來說,這是非常有幫助的。

至於另外的資源,如 3D 模型,您可能還是需要使用 Three.js 內置的組件來處理它們;比如 JSONLoader. 我曾嘗試使用一個定製的 webpack 載入器來載入 3D 模型文件,但是最後做了很多工作卻沒有帶來好處。使用 file-loader 將模型作為二進位數據處理並載入它們會更容易一些,它還會為模型數據提供熱更新。您在 示例代碼 中可以看到。

調試

R3R 同時支持支持 Chrome 和 Firefox 的 React 開發者工具擴展。如果場景是普通 DOM 元素的話,您可以檢查它!通過移動滑鼠到檢查器的組件上,顯示場景的邊界框。您還可以移動滑鼠到紋理的定義上,以查看場景中的哪一個物體使用了這個紋理。

您同時也可以加入 react-three-renderer Gitter 聊天室,尋找應用調試相關的幫助。

性能注意事項

通過構建 「魅力變色龍」,我碰到了一些因工作流造成的性能問題:

  • 我的 Webpack 熱重載時間 長達 30 秒!這是因為在重載的時候,大量的資源被重寫了。解決方案是實施 Webpack』s DLLPlugin,可以將重載時間減少至 5 秒以內。
  • 理想情況下,場景在渲染時候,每一幀的只能調用 一次 setState()。 通過分析我的遊戲,React 自身是最主要的瓶頸,每一幀調用 setState() 超過一次會導致雙重渲染,並且會降低性能。
  • 當超過一定數量的物體時,R3R 會比普通的 Three.js 代碼表現更糟。在我的工程里,大概是 1000 個物體。你可以通過 「Benchmarks」 示例代碼 來比較 R3R 和 Three.js 的區別。

Chrome DevTools 的 Timeline 功能,對於調試性能來說是一款非常好的工具。用它可以很容易直觀的檢查你的遊戲循環,相比於「Profile」 功能,它也更具有可讀性。

這就行了!

查看 魅力變色龍 以了解這個工程的實現。即使這個工具還很年輕,我發現 React 和 R3R 完全可以乾淨的組織遊戲代碼。你也可以查看這個還在不斷增加的 R3R 示例頁面 去查看有組織的代碼示例。

這篇文章由 Mark Brown 和 Kev Zettler 審稿。感謝 SitePoint 上所有的審稿人,是你們讓 SitePoint 內容質量更高!

作者:Andrew Ray

Hello!我是來自 Bay Area 的軟體工程師。

原 文:Building a Game with Three.js, React and WebGL

譯 文:眾成翻譯

作 者:陳龍20155 譯

更多精彩文章:SDK.CN - 中國領先的開發者服務平台


推薦閱讀:

React Loadable 簡介
掌握這5大核心概念,你就理解了React
React 模態框秘密和「輪子」漸進設計
高性能 MobX 模式(part 3)- 用例教程

TAG:threejs | React | WebGL |