游戏框架 核心科技与面试精粹 (樊松阳 著)

第一部分 架构与封装算法

第1章 UI交互 (已看)编程

第2章 玩法底层 (已看)设计模式

第3章 辅助系统服务器

第二部分 艺术资源网络

第4章 资源分类多线程

第5章 后处理效果架构

第6章 资源工做流框架

第三部分 底层核心异步

第7章 渲染原理编辑器

第8章 3D数学基础

第9章 寻路算法

第四部分 自定义扩展

第10章 调试工具

第11章 日志工具

第12章 快捷功能

第13章 后台服务

第五部分 独立游戏

第14章 角色分工

参考文献

 

第1章 UI交互

  1.1 绑定事件响应

请列出为UI控件绑定事件响应的方法, 并分析不一样方法之间的优劣

[总结]

绑定响应的方式有两种: 在组件中添加和在代码中添加. 它们的核心相同, 都是经过代理来回调本身的函数. 组件绑定的操做更友好,但容易被人更改. 代码绑定的优势是更稳定,并且能更灵活地控制监听的事件点,缺点是逻辑编译在代码中, 最好有健全的框架支持, 以防止出现使用问题.例如, 重复绑定的处理函数, 忘记移除的绑定,等等

[扩展问题]

  1. 有没有办法克服代码绑定的缺点?

  2. 既然你们都倾向于使用代码绑定, 那么组件绑定的存在有意义吗?

  1.2 事件传递流程

请从结构的角度概述,控件的消息模型包含哪些部分, 并解释UGUI中点击事件与响应调用是如何关联在一块儿的

[事件体系]

首先说明, 事件体系的基础是设计模式中的观察者模式, 所以按照标准的Subject和Observer来解读没有任何问题. 但在这里, 笔者更但愿以功能为导向, 将事件体系划分红更易于理解的模块. 按这种划分方式, 事件体系由四部分组成, 分别是:

  监测器(Monitor)

  采集器(Collector)

  派发器(Dispathcer)

  响应器(Receiver)

其中用户的操做被监视器驱动的采集器捕获, 接着监视器将反馈的信息通知到派发器中,最后经过派发器将事件传播出去

[Unity3D 实现]

在Unity3D中, 功能模块的每一个部分都有对应的实现类

  监测器 (Monitor) 对应的实现类为EventSystem. 它重写了MonoBehavior的Update方法, 会在每一帧更新挂载在同一个GameObject上的采集器组件状态, 并判断是否应该激活派发器. 若是是, 则调用各个派发器中的派发函数Process

  采集器 (Collector) 由两部分组成, 对应的实现类分别为BaseInputModule和BaseRaycaster, 在UGUI中默认使用的是它们的子类StandaloneInputModule和GraphicRaycaster. 当用户操做时, 会由BaseInputModule激活模块, 而后发出一个射线点触, 返回在BaseRayCaster中能点到的物体并返回信息, 交由派发器进行过滤. 因此采集器有两端,链接它们要靠EventSystem.整个过程当中还有一个静态内部的管理类RaycasterManager, 用来作链接采集器和监测器的数据桥梁

  派发器 (Dispatcher) 对应的实现类也为BaseInputModule, 最经常使用的是它的子类StandaloneInputModule, 该类的角色于采集器混在一块儿.派发器完成了实际的事件生成, 包括且不限于: 事件类型的肯定, 事件内容的提取, 派发对象的过滤. 其中对派发对象的获取须要借助采集器, 但须要经过监测器来驱动. 这种设计能够带来效率上的优点, 便可以合并采集操做, 以达到下降事件频率的目的

  响应器 (Receiver) 对应的实现类为IEventSystemHandler及其子类, 例如最经常使用的IPointerClickHandler, 它的做用是处理点击事件. 经过ExecuteEvents类, 能够将发生事件的对象上的全部响应器都获取到并调用其响应逻辑. 以点击为例, 事件最终会被派发到OnClick的代理上, 完成逻辑的执行

[类图]

[总结]

[扩展问题]

  1. 请结合设计模式中的观察者模式, 分析Unity事件框架的优劣

  2. 如何利用EventSystem来下降事件的频率

控制能够控制采集器中的采集频率,或派发器中派发事件的频率。理论上StandaloneInputModule组件中有InputActionPreSecond这个变量可用来控制消息输入,但查阅源码,只发现它对导航按钮作了限制,未实现其余类型的控制,代码版本为5.5。

  1.3 事件响应接口

一般对于事件传递, 绑定消息处理时都要些不少相同的框架级别方法,例如:

 

m_btn1.onClick.AddListener(OnBtnClick1);
m_btn2.onClick.AddListener(OnBtnClick2);
m_btn3.onClick.AddListener(OnBtnClick3);

public void OnBtnClick1() { }
public void OnBtnClick2() { }
public void OnBtnClick3() { }
View Code

 

请问是否有方法对其简化, 减轻开发编码量?

 

[扩展问题]

  1. 有办法对各类控件添加统一的响应接口吗?

  2. 有时候界面中会有大量不显示的按钮, 这会影响咱们的封装吗?

若是存在大量的隐藏按钮,会致使界面中加载不少无用监听,下降游戏运行效率。为了解决这个问题,能够将绑定函数放到OnEnable中,并在OnDisable中解绑

第2章 玩法底层

  2.1 游戏循环

请简述Unity 3D中经常使用的生命周期函数, 并以此为基础简述游戏循环的设计要点

[问题分析]

通常来讲, 在大型商业项目中, 咱们须要自制一套游戏循环, 来知足多变的游戏玩法, 那些现有的声明周期函数可使用?那些须要被替换掉?这些取舍贯穿了整个游戏开发过程

[生命周期]

深刻理解这张图, 能够应对大多数的时序问题. 在Initialization, Disable/enable, Decommissioning区域的函数只会调用有限次数, 而其余部分都是受控重复或循环调用, 其中

  Awake函数在构造脚本对象时调用

  OnEnable/OnDisable在每次激活对象时调用

  Start在第一次触发脚本时调用

  OnDestroy在销毁对象时调用

主循环分为物理模拟, 游戏逻辑, 渲染绘制三个子循环. 从Unity实现的方式来看, 这三个循环是在同一个线程中. 不过自Unity 3D的5.x版本中, 引擎加入了多线程渲染的选项, 即把第三步循环放在另外一个线程中进行, 这样就能够在固定帧率降低低逻辑循环的压力, 减小掉帧现象的发生. 至于物理模拟循环, 笔者以为不必拆出到另外一个线程.毕竟线程间通讯也须要性能,而物理模拟循环运行的速度通常要快于游戏逻辑, 若是出现数据访问冲突时还须要加锁,就得不偿失了

[重写模板脚本]

using UnityEngine;
using System.Collections;

public class #SCRIPTNAME#: MonoBehavior {
    #region Public Attributes
    #endregion
    #region Private Attributes
    #endregion
    #region Unity Message
//  void Awake() {
//  }
//  void OnEanble() {
//  }
//  void Start() {
//  }
//  void Update() {
//  }
//  void OnDisable() {
//  }
//  void OnDestroy() {
//  }
    #endregion
    #region Public Methods
    #endregion
    #region Override Methods
    #endregion
    #region Private Methods
    #endregion
    #region Inner
    #endregion
}
View Code

[游戏循环]

while (true) {
    Input();
    Update();
    Render();
}
View Code

从这个层面看, 游戏循环由三部分组成, 分别是

  1. 非阻塞的用户输入

  2. 更新游戏逻辑状态

  3. 渲染游戏画面

每次循环完成后, 会更新一次画面的绘制, 这个过程也被称为帧(Frame). 帧率(FPS)能够标定游戏循环的速率与真实时间的映射关系. 帧率值越小, 意味着游戏越"卡". 游戏在PC上, 一般为60帧/秒, 在手机上为30帧/秒. 另外一方面,帧率的倒数即为每帧所占用的时长, 单位一般为毫秒. 影响帧率的主要因素是每帧须要作的工做. 例如, 复杂的物理计算, 游戏逻辑的处理, 图形细节控制等, 这些都会占据CPU与GPU. 若是处理操做的时长超过帧率的倒数,那么就会拖慢帧率,这种现象称为"掉帧"

[固定帧率模式]

while (true) {
    double start = getCurrentTime();

    Input();
    Update();
    Render();

    sleep(start + 1/FPS - getCurrentTime());
}

Application.targetFrameRate = FPS;
View Code

[追赶模式]

double preFrameTime = getCurrentTime();
double lag = 0.0;

while (true) {
    double current = getCurrentTime();
    double elapsed = current - preFrameTime;
    preFrameTiem = current;
    lag += elapsed;

    Input();

    while (lag >= 1/FPS) {
        Update();
        lag -= 1/FPS;
    }

    Render();
}
View Code

[总结]

Unity 3D做为完整的引擎, 常见的生命周期函数与游戏循环模式都已具有. 但做为特定的游戏, 一般有本身的特色. 例如, 竞速类游戏与MMO网游在游戏循环的设计上就有很大的差异. 竞速类游戏对实时反馈的要求很高, 若是采用追赶模式, 则抽帧形成的体验就会不好. 在掉帧方面, MMO网游面临的则是角色在场景中漫游时, 其余玩家的模型加载与位置同步形成的卡顿. 在这种状况下, 可能会使用分帧加载, AOI(Area Of Interset)等处理方法来保障游戏的流畅

[扩展问题]

  1. 请尝试分析多线程渲染的实现原理

在执行Render时,会在另一个专门的线程中进行。好处就是能够利用多核的优点,减小了等待时间

  2.2 时间记录

请问在Unity 3D中常见的时间分为哪几类, 获取和记录它们的方法有哪些? 它们的适用场景分别是什么?

[真实时间线]

真实时间线指的是, 以真实时间的流逝做为标准,进行时间统计的时间线.从重要程度上讲, 它表明着绝对的时间流逝, 是一切时间的参考,不一样设备获得的结果必定是相同的,这也是整个时间记录的基石

Time.realtimeSinceStartup
View Code

[游戏时间线]

游戏时间线是应用更普遍的时间计量方式.它以游戏世界的时间流逝速度做为标准度量时间.不少状况下,它与真实时间线不一样.

[间隔时间]

间隔时间的计算方法为计算两帧之间的差时.

Time.timeScale
Time.deltaTime
Time.unscaledDeltaTime
Time.smoothDeltaTime
View Code

[扩展问题]

  1. 如今有两种状况引发游戏间隔时间变长: 一种是目标机器性能差, 须要追赶补偿效果; 另外一种是断点调试, 不但愿影响逻辑. 请问有没有办法在设计时间线时, 过滤掉断点调试的状况

通常说来,目标机器卡顿与断点调试的延迟不是一个数量级的,能够经过时长的判断,例如是否大于0.1s,来区分这两种状况

  2.3 动画事件

请简述动画事件的制做与逻辑处理方法, 若是有多种备选方案, 请对比它们的优劣

[问题分析]

所谓动画事件, 指的是动画进行到某个时刻触发的事件

[基于动画的事件]

编辑器里编辑

[基于时间的事件]

另外一种常见的事件是逻辑型事件. 虽然它也与动画的状态紧密相关, 但这些事件种包含着逻辑处理. 例如, 角色在攻击动做的过程种触发伤害: 或者在攻击出拳的动做过程当中向前移动,遇到敌人就中止移动. 这些事件与逻辑紧密相关, 不只取决于美术的表现效果,更多的是数值与逻辑的合理性

对于这种事件, 采用基于时间的动画事件就更为合理. 在制做事件时, 咱们记录的是局部时间线的偏移值, 并将它以独立文件的形式存储. 在上一节中, 咱们知道游戏会存在一条游戏时间线. 当动做开始时, 咱们会将这些事件时间偏移量并入游戏时间, 并在游戏时间线的对应时间添加触发事件的回调, 这样事件触发就彻底脱离了模型动画

这样作的另外一个优点是显示与逻辑的分离. 在同屏多人的游戏中, 有时会不播放角色动画, 只产生技能触发的需求. 在这种事件框架下, 就能够只运行基于时间的事件而不去加载模型与动画. 由于伤害触发与计算不须要依赖于动画, 因此一切逻辑均可以正常运行. 另外, 在有异步数据影响的场景中, 这种事件控制方法也有优点, 它能够预先作好逻辑计算, 再对动画作插值处理, 数据优先于动画直接进行同步, 将网络延迟对游戏的影响降到最低

[扩展问题]

  1. 在后期优化时发现同屏特效过多, 在处理基于动画的特效事件时存在性能瓶颈, 请问该如何优化

对于这种状况,制做流程能够保持不变。但在播放特效时,调用统一的代码接口,在接口函数中根据场景中的特效数量进行抉择与控制

  2.4 游戏同步

请问在游戏同步方面有哪些分类方式?帧同步与状态同步有何优劣?

[游戏模式]

根据游戏中玩家之间的互动方式, 能够将多人交互类型的游戏大致上分为两个类别: MO与MMO. 这两种典型的模式有着彻底不一样的侧重点和技术架构

多人在线游戏(Multiplayer Online, MO) 指的是少许玩家汇集在一块儿的竞技游戏, 它的侧重点是实时对战的战斗乐趣, 所以游戏追求告诉的响应时间, 以及频繁的交互操做. 相对应的, 游戏时间一般在几分钟到几十分钟不等, 一般采用房间或副本的形式进行游戏, 玩家数量控制在20人之内. 常见的类型为 ARPG, FPS, 格斗, RTS, 竞速等

大型多人在线游戏(Massively Multiplayer Online, MMO) 则是指望大量玩家进行长期的社交性游戏.它追求的是一种在虚拟社区中的存在感, 所以游戏周期一般几个月以上.因为要处理大量的玩家数据, 不免会牺牲响应时间, 所以在MMO游戏中, 一般不会有高速的交互.为了能长期运行, 系统的稳定性与可维护性也是框架的重中之重

MO与MMO在游戏内容上有很大差异, 所需的技术也大相径庭, 但一个游戏重同时存在两种模式并不冲突. 常见的作法是, 将MO类型的游戏以副本的形式嵌到MMO中, 并分别采用独立架构对游戏逻辑提供支持. 这样就能够兼顾MMO的玩法多样性与MO对战的乐趣性. 在WoW中, 野外战斗与大部分副本都是以MMO架构实现的,而少数在短期内须要玩家紧密配合, 历尽艰难才能通关的副本则是经过响应更快的MO架构实现的

[通讯方式]

  1. C/S架构

从通讯结构上看, 同步能够分为C/S与P2P两种. C/S是Client/Server的缩写,指的是客户端与服务器链接的消息传递方式. 在这种框架下, 全部消息都经过服务器转发.它的游戏在于能够很好地控制非法行为. 服务器知道一切数据, 一般也会对数据进行模拟计算, 所以, 做弊行为很难出现, 另外一方面客户端也能够只作表现, 彻底由服务器的数据驱动. 这种方式经常使用在不须要, 或不多须要预表现的游戏中

这种结构的最主要弊端是网络延迟较大, 一般会控制在200ms如下. 另外一方面, 中央服务器上大量的运算也会增长服务器的设备成本. 在商业项目上线以前,一般会进行压力测试, 主要关注点在带宽, 链接数, 响应速度等. 经过模拟大量玩家的操做, 来测试服务器硬件是否可以应付预期数量的玩家

  2. P2P架构

与C/S架构不一样, P2P架构是两个客户端之间直接链接. 它与C/S结构的优劣正好彻底倒置, 它有低延迟的优势, 也能够节省大量的服务器运算, 却很难避免做弊的行为. 例如, DOTA中的全图外挂没法禁掉, 根本缘由就是客户端拥有其余玩家的数据

[数据模式]

  1. 全网状数据模式

全网状数据模式指的是, 每一个客户端须要给网络中的每一个设备广播本身的数据. 所以每一个设备上有完整的数据, 各个终端之间传递的只是控制设备的输入信息. 能够预想到, 随着网络中终端数量的增长, 信息量会急剧增加. 因为消息量大, 所以不一样客户端之间一般只会传递用户输入的信息, 这就须要保证每一个客户端的初始数据与每次操做的运算结果必须彻底相同, 不然就会表现不一致

  2. 星型数据模式

另外一种状况是网络中的成员并不平等, 全部终端都要依从中央终端的数据. 这个终端在C/S架构中被称为中央服务器, 在P2P架构中被称为主机, 这里为了方便说明, 暂称为服务器. 在标准的星型结构中, 服务器在收集完全部设备发送的数据以前, 一直处于等待状态, 接收完成后再将数据广播出去, 所以它是将输入数据暂时集中到服务器上, 这一点与全网状数据模式彻底不一样.这种结构避免了终端过多致使的数据暴涨, 但也会引发数据传输速度的降低, 有利有弊, 须要根据本身项目的实际状况作取舍

[同步方式]

  1. 状态同步

  本节中提到的状态同步和帧同步都是广义上的, 虽然它们的底层实现均可以基于上面提到的模式, 但从广义讲, 它们的同步思路不一样

  状态同步指的是将其余玩家的状态行为同步, 例如, 怪物的AI控制, 角色技能释放, 战斗伤害计算等等. 纯粹的状态同步模式下, 这些内容都由服务器运算, 只是将结果下发给客户端, 客户端根据获得的数据驱动显示而已. 这种模式的缺点是流量消耗比较大, 消耗的流量取决于场景中须要转发数据的人数和内容. 另外, 先天的结构致使响应速度存在问题, 没法作到高频交互的流畅体验. 最明显的特色就是"拉扯"现象. 它表现为某个角色会忽然出如今某个位置, 或某个技能效果忽然出现, 甚至角色突然死亡等. 引起这个现象的缘由是, 网络波动致使数据未能及时送达, 而客户端进行了某种程度的预表现或航位推测

  对于大多数实时性要求不高, 交互简单的游戏, 通常会使用状态同步. 一般来讲, 常规MMO类型的游戏只能使用状态同步

  2. 帧同步

帧同步是指客户端只同步用户的输入指令, 例如, 向前走, 按下哪些技能等, 不一样的客户端各自计算本身的结果. 因为消耗的流量只取决于指令数, 所以会大大减小消息. 另外因为消息结构的缘由, 帧同步的速度要比状态同步更快, 所以适用于一些高频交互的游戏

不过因为每一个客户端都要独立计算, 所以须要保证计算结果的一致性. 理论上讲, 相同的时机, 输入相同的内容, 会获得相同的结果. 不过从实际状况看, 作到彻底相同仍是有些难度的. 以Unity 引擎为例, 一方面, 各个脚本之间Start, Update等生命周期函数的调用顺序不肯定: 另外一方面, 使用Physic物理系统也不保证是肯定性模拟. 好比一个单位的坐标误差了0.01致使技能未能命中目标, 那么这个目标的血量判断就会受到影响. 若是这个目标的行为受到血量的影响, 那么最终的结果会彻底不一样. 所以让每一个客户端计算结果彻底相同不是一件容易的事, 一些常见的注意事项以下:

  1. 不使用浮点数, 用整数代替  

  2. 不一样客户的同步频率要保证一致

  3. 随机种子相同, 并自定义接口防止其余公用系统干扰

  4. 使用排序容器, 保证遍历顺序

  5. 逻辑显示分离

  6. 使用补间过渡, 调整速率, 掩盖卡顿

除了一致性的难点, 帧同步还须要解决流畅度的问题, 因为经过网络传输过来的数据必定会慢于本地, 而咱们又但愿在相同的时刻输入信息, 所以就会引起等待, 反映到用户体验就是不流畅

优化方法有不少, 例如, 在帧同步游戏中, 因为广播的频率很是高, 所以每次广播的数据就要足够小, 这样能够节省不少消息处理的时间. 对于消息, 能够将须要全部客户端同时发生的内容提早广播给其余用户, 采用时钟同步. 客户端逻辑先行, 显示经过平滑追赶的方式处理. 不少改进是体验优化的范畴, 须要结合具体游戏进行

在传输层, 移动端的同步建议使用UDP做为传输协议. TCP为了保证传输的可信性, 不少机制不太适合波动较大的移动网络. 在弱网络环境下, UDP的RTT几乎不受影响, 而TCP的RTT波动比较大, 特别是在丢包重发时影响比较明显. 虽然使用UDP会引入丢包,丢包的问题.但能够经过冗余的方式来解决这个问题. 好比每帧三个数据包, 其实是包含了过去两帧的数据,也就是每次发三帧的数据来对抗丢包

[扩展问题]

  1. 请问帧同步中的锁帧(Lockstep)是指什么?

锁帧指的是若是未收到消息,就不该该继续向下执行的同步模式,它是帧同步能同步执行的核心。但锁帧会影响流畅性,所以一般会作些定制化的更改。

第3章 辅助系统

  3.1 有限状态机

 

  3.2 脚本系统

 

第4章 资源分类

  4.1 贴图种类

 

  4.2 材质效果

 

  4.3 动画分类

 

  4.4 流动效果

 

第5章 后处理效果

  5.1 模糊效果

 

  5.2 泛光效果

 

  5.3 辉光效果

 

  5.4 景深

 

第6章 资源工做流

  6.1 图片格式更改

 

  6.2 动画抽取

 

  6.3 文件移动检测

 

第7章 渲染原理

  7.1 渲染管线

 

  7.2 渲染顺序

 

第8章 3D数学基础

  8.1 点和向量

 

  8.2 向量的运算

 

  8.3 区域检测

 

  8.4 平面移动

 

第9章 寻路算法

  9.1 寻路这件事

 

  9.2 A*算法

 

  9.3 Navigation系统

 

  9.4 任务调配

 

第10章 调试工具

  10.1 GM命令

 

  10.2 绘制曲线

 

  10.3 指示绘制

 

第11章 日志工具

  11.1 出错暂停

 

  11.2 日志接口优化

 

  11.3 频道化日志

 

  11.4 崩溃日志上报

 

第12章 快捷功能

  12.1 自定义菜单

 

  12.2 定制UI

 

  12.3 回退操做

 

第13章 后台服务

  13.1 编辑器服务

 

  13.2 自动注册框架

 

  13.3 遍历文件

 

第14章 角色分工

  14.1 产品策划

 

  14.2 美术设计

 

  14.3 运营知识

 

    14.3.1 用户规模数据

 

    14.3.2 用户价值数据

 

  14.4 总结

 

参考文献

  1. 游艺网教育部. 次世代游戏开发基础. 北京: 清华大学出版社, 2015.

  2. 游艺网教育部. 次世代游戏角色制做. 北京: 清华大学出版社, 2015.

  3. 李瑞森, 张卫亮, 王星儒. 网络游戏场景设计与制做实战. 北京: 电子工业出版社, 2015

  4. Mike Bailey, Steve Cunningham. Graph shaders(Second Edition). 刘鹏, 译. 图形着色器----理论与实践(第二版). 北京: 清华大学出版社, 2012.

  5. Rick Parent. Computer Animation algorithms and Techniques, Second Edition. 刘祎, 译. 计算机动画算法与技术(第二版). 北京: 清华大学出版社, 2012.

  6. Robert Nystrom. Game Programming Patterns. GPP翻译组, 译. 游戏设计与开发. 北京: 人民邮电出版社, 2016.

  7. 中嶋谦互  网络游戏核心技术与实战. 北京: 人民邮电出版社, 2014.

  8. Scott Rogers. The Guide To Great Video Game Design(Second Edition). 孙懿, 高济润, 译. 通关! 游戏设计之道(第二版). 北京人民邮电出版社, 2017

  9. Eric Lengyel. Mathematics for 3D Game Programming and Computer Graphics, Third Edition. 詹海生, 译. 3D游戏与计算机图形学中的数学方法(第三版). 北京: 清华大学出版社, 2016

  10. 冯乐乐. Unity Shader入门精要. 北京: 人民邮电出版社, 2016

  11. Sanjay Madhav. Game Programming Algorithms and Techniques. 刘瀚阳,译. 游戏编程算法与技巧. 北京: 电子工业出版社, 2016

  12. 王睿杰等. 创世学说: 游戏系统设计指南. 北京: 电子工业出版社, 2016.

  13. 于洋, 余敏雄, 吴娜, 师胜柱. 游戏数据分析的艺术. 北京: 机械工业出版社, 2015

相关文章
相关标签/搜索