一 整体概述
1.1 设计思路
- 产品定位:开放的共建的地图无限大的游戏系统
- 设计原则:开放性,允许社区成员参与共建,共同扩大游戏空间,丰富游戏内容
1.2 整体架构
整体架构如上图所示。
- 游戏视觉层:根据后端返回的数据,进行前端的视觉渲染,可采用前端框架,也可以采用游戏引擎进行
- 连接层:客户端长连接的管理和数据的接收机发送
- 游戏后端:游戏数据的处理、存储、和查询,游戏后端设计为读写分离
- 快速存储层:游戏数据对实时性要求极高,因此数据更新和读取发生在内存(Redis)中
- 持久化层:游戏数据持久化存储,内存(Redis)中的数据会定期同步到持久化层中,以保证数据的一致性
- 链层:游戏中的关键数据,如资产、关键事件等信息将同步写入到链上
- 管理后端及其前端:对全局的数据,如地图、NPC、游戏任务、游戏公告等信息进行管理
- API 接口网关:系统允许用户通过 API 参与到游戏世界的构建中
1.3 系统模块
系统可划分为如上几个子系统
- 地图系统是整个系统的基础,构成游戏空间的基础,地图系统负责游戏世界状态的管理和世界数据的读写
- 实体系统,是整个游戏空间可被识别的单元(可赋予唯一 ID 并关联到地图上)的管理系统
- 游戏系统:赋予游戏世界以意义的系统
-
- 玩家系统:
-
-
- 玩家在系统属于无形实体,拥有自己的 ID
- 玩家可以拥有自己的精灵(英雄),做为自己的代理者参与到游戏世界中
- 玩家在游戏世界中的任何行为都通过他的精灵(英雄)来完成
- 在引入 AI 之前,玩家是游戏世界唯一的事件源
-
-
- 数值系统:对系统中的各种游戏数值进行统一管理的平台
- 经济系统
- 社交系统
- 任务系统
- 公告及通知系统
二 系统数据及业务描述
2.1 地图/世界数据
WorldData 是系统中的基础数据,有且仅有一份。WorldData 以网格块的形式保存在系统中,每个块都有唯一的索引。实体数据被关联到其所在的块上。当实体数据有位置更新时,实体数据会在不同的块中移动。WorldData 的写入操作是块内同步的,块与块之间可以设计成异步的方式。
2.1.1 地图网格
- 系统中的地图切分成一个个固定大小的网格块进行存储,每个块的大小和属性可以在系统后台配置
- 将大的地图切分成小块的背景进行传输,降低数据传输大小
- 每个网格都关联一个对象列表,表示当前处于该网格上的对象数据
- 地图的网格数据在全局中有且仅有一份,构成游戏一致性的空间
- 允许地图和地图上的元素按照一定的规则自由扩张
- 每个网格块所能容纳的实体数量存在一定的上限
- 玩家的操作视域为当前玩家所在的屏幕视图外扩一个(可配置)单位的网格区域
- 一个操作视域中存在的实体越多,服务端的并发越高,根据服务端的并发和网格块的实体上限,可计算出操作视域的最大块数量
2.1.2 地图示例
如下是一个网格的示例,实际的网格可能要更大些。
2.2 实体数据
2.2.1 实体类型
实体系统可分成有形实体和无形实体两种
- 有形实体,即可赋予视觉属性的实体,包括位置、形象、大小、高度
-
- 无生命物:没有生命活力的实体,自身属性不可主动改变的实体
- 有生命物:自身属性可主动改变的实体
-
-
- 精灵(英雄):玩家对应精灵,AI 智能对应精灵,精灵即行动可有特定目标的实体
- 动物:可主动改变自身位置的生命实体,行为的目的是被规定
- 植物:不可主动改变自身位置的生命实体,行为的目的也是被规定的
- 其他:允许开发者扩展实体的类型
-
- 无形实体,不可赋予视觉属性的实体
-
- 动作:实体属性的变化,构成一个动作,如走(移动)、跳跃、跑、旋转、弯腰......
- 事件:现实世界与虚拟世界的一次交互称为事件,事件可能导致动作的发生,引起实体状态的变化,也可能不被响应
- 事务
- 任务,子任务
- 会话
2.2.2 主要数据结构
2.3 数据传输
2.3.2 数据传输
地图的网格块数据(MapBlock)是全局唯一的,定义为 WorldData ,用户端的操作通过长连接传入到服务端,并最终更新到 WorldData 上。用户端呈现的状态,由服务端的传出数据进行渲染,传出数据包含操作视域的数据和全局的公告等信息。为降低数据传输损耗,操作视域传输的为增量数据。
2.3.3 数据一致性与网络延时
在线游戏的网络延时是影响游戏体验的最重要因素,也是游戏系统设计的难点之一。本系统通过玩家计数器来解决这个问题。系统为每一个玩家分配一个计数器,计数器的数值通过 ViewData 传到客户端。
- 用户启动时,读取 ViewData 中的计数器 Count,并使用 ViewData 生成游戏界面,此时端云的计数器必然保持一致
- 用户执行某个操作后,客户端根据操作改变客户端中 ViewData 的状态,并在游戏界面呈现,以保证用户的游戏体验
- 用户的操作将推入到一个操作栈中,用户的计数器 +1,同时将操作传给云端
- 云端对操作进行处理,并将云端计数器也 +1,下一个下发的 ViewData 中将包含最新的计数
- 客户端使用最新的 ViewData 再次渲染客户端(改变其他玩家的位置和交互事件)
- 在网络环境较好的情况下,端云的计数器基本能保持一致
- 如果网络环境较差,则端云计数器不一致,此时客户端的 ViewData 可能领先云端的 ViewData,
-
- 如果网络请求可持续进行,则随着请求的完成,ViewData 仍然能保持一致
- 如果网络断开,则客户端无法同步云端的 ViewData,客户端的 ViewData 会改变,如果网络恢复,用户的操作或者舍弃或者恢复
- 可以设置计数器的最大差值,如端云计数器差值在 10 以上时,自动停止端的 ViewData 渲染,显示网络异常的提示
2.4 面板数据
2.4.1 业务描述
与传统游戏不同,本游戏也承担了信息聚合的功能。面板被设计用来呈现信息,数据传输与游戏数据传输不同。面板信息,可以是图片、文本、网页,也可以是文档视频等信息。面板的一种展现方式如下所示:
2.4.2 面板数据结构
- 数据内容(Panel Content):
-
- 静态内容:文本、图片、网页、文档、视频、文件......
- 动态内容:根据当前上下问动态生成的面板内容,内容格式同静态内容,但生成的过程不同
-
-
- 动态内容需要系统定制和处理
- 允许开发者扩张动态内容
-
- 展现形式(Panel ShowInfo):面板信息的展现方式,如上所示为右侧展示,也可以设置其他的展示方式
- 绑定对象(Panel Binder):面板信息总是被关联到特定的实体对象
- 触发事件(Trigger Event):绑定对象发生了什么事件,面板被触发,如被点击、被长按、玩家对话
- 触发条件(Trigger Condition):面板展示触发的前置条件,比如玩家需完成某个任务,面板信息才会弹出
2.4.3 面板数据逻辑
面板数据的逻辑如下所示。
- 客户端产生的事件,经过面板数据拦截器,如果拦截器检测到事件关联的实体 ID 绑定有面板数据,则进入到面板数据的处理过程
- 综合触发事件的主体和当前系统的信息,判定是否满足触发条件
- 如果满足触发条件,则封装面板的展示数据,并下发给客户端
- 客户端接收到面板展示信息后,不需要关注信息的合法性,只需要根据信息,向用户展示面板数据
- 在存在网络延时的情况,客户端判断面板信息关联的计数器和本地计数器的差值,如果小于 10 (可配置)则展示,否则丢弃
- 如果面板数据是有状态的,如已查看、未查看,则状态的改变应该来自用户主动操作,如点击已查看,这需要做为游戏设计的交互范式
2.5 会话数据
2.5.1 业务描述
2.5.1.1 会话
Web3 是一个开放的空间,游戏中的社交会话也秉承这一原则,如下图是一种设计的可能。
- 精灵(对应玩家或者 AI)之间点击即可发起会话
-
- 比如 A 点击 C,那么 A 发起对 C 的会话,此时 A 头上出现会话的标记,C 可以选择点开或者不点开
- 如果 C 点开了,则一次会话开启,且此时的参与方至于 A 和 C
- A 和 C 再会话中,那么 A 和 C 头上会出现会话标记
- 如果 B 对 A 和 C 的会话感兴趣,可以点击 A 和 C 头上的标记,查看他们当前的会话内容,并也可以在会话中发言
- 如果 A 和 C 不希望他们的聊天内容被别人看到,那他们可以加下联系方式,使用其他社交工具沟通
- 会话的内容是可扩展,比如 A 在看视频,那么 B 可以点击消息,看到 A 在看的视频,他们可以一起边看视频边聊天,所看的视频这时属于会话背景
- 用户也可以和 NPC 对话,但 NPC 的对话是固定程序的
- 当一个会话的最后一个参与者关闭对话框,会话结束
2.5.1.2 群体
群体在 Web3 中是一种去中心化的,松散的状态。系统不为团队和群体做单独的技术设计。每个人在不同的环境下期望展示自己的不同特征和属性,因此,群体的设计体现在可快速切换的标签上或者角色的形象上。
- 每个精灵的名称都可以加上若干个前缀和后缀,来表示自己所属的群体,且这些前缀和后缀应当被设计的可一快速切换
- 每个精灵都可以改变自己的形象,适应不同的场合或者展现自己的归属的群体特征
- ID 是精灵在空间的同一性标识,可以选择公开或者公开
2.5.2 会话数据结构
2.5.2.1 数据结构
会话 Dialog:
| 会话的消息 DialogMessage:
事件消息 EventMessage:
可交互的类型的消息 InteractiveMessage:
|
2.5.2.2 数据存储
会话数据以文件块的形式保存在对象存储或者其他类型非结构存储中。大型会话数据被切分成块(DialogBlock)进行存储,第一个块的 ID 就是会话 ID,nextBlock 指向第二个块,依次类推。会话的 Content 由会话的消息组成。
2.5.3 会话数据逻辑
2.6 任务数据
2.6.1 业务描述
任务被抽象成起点和终点,以及起点中间的路径和节点。
- 任务的起点
-
- 如点击某个 NPC,如果符合条件 A,则进入任务路径 A,如果符合条件 B,进入任务路径 B,但都表示用户领取了任务,并开始任务
- 除 NPC 启动外,也可以设置符合条件的精灵自动认领任务,或者进入某个区域自动认领某个任务
- 任务的起点可能有多个,启动方式可能也有多个
- 任务的终点
-
- 当任务进行到最后一个环节,没有后续的需要完成的子任务时,表示任务结束
- 一个任务可能只有一种结束方式,也可能有多种结束方式,对应的任务的结算方式也可能有多种
- 任务的结束方式:
-
-
- 符合特定条件,任务结束
- 主动交付,任务结束
- 主动交付,审核验收后结束
-
- 任务的路径
-
- 任务可以设计成,根据不同的条件有不同的后续子任务,构成不同的任务路径
- 任务也可能是单路径的,甚至只有起点和终点
- 任务的节点:任务节点,即组成任务的一组子任务
- 任务的奖励:任务奖励可以关联在任意的任务节点上进行结算
2.6.2 任务数据结构
在 BountyGO 的任务数据的基础上引入结构化的任务描述。任务实际上可分解成子任务的有向无环图,因此可以采用图的形式作为任务描述(TaskDecription)的结构化的方式。
任务节点:TaskNode
- relateEntityId: 任务节点关联的实体 ID
- triggerEvent: 触发任务节点的事件
- triggerCondition: 触发任务节点的条件,单路径
- nextNode: 下一个任务节点
- triggerConditionMap: 触发任务节点的条件表,Map 的 key 是条件,value 是下一个任务节点
领取任务后,创建任务的事务
- 在 BountyGO 的事务数据的基础上增加 TransactionNode 列表属性,用来描述用户完成该事务的子事务路径
- TransactionNode
-
- taskNode: 关联的 TaskNode 的信息
- matchConditon: 符合的条件
- startTime, endTime, status
条件:Condition,条件是系统各处都会用到的一个数据结构
- leftCondition: 左节点
- op: 运算规则,“与”,“或”
- rightCondition: 右节点
- condition: 条件
-
- 继承自 认定器,扩展认定器数据源,引入内部数据源
- 可独立编写认定器的代码,在内部计算完成后,做为接口对外使用
2.6.3 任务数据逻辑
任务的数据处理逻辑也通过系统的事件拦截器进行。系统定义有任务拦截器。
- 拦截器判定事件关联的实体 (NPC)是否关联有任务
-
- 如果有关联任务,则判定当前的精灵是否满足触发条件
- 如果满足触发条件,则提交到任务处理系统进行处理,任务处理系统通过 BountyGO 的任务引擎(或独立引入)进行数据和状态的管理
- 任务处理系统返回任务信息或者提示信息及其展现形式
- 客户端收到下发的任务信息后,根据展现形式,将信息展示在玩家面前
三 技术方案
四 实现路径
第一期:
对象管理
地图网格管理
游戏数据传输模块
面板数据
任务数据
整体的设计还未完成,如果有人感兴趣,可以一起实现的,欢迎一起组队