游戏性基础系统

游戏性基础系统

  游戏性基础系统(gameplay foundation system):

    1)运行时游戏对象模型(runtime game object model)

    2)关卡管理及串流(level management and streaming)

    3)更新实时对象模型(real-time object model updating)

    4)消息及事件处理(messaging and event handling)

    5)脚本 (scripting)

    6)目标及游戏流程管理(objectives and game flow management)

  其中1)运行时对象模型是最复杂的,通常它需要提供以下功能:

    1)动态地产生(spawn)及消灭(destroy)游戏对象。

    2)联系底层引擎系统

    3)实时模拟对象行为

    4)定义新游戏对象类型

    5)唯一的对象标识符(unique object id)

    6)游戏对象查询(query):根据ID查找对象,或是根据任意条件做高级查找,如寻找玩家20m以内的所有敌人

    7)游戏对象引用(reference)

    8)有限状态机(finite state machine, FSM)

    9)网络复制(network replication)

    10)存档及载入游戏、对象持久性(object persistence)

运行时对象模型架构

  游戏对象模型的2种架构:

    1)以对象为中心(object-centric):每个对象含一组属性及行为,这些都会封装在那些对象实例的类之中。游戏世界只不过是游戏对象的集合。容易产生单一庞大的类层次结构(monolithic class hierarchy)。一个类越是在类层次结构中越深的地方,就越难以理解、维护及修改。因为要理解一个类,就需要理解其所有父类。

    此方式最大的问题是,当设计层次选择了某个标准,就很难甚至不可能用另一个完全不同的标准分类。比如按物种分类的生物,要按颜色分类就不好办。mix-in类可以改善改方式。一个类可以派生自主要继承层次结构中的一个且仅一个类,但也可以继承任意数量的mix-in类(无基类的独立类)。通常更好的做法是合成(composition)或聚合(aggregation),而不是继承他们。

    把Window派生自Rectangle类。然而,一个视窗并不是一个长方形,它只拥有一具长方形,用于定义其边界。因此,这个设计问题的更好解决方法是把Rectangle的实例安置于Window类之中,或是令Window拥有一个Rectangle的指针或参考。面向对象设计中,“有一个”的关系称为合成(composition),合成的对象与主对象有同样的生命周期。对于主对象与子对象生命周期不同步的设计,称为聚合(aggregation)。

    要降低游戏类层次结构的宽度、深度、复杂度,一个十分有用的方法是把“是一个”关系改为“有一个”关系。业界成熟的方式是使用组件模式:

      1)“枢纽”HUB组件模式

      2)通用组件模式

      3)纯组件模式

    2)以属性中心(property-centric):每个游戏对象仅以唯一标识符表示。我们先定义游戏对象可能含有的属性集合,然后为每个属性建表。此设计更能有效地使用内存,因为我们只需存储实际上用到的属性。SoA(struct of array)的性能优于AoS(array of struct)

世界组块的数据格式

1、二进制对象映像,把每个对象的二进制映像(binary image)写入文件,对于指针和虚函数表需要特殊处理。

2、序列化(serialization),XML解析之慢众所周知。

3、生成器(spawner)是游戏对象的轻量、仅含数据的表示方式,可用于运行时实例化及初始化游戏对象。

游戏世界的加载和串流

  游戏加载系统主要有2个功能:

    1)人磁盘加载游戏世界组件及其它用到的资产至内存中。

    2)管理这些资源的内存分配及释放。

  加载包括:

    1)简单的关卡加载,玩家需要等待关卡载入,期间会显示静态或简单动画的二维加载画面。

    2)串流加载,即预加载

    3)关卡加载区域,区域之间可能会有重叠,每个区域配有一个表,列出玩家位于该区域时内存应该包含的世界组块。

  内存管理采用池分配器,为每种类型对象生成一个池分配器,避免内存碎片。

  游戏存档分为:

    1)存储点存档,这法较为简单,因为可以避免对大量世界数据的记忆

    2)任何地方皆可存档

对象引用与世界查询

  几个实现对象引用的方式:

    1)指针

    2)智能指针

    3)句柄。句柄会有引用过时对象的问题,解决此问题的方案之一是,在每个句柄中加入唯一的对象标识符。

  游戏对象查询可以提供几下以种能力:

    1)以唯一标识符搜寻游戏对象

    2)对合乎某条件的所有对象进行迭代

    3)搜寻射线路线以及范围内物体

实时更新游戏对象

  “差一帧”问题引起的状态不一致是bug的主要来源。相比游戏对象模型,低阶引擎子系统才是性能关键。

事件与消息泵

1、为每种事件定义一个虚函数,如OnExplosion()。这种方式的坏处在于,所有的对象都必须实现OnExplosion,即使是不响应此函数的对象也要实现。

2、把事件封装成event,基类event提供类型,子类event提供具体内容,通过统一的OnEvent(Event *evt)来传递。

  关于事件的类型,有2种方法:

  1)enum枚举,此法有以下几个缺点,首先,所有的类型聚合在一起,破坏封装。其二,类型硬编码,其三,硬编码被改变后,对于存储于磁盘上的内容无法修改。

  2)使用字符串,缺点是占用内存点,消耗大。

  关于事件的参数,有以下几种方法:

  1)统一的variant集合

  2)键值对,此法可以解决variant的次序问题。

  事件的参数必须深拷贝出去,采用池分配可以有效减少内存碎片。但是采用event模式,会加大调度困难,因为无法追溯事件的发送者。

3、事件处理器,即OnEvent的实现可以用switch来实现。另外,在设计上可以采取职责链模式。

4、使用事件注册机制可以减少需要响应事件在范围。

5、即时消息发送可能会导致栈开销过大,层次过深。

6、事件响应的逻辑可以通过世界编辑器开放给策划,由策划来完成。

7、事件机制可以提供排队的功能,以及延时响应功能。

脚本

  游戏脚本语言通常有2种:

  1)数据定义语言(data-definition language)

  2)运行时脚本语言(runtime scripting language)

  对于大多数工作室而言,更合适的方未能是选择一个知名且成熟的脚本语言。游戏引擎必须要能执行脚本代码,脚本代码也需要发志引擎中的操作。运行时脚本语言的虚拟机通常是嵌入游戏引擎中的。引擎启动虚拟机,需要时执行脚本代码,并管理脚本的执行情况。

  脚本需要和游戏对象互动,其中一个方法是在脚本中以不透明的数值类型句柄(handle)来引用对象。

原文地址:https://www.cnblogs.com/tekkaman/p/3649018.html