本文转载自公众号:流利说技术团队

一、背景

半年前项目组决定开发一款游戏化的 app。但是组内还没有游戏开发人员。作为一个前端工程师,还算熟悉号称“镇后端”、“镇客户端”的 JavaScript。遂果断跳入游戏开发的坑中。这篇文章从比较 general 的方面对比了前端开发和游戏开发的一些区别,算是这段时间工作的一个总结,希望更多前端小伙伴们也可以尝试下游戏开发。

二、引擎选择

市面上存在的游戏引擎有很多,比如 Unity,Cocos2d-x,Egret 等。最终选择了 Cocos Creator,原因主要有几下几点:

  • 跨平台,平台无关的代码共用,可以提高开发效率
  • 使用 JavaScript/TypeScript 作为开发语言,对于前端比较友好。对 ES201X 语法支持比较完善。
  • 1.x 版本(笔者开始做时还未发布2.0版本)是基于 Cocos2d-x 实现的。文档资源和社区环境都比较成熟,便于上手。
  • 编辑器提供了集成式的开发、设计环境。最大程度上提高了程序员和设计师的合作效率。
  • 支持多种平台,可移植性比较好

本文以下部分提到的”游戏“均指 Cocos Creator 引擎,其他游戏引擎可能会有所不同。

三、游戏和前端开发的区别

1、共同点

  • 主要编程语言都是 JavaScript/TypeScript,JavaScript 中常用的 deisgn pattern 都可直接使用
  • 使用 prettier 进行代码风格管理,使用 tslint/eslint 对进行代码质量管理
  • 使用 jest 进行单元测试
  • 基于组件。组件在各种前端框架中很常见,同样游戏中也是基于组件封装隔离各个模块的。当然游戏中的组件跟前端框架中的组件还是有很大区别的,后面会专门介绍。
  • PM还是会不断地改需求😂😂

2、不同点(技术方面)

资源(Asset)

游戏的代码仓库里资源占了很大一部分比例,这些资源的组织和使用方式跟前端还是差别很大的。

  • 游戏工程里几乎所有的东西(包括代码、文件夹)都是资源。这里的资源是面向引擎而言的,而非面向开发者的。
  • 游戏中用的资源种类非常多,对于不同的资源的加载使用方式略有不同。前端通常使用不同的 HTML 标签来加载对应的 DOM 来处理,游戏里主要是通过不同的内置组件来。
  • 游戏中所有的./assets目录下的资源和文件夹,编辑器都会自动创建一个对应的.meta文件。该文件定义了该资源的 uuid、版本信息等。这里需要注意一个常见的问题,git 不会管理空文件夹,游戏编辑器却会为这个空文件夹创建一个 meta 文件,此时远端工程可能会有一个没有资源与之对应的 meta 文件,从而导致 ci 编译失败。
  • 游戏中使用资源的方式有两种。静态索引和动态加载。前者通过在渲染的节点组件上设置属性,指向资源的id,在节点加载的过程中加载资源。后者是在代码中根据资源路径动态加载。动态加载的资源必须位于工程的resource目录下。因为引擎打包时默认会打包所有resource目录下的资源和其他目录中被静态引用的资源。前端(web环境)的资源都是通过网络加载的,所以不存在这个问题。

工程目录结构

游戏中一种比较常用的工程组织结构是按照资源的种类进行划分。比如/imgs/audios。这种组织方式在比较小的项目中问题不大,但在大的项目中会有如下缺点:

  • 多人修改同一目录,非代码文件(.meta.fire.prefab)的冲突会增多,解决起来非常难受
  • 过于扁平,不能反映出项目模块的关系,迭代几次后项目会变得非常混乱
  • 在我们的实践中其实借鉴了前端项目中的工程目录方式,
  • 这里assets相当于前端工程惯例中的src目录;
  • foundation相当于services或者utils,放置一些公用(全局)的类库。
  • modules相当于pages,存放的是场景(相当于前端的页面)。
  • resources则相当于components,存放是在页面中引用的组件。每个组件目录下都有对应的脚本、图片、音频等资源。
assets
├── foundation
│   ├── common
│   ├── lib
│   ├── protos
│   └── utils
├── modules
│   ├── activity
│   └── roadmap
└── resources
    ├── activities
    ├── roadmap
    ├── sessions
    └── start

组件设计

用惯了 React、Vue 之类的前端框架之后,组件就是一个输入数据、输出UI的模块,开发的过程中很少去操作 Component,component 的创建销毁几乎都由框架完成。开发者的工作重心更多的是如何将多个代表组件的元素(Element)拼接起来。他们之间通过 props 或者事件(Vue)来通信就好了。组件的开发和使用都是偏声明(函数)式的,更关注数据的流动。

而在游戏中的组件,只有在其他组件获取到了这个组件的引用并使用组件的方法使之完成某个任务才有意义,是偏面向对象的,更关注逻辑的依赖。

其次。前端框架的组件里需要关注UI的渲染,即输出的(虚拟)DOM结构。而游戏组件只专注逻辑,渲染则需要开发者手动在编辑器中创建节点,游戏引擎通过解析节点树及绑定在节点上的脚本组件来完成的。

举个🌰

游戏脚本组件

class Prarent extends cc.Component {
    son: Son = null
    letSonRun () {
        this.son.run()
    }
}
class Son extends cc.Component {
    label: cc.Label = null
    run () {
        this.label.string = 'Running'
    }
    stop () {
        this.label.string = 'Stopped'
    }
}

React 组件

class Parent extends React.Component {
    letSonRun () {
        this.setState({ isSonRun: true })
    }
    render () {
        <Son isRun={this.state.isSonRun}>
    }
}

class Son extends React.Component {
    render () {
        return this.props.isRun ? <div>Running</div> : <div>stopped</div>
    }
}

样式/适配

  • 前端的样式是通过 CSS 实现的,游戏的样式则是建立在一套坐标位置体系和一些内置的组件上的。

  • web 页面中元素的位置在自然状态下是流式排布,即结构中后来的元素在已有元素的后面(下面)。而游戏中则需要每个元素都给定一个基于笛卡尔坐标系的位置。默认位置是(0, 0)
  • 相对于 css 的font-属性,游戏提供了 Label 和 RichText 组件
  • 相对于 css 的定位属性,游戏提供了 Widget 组件
  • 前端针对移动端的适配方案多采用自适应弹性布局。除了可以使用块级组件的流体特性外,还可以使用 FlexBox、Grid,em 等实现更为精细复杂的适配。而游戏中的弹性是天然自带的。在游戏工程中需要设置顶层节点(Canvas)的设计分辨率,并指定是按照高度还是宽度适配,这里先假定按照高度适配。设定完成后,最终 canvas 的高度就会撑满屏幕,然后宽度按比例缩放。当然这种适配方式只能根据屏幕做等比缩放,还是很有局限性的。比如多个子元素在固定大小父元素下的弹性布局,游戏提供了 Layout 组件(类似 flexBox)解决这个问题。在进行更为精细的屏幕适配和弹性布局,需要手动对节点进行缩放。

测试

前端的测试生态已经很完善了,从单元测试、集成测试、到 E2E 有一整套的工具可以拿来用,不同的框架也会有响应的测试工具包提升写测试用例的效率。但是游戏社区内几乎未找到现成的测试框架、工具。

基于游戏引擎跨平台的特性,我们正在试验一种测试方案:

  • 把测试分成两种类型:一种是跟 UI 无关的纯逻辑功能测试;另一种是需要验证 UI 的测试。
  • 对于第一种情况。我们可以借助前端现有的测试框架来做单元测试。
  • 对于第二种情况又可以分成两种情况来做。
  • 对 Native 平台没有依赖的模块完全可以将代码跑在浏览器环境下做集成测试
  • 对 Native 平台有依赖的的模块可以搭建一个 Electron 环境,模拟 Native 环境,完成集成测试

Git & Code Review

  • 游戏工程中会有很多的大体积资源文件,比如图片、音频甚至是视频。这些文件的 git 变动会导致我们的远程仓库体积快速膨胀。所以我们使用git-lfs来管理这些大文件。它将你所标记的大文件保存至另外的仓库,而在主仓库仅保留其轻量级指针。 那么在你检出版本时,根据指针的变化情况下更新对应的大文件,而不是在本地保存所有版本的大文件。
  • 在资源部分我们提到导入资源时,会为每个资源创建 meta 文件。同时在游戏项目中还有场景(fire)文件、预制(prefab)文件。这些文件都是编辑器为我们创建的,虽然都为文本,但是可读性比较差,在 Merge Request 中的进行 Code Review 的意义不大。这里我们采用 @melon 同学开发的 chrome 插件 gitlab-reviewer 折叠某些后缀的文件内容,只阅读代码和配置文件等有价值的部分,极大地提高了 code review 的效率。

周边工具

游戏的工具链比较长,尚未窥得全貌,这里只说几个平时用的比较多的吧。

  • TexturePacker Texutre 是游戏里面对图片的称谓。顾名思义,这个工具就是打图集。图集在游戏里的意义前面已经说明了。
  • Tiled Map Editor 是一款开源的地图编辑器。主要用于规划地图上的点位、路径及相关的数据。有地图的游戏应该都离不开
  • dragBones 一款骨骼动画设计工具,主要供设计师使用,开发只需要了解即可
  • particle designer,一款粒子编辑器,主要给设计师用
  • Hiero 位图编辑器,用于生成字体位图
  • tiny-png CLI 图片压缩工具,可以批量压缩图片,减少包体积利器
  • ffmpeg 音视频处理工具,主要用于压缩音频,减少包体积利器

3、不同点(非技术层面)

与设计师的合作

通常对于前端来说,设计师给出交互稿和视觉稿就可以动工了。但是对于游戏来说还远远不够。各种粒子特效、帧动画都需要设计师来做。好在游戏编辑器提供了一套对设计师友好的设计工具。设计师可以直接在编辑器中进行设计,设计输出可以推送到仓库中直接给开发使用,避免了开发从设计到开发的重复实现过程。

但是开发和设计师事先规定一套良好的设计规范仍然非常重要。

  • 命名规范。设计师命名都比较写意,从开发角度 camelCase, PascalCase, kebab-case 还是要提前规范好,所有的资源文件名应能直接看出其用途
  • 组件拆分。每次新的需求需要开发和设计师共同定义要拆分的模块的动效。避免开发过程中对设计师已经做好的特效再重新拆分重组,浪费时间。
  • git 规范。没错,我们的设计师会用 git,但他们可能在这个项目之前并没有用过,所以所有 git 规范都应该提前定义好,git-flow 和 commitlint 都要提前设置好😂

四、后记

经过将近半年的迭代,我们的项目已经走过了5个版本,迭代也日趋稳定(插播一条广告,各大应用市场搜索“ 儿流利说 ”即可看到我们的产品)。这是一个 Team 团结合作的结果,在这里感谢所有为游戏项目贡献力量的开发小伙伴们:

  • 我们的 Tech Leader 在项目初期在架构上以其深厚的项目经验给出了很多宝贵的指导和建议,让项目在初期少走很多弯路。在日常工作里也负责很多跨部门的协调工作,可以让我们更专注在开发上,感谢🙏;
  • 我们的 Team 后来也慢慢有了更多、更专业的游戏开发工程师,给后来的游戏开发提供了强有力的支持,也给我个人的游戏开发技能提升提供了很多帮助,感谢🙏;
  • 我们游戏还是运行在 Native 平台上的,客户端的同学在完成产品需求外,在游戏的编译、打包工具方面提供了大力支持。现在偶尔也来支援写游戏,感谢🙏;
  • 最后,还有和我一样身为前端工程师还奋战在游戏开发前线上的小伙伴,和你们一起写代码、code review 是一件非常开心的事,感谢🙏。

文章已于2019-03-12修改

更多推荐