• 实现快速迭代的引擎设计


    [译]实现快速迭代的引擎设计 - Capcom RE Engine的架构与实现

    原文(日文):ラピッドイテレーションを実現するゲームエンジンの設計

    CEDEC2016上的一个Session。基本上是根据PPT的翻译(可能成为笔记更恰当一点),夹杂了一些现场听来的信息。PPT里有很多优点举例基本没什么营养就省略了。没正经的翻译过文章,有错误欢迎指正。主要是来抱囧聚大腿的。

    以下正文:

    引擎简介

    • RE Engine是Capcom内部开发的次世代游戏引擎
    • 支持PS4,XboxOne,PC(Steam/UWP)等平台
    • 应用于开发中的项目生化危机7
    • 引擎特征:高性能,高开发效率 <- 本文主要内容

    游戏开发循环

    • 编程 → Build → 确认 → ...
    • DCC工具编辑 → 资源转换/导出 → 确认 → ...
    • 游戏启动 → 测试/试玩 → 参数调整 → ...
    • QA → Bug修正 → 打包 → ...

    传统开发流程中,上面各个步骤不是费时间就是需要大量手工操作,效率相对低下。 

    快速开发的实现

    → 最终节省下来的时间用来提升游戏质量

    • 减少等待时间(主要是Build以及资源转换等)
    • 减少每回试错的时间
    • 提高在运行环境(特别是主机上)中运行的频率
    • 降低Bug率

    RE Engine界面(译注:基本与UE4或Unity相近,支持所见即所得的编辑方式) 

    一些细节

    • 生化7目前大约有20万行C#
    • Editor里可以直接运行(整体更像UE4一点),支持所有资源的重新载入
    • Scene是树状结构,有一个Master Scene,里面有Chapter1Scene等,导出Master Scene就等于是打包游戏。随着游戏流程Load/Unload Scene
    • 演讲的 石田 智史 就是设计了MT Framework的人

    Scene的结构示例 

    ----------------------------------------------------------------------------------------------------------

    引擎架构

    (译注:演讲包括了新旧引擎的对比,但旧引擎的参考价值不大,故基本省略) 

    • 工具架构(Editor)
      • 工具和Runtime用TCP/IP通信同步
        • Runtime就算崩溃了Editor也能继续运行
        • Runtime包含了各个平台的实现(如PS4的Runtime)
      • 工具部分用WPF/C#开发,运行于Windows平台
      • 问题点
        • Runtime使用C++,而工具使用C#开发(包括游戏逻辑)
        • 通信同步导致大量操作需要异步的实现,增加了复杂度
        • 通信受限于传输速度
      • 解决方案
        • 统一通信协议:二进制通信,用C#的定义生成C++代码,Query/Response形式
        • 远程对象系统:跨语言,跨进程的标识对象
          • RuntimeType,RuntimeObject跟C#自己类似,成员貌似只是key-value对
          • 通过ObjectID来同步(ID的正负来标识Editor/Runtime)
          • 以手动同步为主,也可以注册进Observer来监视更新,对性能有影响
          • Gizmo之类的辅助对象也采用类似的方法实现
        • 优点
          • Runtime可以自由切换
          • 可以对应一些只能在实际环境确认的功能(VR,手机等)
          • Runtime经常会崩溃ww
    • 资源架构
      • File-Based(旧)→ Asset-Based(新):文件更容易产生冗余,并且不利于Reload
      • 所有资源支持异步更新,用Cache加速资源转换,根据依赖关系优化打包速度
      • 问题点
        • Scene加载的时候需要预载入所有资源
        • 引擎需要把握资源的载入顺序
        • 需要实现异步载入的功能
      • 解决方案
        • 静态资源依赖关系生成
          • 导入Asset的概念:在文件里追加了Metadata
            • 工具启动的时候载入所有的Asset,并生成依赖关系
          • 禁止代码控制的资源载入(译注:是不是完全不支持动态载入没有细说)
        • 严格的资源管理(译注:听起来自由度相当低,但毕竟是内部引擎估计可以根据实际需求调整而不用Hack)
          • 禁止同步载入(容易导致Spike),并且异步载入有利于整体吞吐量
          • 强制实现Reload
          • 从游戏代码不能直接访问资源

    资源依赖关系示例 

    • 脚本架构
      • 旧引擎开发游戏即使用上分布式Build多的时候也要15分钟,并且容易崩溃,内存破坏
      • 新引擎开发游戏时,游戏的逻辑部分完全使用C#,Build最多10秒,内存管理更好
      • 问题点
        • C#比C++更慢(Marshal,异常)(译注:猜测Marshal应该主要是与SDK的交互或者与第三方库的交互)
        • GC导致的长时间停止
        • 主机平台兼容性差(JIT禁用等)
      • 解决方案
        • 自制C#虚拟机(REVM)
          • 以AOT为前提,不支持JIT
          • 提高C++亲和性,降低Marshal开销
            • 重写C#对象布局 ↓ ,包了一层C++对象在里面,Marshal的时候可以直接传指针。RC为引用计数。
            • C# → C++:利用Clang解析C++代码,生成供C#访问的接口,最终和直接调用C++函数开销相同
            • C++ → C#:利用template,最终和函数指针开销相同(译注:黑魔法没仔细看。。。)
          • 更轻量的标准库(从Core FX搬)
          • 编译
            • 开发时:MicroCode(IL不适合解释执行,自制MicroCode) → 解释执行
            • Release时:C++(il2cpp)→ 编译(Marshal部分inline展开)
              • 编译时间:分散编译15分钟左右(跟旧引擎相近)

    C#的编译流程(Release时生成C++代码) 
    C#与C++的运行速度比较(有了GC当后盾对象生成快了很多) 
    开发时与实际运行时的效率比较(译注:可以看出来开发时候也不是特别惨) 

    • 自制实时垃圾回收(译注:有些概念不太懂就没详细解释了)
      • 现有的GC不适合游戏
        • 分代GC:Major GC导致的长时间停止
        • 并行GC:GC执行中的速度低下,需要足够的空余内存
        • 自动引用计数方式:循环引用的泄露,对引用计数操作的高Overhead
      • 适合游戏的实时GC(FrameGC)
        • 限定于游戏应用
        • 主循环的同期点
        • C#作为脚本的前提
      • 特征
        • 预测,可控的停止时间(GC中)
        • 即时释放性(译注:用完的内存尽早释放)
        • 可以更有效的利用所有内存
        • 在多核环境下发挥性能
        • 与C++的亲和性
      • GC算法(译注:原ppt内有简单的流程动画说明可供参考)
        • LocalObject:TLS,存在LocalTable里(当然也是TLS),C#生成的对象都作为LocalObject
        • GlobalObject:所有线程都能访问,C++生成的对象都作为GlobalObject
        • LocalObject → GlobalObject(译注:有点类似C#的Box只不过这里不是stack和heap的区别而是可访问线程和所属Table的区别)
        • LocalFrame GC:C#的函数调用都结束的时候执行,释放所有LocalObject(译注:跟函数调用完了退栈差不多)
        • Local GC:LocalTable满了的时候执行,根据引用计数释放LocalObject(实际很少发生)
        • GlobalObject 释放
          • 由各个线程增减引用计数,为0了就释放
          • 当没有所有线程里都没有C#的栈(C#的调用都结束)的时候,执行Global GC(释放所有被有被用到的)
        • 循环引用由Incremental Cycle GC释放
          • (译注:基本上就是扫一遍找出循环部分)
          • 每帧Check循环引用的辅助表CycleRoot,按需GC
      • FrameGC的Overhead
        • Write Barrier:线程内的直接Check引用计数,跨线程的利用InterlockedCAS
        • LocalObject→GlobalObject:类似↑

    FrameGC的一帧的Profile结果(译注:整数估计是Profile的Sample数)

    ----------------------------------------------------------------------------------------------------------

    总结

    • C#就是好!
      • 生产效率大幅提升
      • 杜绝应用层(游戏逻辑)产生的崩溃情况
    • 执行效率没有问题!
      • REVM比写的挫的C++更快
      • FrameGC改变了GC不适合游戏的常识

    ----------------------------------------------------------------------------------------------------------

    现场Q&A

      • C#版本? 6.0 但是因为造了GC所以不算完全支持。
      • 支持yield吗?应该不支持(?),C#的部分不支持跨帧的处理。
      • 开发人数?开始2人 x 3个月,后来加了5,6人又做了几个月。(译注:听起来有点快的可怕,不过去年CEDEC的时候就有相关消息了所以实际应该更长一点)。感谢评论里@大萨比补充,现场有提到了开发速度快是因为RE引擎基于panta rhei开发。
      • 序列化用了什么?第三方库?没有,因为涉及到ID之类的问题所以自己写了。
      • 转成C++代码的时候出现问题了怎么办?那是bug...初期会有,现在已经基本没有了。顺便转出来的C++代码很长,VisualStudio会打不开...
      • 游戏部分完全是C#吗?完全是。
      • 如果开发游戏的那边提需求呢?因为是内部引擎所以可以商量,但不会让他们直接改。
      • 场景中的顶点数之类的统计数据可以取得吗?Asset的meta信息里有,可以做。但是美术基本不关心这个....
      • WPF的框架?Livet。
      • 中间件是集成到引擎里?是的。
      • 对中间件有什么要求?省内存,跑的快(笑)
  • 相关阅读:
    操作标签的属性和属性值 table表格
    dom基本获取 标签文本操作
    延时器 清除延时器
    倒计时
    电子时钟
    时间戳
    设定时间的方法
    内置对象Date
    对象的基本特点
    终于有人把云计算、大数据和 AI 讲明白了【深度好文】
  • 原文地址:https://www.cnblogs.com/88999660/p/5842862.html
Copyright © 2020-2023  润新知