17611538698
webmaster@21cto.com

用 Three.js, React 和 WebGL 开发游戏 — SitePoint

资讯 0 4313 2017-09-26 12:02:09
摘要:我正在制作一款名为 “Charisma The Chameleon” 的游戏,它使用 Three.js,React 和 WebGL 开发。这是一篇使用 react-three-renderer (简称 R3R) 结合这些框架的介绍。SitePoint 上有关于 React 和 WebGL 的介绍。

three.png

本文由陈龙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 开发第一人称射击游戏!
敌人有 `` 之类.


我和他都笑了,每个人都很开心。“到底谁会那样做?”我想知道。
一年后,这就是我正在做的事情。
/uploads/fox/26221238_0.gif
魅力变色龙 ,是一款通过收集“能量电源”,使自己收缩以解决无尽的分形迷宫的游戏。作为一个有多年经验的 React 开发者,我很好奇是否有一种方式能够很好的在 React 中使用 Three.js。这时候,R3R 吸引了我的眼球。为什么用 React?
我知道你在想什么:为什么?让我幽默一会。下面是考虑使用 React 驱动 3D 场景的几个理由:
  • “声明“视图让你能很清晰的把场景渲染从游戏逻辑里分离出来。
  • 组件设计起来很容易, 比如 ,, ``, 之类。
  • 游戏资源热加载。实时更新场景中纹理和模型的变化!
  • 使用浏览器工具(如:Chrome inspector),像标记一样检查和调试 3D 场景。
  • 使用 Webpack 依赖管理游戏资源,例: ``

让我们来搭建一个场景,搞明白它们是如何工作的。推荐课程
/uploads/fox/26221238_1.jpg
初学者学习 React 最好的方式 Wes Bos 一个循序渐进的训练课程,让你花几个下午的时间,就能够使用 React.js + Firebase 搭建真实的网站和应用。付款时使用优惠码'SITEPOINT' 获得 25% 的折扣。
 React 和 WebGL
伴随这篇文章,我建了一个 GitHub 示例仓库。克隆仓库,参照 README.md 里的指令运行代码并跟着来。它启动了 SitePointy 3D 机器人!
/uploads/fox/26221238_2.jpg
警告: R3R 还是测试版,它的 API 还不太稳定,而且将来可能会更改;目前只处理 Three.js 的子集。在我看来它完全足够去开发一个完整的游戏,但是因个人可能会有差异。组织视图代码
使用 React 驱动 WebGL 最大的好处,是能够讲我们的视图代码与游戏逻辑 解耦,这意味着我们所呈现的实体可以是很方便导出的小的组件。
R3R 通过包裹 Three.js,暴露了一个声明的 API。举个例子,我们可以这样写:
[code]<scene>
<perspectiveCamera
position={ new THREE.Vector3( 1, 1, 1 )
/>
</scene>
[/code]
现在,我们有了一个空的 3D 场景和相机。在场景里添加网格,就像引入 component, and give it和 `` 一样简单。
[code]<scene>

<mesh>
<boxGeometry
width={ 1 }
height={ 1 }
depth={ 1 }
/>
<meshBasicMaterial
color={ 0x00ff00 }
/>
</mesh>
[/code]
在代码底层,它创建了一个 THREE.Scene(场景),并通过 THREE.BoxGeometry 自动添加了网格。 R3R 会处理场景的变化。如果你添加一个网格到场景中,原网格不会被重建。就像普通的 React DOM 一样,3D 场景 只更新有差异的地方。
因为使用 React 开发,我们可以把游戏整体分离至组件文件。示例仓库里的 Robot.js 文件 演示了如何用纯 React 视图代码去表示主角。它是一个 “无状态组件”, 意味着它没有自己的状态要管理:
[code]const Robot = ({ position, rotation }) => <group
position={ position }
rotation={ rotation }
>
<mesh rotation={ localRotation }>
<geometryResource
resourceId="robotGeometry"
/>
<materialResource
resourceId="robotTexture"
/>
</mesh>
</group>;
[/code]
现在,我们将 `` 加载到 3D 场景中来!
[code]<scene>

<mesh>…</mesh>
<Robot
position={…}
rotation={…}
/>
</scene>
[/code]
您可以在 R3R GitHub 仓库 中看到更多关于 API 的示例,或者在 附带工程 中查看完整的示例代码。组织代码逻辑
问题的第二部分是处理游戏逻辑。咱们来给 SitePointy 机器人加一些简单的动画。
/uploads/fox/26221238_3.gif
传统游戏是如何工作的?它们接收用户输入、分析现有的 “游戏世界的状态”,然后返回新的状态用来渲染。为了方便起见,咱们将“游戏状态”对象保存到组件里。在更成熟的工程中,建议将游戏状态放到 Redux 或 Flux 的 store 里。
我们使用浏览器的 requestAnimationFrame API 回调作为游戏循环的驱动函数,并在 GameContainer.js 中运行。为了让机器人动起来,我们要基于传给requestAnimationFrame的时间戳来计算新的位置, 然后将新位置保存到状态里。
[code]// …
gameLoop( time ) {
this.setState({
robotPosition: new THREE.Vector3(
Math.sin( time * 0.01 ), 0, 0
)
});
}
[/code]
调用 setState() 触发子组件重绘,并且 3D 场景会更新。我们将状态从容器组件传给展示组件:
[code]render() {
const { robotPosition } = this.state;
return <Game
robotPosition={ robotPosition }
/>;
}
[/code]
咱们可以使用一个非常有用的模式来帮助组织这些代码。更新机器人的位置是很简单的基于时间的计算,将来,也会考虑用来从前一个游戏状态里记录之前的机器人位置。一个函数接收数据并处理,然后返回新的数据,通常被称为 reducer。我们可以把移动位置的代码抽象至 reducer 函数中!
现在我们可以写一个干净、简单的游戏循环,只有函数的调用在里面:
[code]import robotMovementReducer from './game-reducers/robotMovementReducer.js';

// …

gameLoop() {
const oldState = this.state;
const newState = robotMovementReducer( oldState );
this.setState( newState );
}
[/code]
为了给游戏循环添加更多的逻辑(比如处理物理现象),需要创建另外一个 reducer 函数,然后将它的结果传给前一个函数:
[code]`const newState = physicsReducer( robotMovementReducer( oldState ) );`
[/code]
由于游戏引擎不断的变得复杂,将游戏逻辑组织到分离的函数中很关键。这种组织在 reducer 模式里会很简单。资源管理
这仍然是 R3R 不断进化的部分。纹理组件需要在 JSX 标签上指定 url 属性,使用 webpack,可以直接通过本地路径引入图片:
[code]`<texture url={ require( '../local/image/path.png' ) } />`
[/code]
基于这种方式,如果修改了本地图片,您的 3D 场景将会热更新!对于快速迭代游戏设计和内容来说,这是非常有帮助的。
至于另外的资源,如 3D 模型,您可能还是需要使用 Three.js 内置的组件来处理它们;比如 JSONLoader. 我曾尝试使用一个定制的 webpack 加载器来载入 3D 模型文件,但是最后做了很多工作却没有带来好处。使用 file-loader 将模型作为二进制数据处理并载入它们会更容易一些,它还会为模型数据提供热更新。您在 示例代码 中可以看到。调试
R3R 同时支持支持 Chrome 和 Firefox 的 React 开发者工具扩展。如果场景是普通 DOM 元素的话,您可以检查它!通过移动鼠标到检查器的组件上,显示场景的边界框。您还可以移动鼠标到纹理的定义上,以查看场景中的哪一个物体使用了这个纹理。
/uploads/fox/26221238_4.jpg
您同时也可以加入 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 译


评论