文章目录
-
- 一、Y3地图防作弊策略
-
- [1.1 Save/Load作弊](#1.1 Save/Load作弊)
-
- [1.1.2 存档初始值](#1.1.2 存档初始值)
- [1.1.3 存档常见问题](#1.1.3 存档常见问题)
- [1.2 CE类工具修改内存数据](#1.2 CE类工具修改内存数据)
- [1.3 CE类工具函数hook作弊](#1.3 CE类工具函数hook作弊)
- 二、创意实验室上传教程
- 三、性能优化
-
- [3.1 初步定位](#3.1 初步定位)
-
- [3.1.1 GPU排查](#3.1.1 GPU排查)
- [3.1.2 内存排查](#3.1.2 内存排查)
- [3.1.3 CPU排查](#3.1.3 CPU排查)
- [3.2 ECA Profiler分析](#3.2 ECA Profiler分析)
- [3.3 Tips](#3.3 Tips)
- 四、多人联机同步机制
-
- [4.1 同步机制](#4.1 同步机制)
-
- [4.1.1 帧同步(战斗逻辑)](#4.1.1 帧同步(战斗逻辑))
- [4.1.2 帧同步+事件同步(界面逻辑)](#4.1.2 帧同步+事件同步(界面逻辑))
- [4.2 "获取本地玩家"接口示例](#4.2 “获取本地玩家”接口示例)
-
- [4.2.1 在判断逻辑中使用了"获取本地玩家"](#4.2.1 在判断逻辑中使用了“获取本地玩家”)
- [4.2.2 动态创建UI时使用了"获取本地玩家"](#4.2.2 动态创建UI时使用了“获取本地玩家”)
- [4.2.3 使用不带玩家指向的接口获取位置执行逻辑](#4.2.3 使用不带玩家指向的接口获取位置执行逻辑)
- [4.2.4 使用不同步的随机数接口](#4.2.4 使用不同步的随机数接口)
- [4.2.5 "获取本地玩家"接口的推荐用法](#4.2.5 “获取本地玩家”接口的推荐用法)
- 五、地形
-
- [5.1 地形迁移](#5.1 地形迁移)
- [5.2 地形搭建小技巧](#5.2 地形搭建小技巧)
-
- [5.2.1 引言](#5.2.1 引言)
- [5.2.2 设计概念图](#5.2.2 设计概念图)
- [5.2.2 选择风格](#5.2.2 选择风格)
- [5.2.3 相机设置](#5.2.3 相机设置)
- [5.2.4 地形制作(排列组合)](#5.2.4 地形制作(排列组合))
- [5.2.5 丰富场景](#5.2.5 丰富场景)
- [5.2.6 氛围](#5.2.6 氛围)
- [5.3 小地图制作指南](#5.3 小地图制作指南)
-
- [5.3.1 材料准备](#5.3.1 材料准备)
- [5.3.2 制作步骤](#5.3.2 制作步骤)
- 六、使用对象池(练功房)
-
- [6.1 为何使用对象池](#6.1 为何使用对象池)
- [6.2 对象池的使用(以插件商城练-功房插件为例)](#6.2 对象池的使用(以插件商城练-功房插件为例))
- 七、存档使用教程
-
- [7.1 整数存档(金币)](#7.1 整数存档(金币))
- [7.2 实时存档-表格](#7.2 实时存档-表格)
- [7.2 全局存档](#7.2 全局存档)
- [7.3 排行榜存档](#7.3 排行榜存档)
- 八、版本管理(待补)
- [九、 lua编程(待补)](#九、 lua编程(待补))
一、Y3地图防作弊策略
1.1 Save/Load作弊
Save/Load作弊原理与单机游戏SL大法一致,利用游戏存档的间隔,达到在无消耗游戏道具或其它机会成本的情况下获取最优解的目的,常见的场景如随机词条强化、抽卡,具体手法是在获取到游戏结果的一瞬间进行断网或断电,使得道具消耗无法上传。
在Y3编辑器中,针对该类场景研发了实时存档策略。最新版本中,表格型存档不允许覆盖,所以也只能是实时存档,且表格型存档槽将会默认加密。
1.1.2 存档初始值
如果进入游戏的玩家没有这个槽位,那么这个槽位中的数据将会被设置为初始值。存档的初始值主要的意义在于避免解析报错,以及发放一些初始道具。修改初始值时并不会影响到已有存档的玩家,也就是说,并不能通过修改初始值然后发布地图来达到补发、增发初始道具或类似的功能场景。
常见的错误使用方式为:在使用前将所有的未来数据塞到存档槽中,后续不再增加行列,仅修改内容对应的键值。比如在存档中直接使用武器图鉴功能的配置表,每个图鉴可能包含id,图片,标题,说明文本,剪影图片,是否解锁等信息,在解锁图鉴后,将"是否解锁"的属性值改为是。这种使用方式会导致两个严重后果:
- 存储空间被大量浪费。
- 由于存档的信息是加密的,在访问对应的功能时,产生较大的性能压力,玩家卡顿。
上述案例 正确的使用方式是初始值配为空表,或带有初始武器的表,在解锁图鉴后,将解锁的对应id插入存档槽存储。在界面显示时,遍历预设的图鉴表,根据存档槽中已有的id判断是否显示以及显示什么内容。武器强化等场景同理,如何拓展空行空列可见使用方式。
1.1.3 存档常见问题
在编写业务逻辑时,不建议频繁访问存档槽内部数据 ,因为访问存档槽内部逻辑涉及到解密及解析过程,会增加无谓的性能损耗。正确的方式是在初始化时,将存档槽内部的数据加载到全局变量中,后续访问变量进行使用。后续对此变量进行修改,得到修改过的表,可通过设置存档语句,更新对应存档槽内容:
对于非实时表格,将表格转为合规的字符串是在设置时完成,而非上传时完成,意味着频繁调用将会产生极大的性能压力。若需要多次修改表格中的内容,不需要修改一次就设置一次,尽量等所有预期的表格操作完成后执行一次,并尽量控制执行的频次。
设置语句执行的时间与表格中的内容量正相关,首先建议尽可能优化数据内容(一般可采用常量、大量程序可批量处理的默认值从存档中转到预设表格中、程序增加空值保护等手段),其次尽量将表格中的功能模块粒度切细,以压缩单一表格体积,同时避免在一帧中设置较多主动型表格槽位的值(通过计时器延时处理等方式)。
在执行过设置操作后,此时对应的数据仍然处于玩家本地,需要调用上传存档语句进行更新。除主动型存档表外,全局存档的上传也并非实时的,修改过后也需要调用上传存档语句。若数据量过大,存档上传行为可能发生卡顿,建议周期或在特点业务节点结合界面告知进行上传,尽可能避免在战斗过程中进行上传。若有类似的需求,建议换用实时型存档表。
1.2 CE类工具修改内存数据
在游戏运行时通过锚定某个数值(如主角攻击力),使这个数值连续发生变化,然后通过CE(Cheat Engine)类工具筛出变化前后与预期一致的内存地址,从而确定这个数值在内存中实际的地址。最后直接绕过游戏逻辑修改这个地址的数值,游戏中的数值就会同时产生变化。其修改方式有两种:
- 直接修改内存数据,比如战斗数值,达到具备惊人的属性,秒天秒地快速通关
- 修改参与存档运算的内存数据,曲线达到修改存档的目的。
针对第一种方式,由于修改绕过了游戏逻辑,所以在修改的瞬间,联机情况下游戏会直接异步。存档的基本原则是每个人只对自己的数据正确性负责,所以作弊玩家没法通过小号带大号的手段完成刷分,需要刷分时一定要大号亲自开挂。针对这个场景,Y3增加了内存迷惑机制,现在生效在加密表、存档槽、单位属性三个维度中,这些数据都存在两份,一份真实数据(非明文),一份钓鱼数据(明文)。在钓鱼数据被修改后,将会立即触发发现作弊事件,作者可以在发现作弊事件中做一些惩罚处理。
针对能突破并能实际修改到真实内存作弊数值的情况,建议使用lua接口 gameapi.save.anticheat_data 在不同节点埋点输出一些核心数值到反作弊日志进行后续判断。如通关时输入战斗力,游戏局时长,其他便捷通关的可作弊数值。
--上传反作弊数值统计
---@param role py.Role # 玩家
---@param args py.List # 自定义参数
---@return string # 玩家全量昵称
function GameAPI.save_anticheat_data(role, args) end
针对第二种场景,需要地图作者在存档逻辑的编写过程中使用增量设置的方式修改存档,并且避免增量的参数使用本地数据作为运算因式。在下图案例中,英雄模板(1)的物理攻击力、存档槽中战斗力的数值均是会在运行时发生变更的本地数据,存在较大被CE修改的风险,这种写法不建议使用。
优化后的逻辑:
- 配置表中的值在初始化后保持不变,较难被CE捕获
- 500以及局部变量a在当前事件执行完毕之后生命周期就会结束,不存在被CE修改的风险
针对核心资源,特别是货币类存档,在使用常规table存档的情况下,在独立的int存档槽位进行存档,并使用平台提供的防刷限制每局、每次、每单位时间变更上限、上传频率等。可有效防止存档被直接修改到夸张数值。
- 通过一个只增存档,来记录当前资源的消耗量,结合当前存档余量,来获取总量判断是否存在作弊
- 通过一个只增存档,来记录当前资源在合理逻辑下(地图、商城、赠送)的获得总量,若该值小于存档当前值,可基本认定为作弊
- 通过一个存档来记录每次用户指定资源的最大值,并记录最大值,将该存档设置内部排行榜,快速排查顶部异常数据
- orpg类游戏道具较多,不方便用平台防刷存档,也可以在地图逻辑中写死上限,虽然也存在被修改,但是由于定值不变化,较难被定位。
1.3 CE类工具函数hook作弊
捕获到某些数值的内存地址后,分析访问这些数值的修改堆栈来定位到引擎函数的地址,然后修改这个函数中的运算逻辑,从而达到在游戏每次正常修改这个属性的时候得到一个被偏移过的结果的目的。与内存修改相同,在修改发生时,联机情况下会发生异步。
上文提到的钓鱼数据由于是基于真实数据生成的,所以在查找堆栈的过程中,外挂开发者可能通过钓鱼数据找到真实数据,也能够通过钓鱼数据找到"判断数据是否被修改的函数",可以理解为鱼竿,然后修改鱼竿的材质,变成一个钛合金鱼竿,使得鱼竿没法正常判断是否中鱼。如果再加一个观测鱼竿材质的电子眼,仍然可能被找到,并且断电。所以这种方法可能能够通过骗过ECA语句的运算以及钓鱼数据的验算,防御较为困难,已知相对有效的防御手段是增加验证数据源。
下图案例中,在设置玩家属性前,增加一个全局变量用于验算数据真实数据和校验值是否相符,不相符则视为作弊。
二、创意实验室上传教程
通过Y3编辑器可以快捷的将作品上传到创意实验室,创意实验室审核要求较低,但是不能进行商业道具的售卖,适合一些玩法上进行创新尝试的作品进行验证。
-
地图上传 :Y3编辑器内点击发布-【上传地图】,选择【上传至创意实验室】
-
确认上传:因为创意实验室的游戏无法出售商城道具,主要展示创意和玩法,所以会弹出确认弹窗,避免你误操作。
-
选择同步上传地图自定义文件。上面那个选项仅用于自测。
-
配置游戏信息,如版本号,地图名,更新日志,说明,封面图等
-
等待审核,【商城】的功能将会被隐藏,无法使用,审核通过后就会上架创意实验室了。
三、性能优化
Y3编辑器提供了多种工具用于定位地图中存在的各种性能风险,本文将提供一些有价值的优化建议。
3.1 初步定位
3.1.1 GPU排查
打开任务管理器查看显卡使用率。若显卡使用率已满:
- 使用GTX-960以下显卡,建议调整至低画质。
- 使用GTX-960及以上显卡,需检查以下方面:
- 阴影范围:过大的阴影范围会增加GPU负担,可适当缩小。
- 场景内点光源和聚光灯的数量/范围:过多或范围过大的光源会消耗大量GPU资源,需合理控制。
- 场景特效/模型数量和规格 :重点关注模型面数、纹理分辨率、shader复杂度、骨骼数量等,尤其是自行导入的模型,过多或过于复杂的模型会拖累GPU性能。
3.1.2 内存排查
内存使用率大于80%可能导致系统内存不足,不断触发缺页中断,从而使游戏卡顿,尤其是独立进程运行时。
- 建议配备16G以上内存,本地多开建议数量不超过2个。
- 内存只有8G时,建议仅使用"编辑器内运行",避免打开过于复杂的地图。
3.1.3 CPU排查
打开Y3编辑器,右上角的UI会实时显示以下几个统计值(若未出现实时统计,可在编辑器的"编辑-通用设置"中打开。):
-
帧数:当前游戏的帧数,帧数小于45时显示黄色,小于30时显示红色,可能出现卡顿或者低帧率。持续小于30帧或出现大幅波动会显著影响游戏体验,操作敏感的作品需保持60帧或更高。
-
Drawcall(DC) :DrawCall是引擎调用图形API【DirectX、OpenGL等】,命令GPU进行渲染的操作。DC大于1000时显示黄色,大于3500时显示红色。
提交Drawcall前CPU需要设置渲染状态,加载数据等;如果Drawcall数量过高,CPU将需要大量的时间提交渲染指令给GPU,导致CPU占用过高,出现性能瓶颈。解决方案:
-
检查场景中是否有点光源、方向光勾选了投射阴影且范围过大,此类光源的阴影计算需大量Drawcall,建议考虑关闭其阴影。
-
默认的单层阴影范围是否偏大,尤其当游戏摄像机视角接近水平时,自适应阴影可能存在范围过大的问题,大范围shadowmap的计算会导致较高的drawcall和面数。可在美术效果中将阴影调整为级联阴影,并控制阴影范围(使用游戏视角,逐步调低阴影范围,直到游戏摄像机视角边缘的阴影正好消失,消失前的阴影距离即可以作为阈值)。
-
视野内大量特效同屏或部分特效过于复杂,需谨慎使用,可结合"特效数量"参数分析。
-
相机裁剪距离过高且角度过高,导致大量被遮挡的物体参与绘制,应降低相机裁剪距离,设置为50-100。
-
检查"单位""可破坏物""物品""投射物"数量是否偏大,以及视野内以上物品的数量与UI统计值是否匹配。
-
场景中是否存在大量零散的装饰物,如使用大量零散实体手动拼接的地图元素。部分装饰物可考虑设置不产生阴影(如地板、地基等),自行导入的模型可考虑预先合并,减少Drawcall数量。
- 引擎调用图形API:在游戏开发中,引擎是连接游戏逻辑和图形渲染的关键部分。图形API(如DirectX、OpenGL等)是操作系统或专门的图形库提供的一套用于与图形硬件(显卡)进行交互的接口。当引擎需要将游戏中的场景、角色、特效等图形元素渲染到屏幕上时,它会通过调用这些图形API来实现。
- 命令GPU进行渲染的操作:GPU(图形处理单元)是专门用于图形渲染的硬件。DrawCall就是引擎通过图形API向GPU发出的一个渲染指令,告诉GPU要渲染哪些图形元素以及如何渲染。比如,渲染一个角色模型、一个场景中的建筑物、一束特效光等,每一个这样的渲染任务都可能对应一个DrawCall。提交Drawcall前CPU有两项工作:
- 设置渲染状态:在GPU开始渲染之前,CPU需要先设置好一系列的渲染状态。这些状态包括但不限于:渲染模式、纹理状态、着色器状态、混合模式等
- 加载数据:CPU还需要将渲染所需的图形数据加载到GPU可以访问的地方
-
-
面数:场景模型和粒子的总面数,指被CPU提交至GPU的模型面数,不含被硬件剔除的部分。该值过高,配置较低的GPU可能会存在瓶颈。解决方案:
- 场景中是否有点光源、方向光勾选了投射阴影且范围过大,此类光源的阴影计算需对范围内所有模型计算一遍shadowmap,提高drawcall和显卡渲染压力,建议考虑关闭其阴影。
- 默认的单层阴影范围是否偏大,处理方法同Drawcall优化中的阴影范围调整。
- 相机裁剪距离过高且角度过高,导致大量被遮挡的物体参与被提交渲染,应降低相机裁剪距离,设置为50-100。
- 检查"单位""可破坏物""道具""魔法效果""特效"数量是否偏大,以及视野内以上物品的数量与UI统计值是否匹配。
- 是否有模型面数过高,尤其是自行导入的模型。
-
单位数量,可破坏物数量,道具数量,魔法效果数量 :该值表示当前引擎每一帧正在计算的单位数量。若一个单位身上挂接了其他单位,也需要被计入。若这些值非常高或与看到的数量存在显著差异,需检查相机的剔除距离,以及场景中是否把单位摆放在了不应存在的地方(可使用filter关闭部分物体寻找被遮挡的单位)。
导致游戏掉帧的情况很多,主要关注Drawcall或面数是否有突增(变成黄色/红色),若存在,需考虑场景内此时的模型、投射物、魔法效果是否存在数量过高的情况,并对其进行简化。初步排除以上情况后,可以进一步使用分析工具分析游戏ECA部分的性能。
3.2 ECA Profiler分析
Y3的游戏逻辑,单位逻辑以及UI均由ECA控制,过于复杂的ECA逻辑可能导致游戏出现卡顿和低帧率等问题。在Y3 Editor 中运行内置的ECA分析器(ECA Profiler),可快速定位ECA相关的性能瓶颈。
-
如果要求游戏帧数为60帧, ECA部分的总耗时应当<16ms;
-
如果要求游戏帧数为30帧, ECA部分的总耗时应当<33ms;
工具使用流程如下:
具体操作详见 演示视频。各部分功能如下:
-
运行/暂停控制区域:用于在调试中控制游戏的暂停和继续运行
-
.CPU帧耗时展示区域:每个竖条表示一帧的CPU部分函数耗时【主要是ECA部分】
-
ECA耗时展示区域:以火焰图(Flame Graph)的形式展示该帧触发器的调用关系和耗时【精确到每个触发器】
- 纵向:
- 每级触发器及其运行中调用的其他触发器/动作,每一次子调用对应一个纵向的色块;
- 如果调用太深或者出现嵌套,建议考虑简化调用逻辑;
- 横向
- 每个色块对应本帧的每一次触发器调用的名称及其运行耗时【单位/ms】;
- 如果游戏需要60帧 横向的总耗时应当<16ms;如果游戏需要30帧 横向的总耗时应当<33ms;
- 如果一帧内次数太多,可以考虑简化逻辑或者分帧处理;
- 点击相应的色块可以打开触发器编辑界面
- 【点击后出现】触发器界面:点击后会打开,并跳转到对应行,例如点击上面的【创建单位】就跳转到【创建单位】的触发器的第8行
3.3 Tips
-
谨慎控制单位的警戒距离,不要拉的太大,通常800左右比较合适,刷怪后立即攻击可以用ECA实现
警戒距离过大带来的问题:
- 性能开销:单位需要不断地检测其警戒距离范围内的所有对象,判断是否有敌人进入。如果警戒距离过大,单位需要检测的区域范围就非常广,这就需要更多的计算资源来完成这个检测过程。特别是在单位数量较多的场景中,每个单位都在检测大范围的区域,会导致CPU负载大幅增加,影响游戏的性能,可能出现卡顿、帧率下降等问题。
- 逻辑复杂度增加:过大的警戒距离可能会使单位之间的交互逻辑变得复杂。例如,多个单位的警戒范围相互重叠,当有敌人进入重叠区域时,可能会引发多个单位同时做出反应,这就需要更复杂的逻辑来协调各单位的行为,避免出现混乱的情况,如多个单位同时攻击同一个目标导致的伤害计算复杂、单位移动路径冲突等。
-
谨慎使用【任意单位-造成伤害】这类触发,尝试添加一些其他限制条件,避免伤害调伤害调伤害的嵌套风险
-
物品栏可以堆叠,没必要做几千个
-
加载页等UI页摄像机放在地图偏远一点的地方,降低不必要的负载
假设你正在开发一款3D游戏,游戏地图非常大,包含丰富的场景元素。加载页的UI设计得很精美,有自己的背景和动画效果。如果加载页的摄像机放置在地图的中心区域,那么在加载游戏时,摄像机可能会"看到"并尝试渲染周围复杂的场景元素,如附近的建筑、角色模型等,即使这些元素与加载页的显示并无直接关系。这会增加渲染负担,可能导致加载时间变长或出现卡顿。而将加载页摄像机放在地图的一个偏远角落,它就只会关注和渲染加载页本身的UI元素,不会被周围的复杂场景干扰,从而降低不必要的负载,使加载过程更加流畅。
-
AOE技能建议谨慎控制伤害的范围和数量上限
-
如果一个特效可能存续数量很多,不要使用过于复杂的粒子
-
场景中伤害结算展示功能等与ECA相关的UI,需要控制更新频率
-
如果你需要给一大片单位加buff,请直接加给他们而不是给某个光环持有者套个范围2000+的光环
- 资源消耗大:范围过大的光环需要不断地检测其范围内的所有单位,判断哪些单位需要添加buff,哪些单位已经拥有buff需要更新buff状态等。这个检测过程会消耗大量的CPU资源,尤其是当游戏场景中单位数量较多时,会导致性能下降。
- 难以精细管理:由于是通过光环这种间接方式添加buff,当需要对特定单位的buff进行特殊处理时,操作起来比较麻烦。比如,想要提前移除某个单位的buff,就需要先找到对应的光环,再通过光环来对该单位的buff进行操作,而不是直接对单位的buff进行管理。
-
小兵尽量使用简易普攻进行制作,在伤害事件里面挂buff,而不是所有技能用ECA实现
假设你正在开发一款塔防游戏,游戏中有大量不同种类的小兵从起点向终点移动,玩家需要布置防御塔来阻止它们。对于小兵的设计:
- 使用简易普攻:小兵的攻击动作就是一个简单的挥刀或射击动作,没有复杂的连招或特殊效果。这样设计使得每个小兵的攻击行为计算简单,即使同时有成百上千个小兵在攻击防御塔,也不会对游戏性能造成太大压力。
- 在伤害事件里挂buff:当小兵攻击防御塔并造成伤害时,给防御塔附加一个"防御下降"的buff,持续一段时间。这个buff的实现不需要复杂的ECA逻辑,只是在伤害计算的代码部分增加几行用于应用buff的代码。这样既丰富了小兵与防御塔之间的互动,又不会因为复杂的技能逻辑而影响游戏的流畅性。如果采用让每个小兵都拥有多个复杂技能并通过ECA来实现,当大量小兵同时触发技能时,可能会导致游戏卡顿,影响玩家的游戏体验。
四、多人联机同步机制
4.1 同步机制
4.1.1 帧同步(战斗逻辑)
在Y3引擎的多人联机战斗逻辑中,采用帧同步机制。每个玩家的客户端都独立运行一份相同的游戏代码逻辑,但它们之间只同步操作行为数据。也就是说,每个客户端根据接收到的所有玩家的操作行为数据,运行本地的代码和ECA(逻辑,来演算整个游戏战局的数据状态。
- 操作行为同步 :只同步玩家的操作行为,如移动、攻击、施放技能等指令,而不同步其他数据。这样可以减少网络传输的数据量,提高同步效率。其它数据包括:
- 单位数据:包括单位状态(单位的当前生命值、魔法值、能量值等);单位属性、buff和debuff。这些数据通常由服务器(负责结算)计算并同步给客户端(负责表现),而不是通过客户端之间的帧同步来实现
- 物理数据:单位的物理位置、速度、加速度等。这些数据通常由客户端的物理引擎计算,但为了确保一致性,服务器会进行校验和同步。
- 事件数据:包括UI点击输入等事件,UI控件的节点对象、自定义属性等,技能的动画、特效等(由客户端根据同步的操作行为自行生成)
- 渲染数据:如模型的纹理、动画帧等。这些数据通常由客户端根据同步的操作行为自行渲染
- 代码逻辑一致性:所有客户端执行的真实代码必须一致
- "获取本地玩家"ECA:这条ECA会根据本地客户端的玩家进行映射。例如,在玩家1的客户端中,该逻辑会替换成玩家1,在玩家2的客户端中会替换成玩家2,在不同客户端执行时对应着不同的真实代码逻辑。开发者在设计ECA逻辑时,要特别注意这种映射关系,确保所有客户端在执行相关逻辑时能够正确地映射到本地玩家,从而保持同步。
4.1.2 帧同步+事件同步(界面逻辑)
Y3在处理界面逻辑时,为了节省性能开销,将界面数据分为 渲染数据 和 逻辑数据。
- 渲染数据:是针对本地玩家用于界面绘制的数据,本地客户端只为当前用户维护一份,不会维护其他玩家的数据。例如,静态的UI图片、按钮的外观样式等都属于渲染数据。这些数据只与本地玩家的界面显示有关,其他玩家的客户端不需要同步这些渲染数据。
- 逻辑数据:是本地客户端为所有玩家维护的一份数据,用于游戏逻辑的处理。常见的逻辑数据包括控件节点对象、控件的自定义属性、控件的显示状态、文本框中的文字、输入框中的文字等。这些数据在不同客户端之间可以进行帧同步操作,以保证界面逻辑的一致性。
TIPS:如何区分渲染数据和逻辑数据,可以看设置/查询该数据的ECA接口是否需要传递玩家参数,如果不需要传递,则必然是渲染数据。
渲染数据的局限性:由于渲染数据只针对本地玩家,其他客户端上没有维护触发事件玩家的界面状态。例如,当玩家1点击某个按钮时,其他玩家的客户端上并没有绘制玩家1的这个按钮界面状态,因此无法通过同步玩家1的点击行为位置,让其他玩家的客户端演算出相同的操作结果。
UI事件同步:为了解决上述问题,Y3采用了UI事件同步机制。客户端会根据本地玩家的操作指令响应,生成对应的UI事件,并将该UI事件同步给其他玩家。这样,即使其他玩家的客户端上没有演算触发事件玩家的UI状态,也可以通过接收到的UI事件同步演算后续的操作逻辑。
举例说明:假设有一个多人联机的策略游戏,游戏中有一个UI界面,玩家可以通过点击按钮来发送资源给其他玩家。玩家1点击"发送资源"按钮,客户端生成一个UI事件,该事件包含按钮的唯一ID和发送资源的数量。这个UI事件被同步给其他玩家的客户端。
玩家2的客户端接收到这个UI事件后,根据事件中的按钮ID和资源数量,查找本地维护的逻辑数据(如玩家1的资源数量、玩家2的资源接收状态等),并执行相应的逻辑处理(如减少玩家1的资源,增加玩家2的资源)。如果玩家2的客户端上没有维护玩家1的资源数量这一逻辑数据,那么当接收到UI事件时,就无法正确地处理这个事件,导致资源发送失败。
确保逻辑数据的完整性:开发者在创建UI时,需要确保所有客户端都能正确地维护和同步逻辑数据。例如,有些开发者在动态创建UI时,会使用"获取本地玩家",仅为自己创建UI,导致本地数据中并没有其他玩家的对应UI的逻辑数据,后续逻辑响应就会出问题。
4.2 "获取本地玩家"接口示例
4.2.1 在判断逻辑中使用了"获取本地玩家"
该开发者的想法肯定是:我只处理当前玩家的数据呀,谁点召唤我处理谁就行了呀。但是,根据之前我们讨论的同步机制来看,每个客户端都需要完整的演算整个游戏的数据,不能只算本地玩家的数据(在条件判断中,使用了本地玩家逻辑)。所以,以上逻辑应该修改为:
4.2.2 动态创建UI时使用了"获取本地玩家"
上图中,开发者想为玩家构建一个包裹界面,根据玩家的背包数据动态创建了包裹格,并用自定义属性存了物品数据。在整个逻辑中唯一使用了"获取本地玩家"接口的,只有动态创建包裹格语句。
开发者的思路是只给本地玩家创建界面就行了,其他玩家的不用处理。但在其他玩家点击包裹格时,会发现对应玩家名下并没有被创建那个包裹格,后续代码也无以为继了。正确的做法是为所有玩家创建包裹格,才能保证后续逻辑正常运行:
4.2.3 使用不带玩家指向的接口获取位置执行逻辑
各客户端之间是不同分辨率可能不一样,所以UI渲染数据也不一样。无论是【获得滑动条当前值】还是【获取本地控件坐标】,都没有携带目标玩家参数,说明取的是当前客户端本地的属性,所以这些逻辑必然会导致不同步。
在代码中,尽量避免使用界面属性及其他显示类属性作为游戏逻辑的判断,取而代之可以在数据层面维护该数据进行判断。
4.2.4 使用不同步的随机数接口
在Y3引擎中,系统可以保证在同一随机种子下,不同客户端计算出的同一次随机值相同。上图中,开发者想只在触发事件的玩家客户端上,以50%的几率播放特效,看似没有问题,但是触发播放特效的玩家客户端,多计算了一次随机值。那么在后续的逻辑中,他获取的随机值就与其他客户端不同了。以上逻辑应该修改为:
这保证每个客户端进行了同样次数的随机运算,只是在处理显示的逻辑的时候,才进行差异处理。
4.2.5 "获取本地玩家"接口的推荐用法
提供"获取本地玩家"接口的目的,是让开发者可以便捷高效地处理本地的专属显示逻辑。比如操作提示、开关界面等。以下列出了一些相对安全,不容易出现不同步问题的用法:
- 修改界面控件的显示状态、位置、文本或图片内容
- 播放声音
- 创建和修改特效
- 设置镜头位置等属性
- 打印调试数据
**凡是需要其他玩家在逻辑中知晓的数据行为,不得使用"获取本地玩家"ECA执行。**建议仅在UI显示逻辑中使用"获取本地玩家"ECA,来控制界面和控件的显示状态。
五、地形
5.1 地形迁移
在Y3编辑器中,打开地图所在的文件夹,按照下列列表复制并粘贴.json
和.data
文件,即可完成地形和摆件的转移。
文件名 | 内容 | 文件名 | 内容 |
---|---|---|---|
decal.json | 地形依赖 | projectile.json | 投射物 |
foliage.json | 地形依赖 | item.json | 物品 |
grid.data | 地形依赖 | itemtype.json | 物品 |
terrain.json | 地形依赖 | decorationdata.data | 装饰物 |
texture.json | 地形依赖 | resourceobjectdata.data | 装饰物 |
texturefoliage.json | 地形依赖 | destructible.json | 可破坏物 |
ogicres.json | 区域点等 | unit.json | 单位 |
5.2 地形搭建小技巧
5.2.1 引言
我一直是 3D 开放世界游戏的忠实粉丝,例如 Grand Theft Auto V、Red Dead Redemption 2、Elden Ring、The Legend of Zelda Breath of the Wild 和 Genshin Imapct。里面有太多令人惊叹的场景,我也想有一天能做出一部像他们一样的好作品。
在查看编辑器中的资源时,我发现了一个非常有趣的 Boss 模型,它看起来像是某个失落文明的古老守卫。我很喜欢这样的故事,并决定为它创造一个环境。
5.2.2 设计概念图
在动手制作地形之前,可以花一些时间写下关于背景设定的关键词,并在网上搜寻一些参考图片。这个过程可以梳理你的想法,让脑海中的画面更加具体。
既然已经收集到了参考图片,那现在可以画一下概念构图了,将你想要的场景用笔画出来,我举个例子,我想在暮色中的山顶上制作一个山洞入口,守卫站在门前。我还需要一些更多的建筑物、柱子和塔来创造一个文明的废墟。
5.2.2 选择风格
编辑器中的所有模型都分为两种不同的风格:卡通和逼真。为了确保场景的一致风格,我会选择逼真的模型来构建环境,比如岩石。
通常情况下,风格化的模型只在纹理上保留一些核心细节,因为它们在总多边形数上有限制。但逼真的模型可以在纹理上具有更多的大、中、小和微观细节。以下是一些好的例子,可以帮助您理解它们之间的差异:
5.2.3 相机设置
在编辑地形之前的最后一步是相机设置。我希望我能看到我的老板站在山洞前,废墟塔应该设在山上。设置合适自己游戏的角度,通过更改"时间"设置,我可以控制灯光角度来移动阴影方向。
5.2.4 地形制作(排列组合)
在明确了以上需求后,现在我可以开始创建一些大的地图信息了。通常我会使用地形工具来雕刻景观,但是通过旋转相同的岩石的角度可以获得不同的山脉趋势。理论上,我可以只使用一块石头来覆盖整个场景。
有时我无法直接从编辑器中找到最合适的模型,因此我需要排列和组合现有的模型,以创建我想要的更好的结果。
例如,我将上述不同的破墙部件以特定的方式组合在一起,以达到废墟的形状,或者我可以将它们堆叠起来,制作一个破损的塔。不要限制自己的想法,勇敢尝试新的组合,这可能会带给你一些意想不到的结果。
5.2.5 丰富场景
在完成建造山石之后,我仍然需要其他中小细节来丰富场景。比如使用植被工具在地图上绘制了一些草,并希望草的颜色能够与当前的地理特征相匹配,然后在路径周围放置了一些石头,以营造更多的山地氛围。
上面已经完成了大部分自然元素的构建。我将在这个环境中添加废墟。为了让场景看起来更古老和破损,我倾斜了大部分建筑的角度。同时,我还使用枯死的树木和残破的零件,使图片更具叙事性。
5.2.6 氛围
在艺术风格的功能中,我尝试并切换了不同的滤镜,调整了界面右上角的时间,并创造了一个黑暗的氛围。如果你感兴趣,可以尝试使用灯光和雾效果,使图片更加统一。
5.3 小地图制作指南
游戏中的小地图是一种常见的界面元素,它可以给玩家提供方向和定位以及视觉引导和战术信息,展示环境和地形,增加游戏的沉浸感。本教程将介绍如何在Y3编辑器中制作小地图。成品展示:
5.3.1 材料准备
在开始制作小地图之前,你需要准备如下素材:
- 地图图片:手里没有对应文件的,可以直接在地编中选择 查看-完整视角,然后截图。
- 角色箭头图片与视野效果图片
5.3.2 制作步骤
- 计算场景位置与图片位置的映射关系
-
在地图上取两个点 记录它的坐标(a1,b1)(a2,b2)。两个位置离得越远越好,最好是地图的左上角和右上角。
-
在ui界面中获取这两个点的坐标(A1,B1)(A2,B2)
-
具体计算公式为:
python(A1-A2)/(a1-a2)*场景坐标的X+(A1*a2-a1*A2)/(a2-a1)= 大地图坐标的X (b1-b2)/(B1-B2)*场景坐标的Y+(b2*B1-b1*B2)/(b2-b1)= 大地图坐标的Y
-
- 实时更新
在完成了第一步之后已经可以正常将玩家位置显示在大地图上,但我们目标效果中小地图和代表玩家位置的箭头其实是固定的,所以这边需要再对公式进行一次计算(箭头不动,设置地图图片位置)-
创建一个空节点,添加一个圆形的遮罩后打开裁剪开关
-
计算公式:取大地图位置(a1,b1)和(a2,b2),通过移动地图图片将这些点挪到圆形中央后,记录地图图片位置(A1,B1)和(A2,B2)。计算公式如下:
python大地图坐标X*(A2-A1)/(a2-a1)+(A1-(A2-A1)/(a2-a1))=地图图片坐标X 大地图坐标Y*(B2-B1)/(b2-b1)+(B1-(B2-B1)/(b2-b1))=地图图片坐标Y
-
- 将箭头面向设置成与角色一致
- 每帧去刷新小地图信息即可(ui位置信息不需要同步,整体消耗不大)
六、使用对象池(练功房)
参考《使用对象池》
6.1 为何使用对象池
对象池是一种用于存储和重复利用对象的数据结构。在程序运行过程中,预先创建一定数量的对象并将其保存在池中,当需要使用对象时,从池中获取并使用,使用完毕后再将对象放回池中,以便下次重复利用。
为什么使用对象池:
- 提高性能:对象池可以避免频繁的对象创建和销毁操作,从而减少了系统资源的消耗,提高了程序的性能和效率。重复利用对象可以减少内存分配和垃圾回收的开销。
- 减少对象创建时间:对象的创建通常是比较耗时的操作,利用对象池可以预先创建一定数量的对象,并在需要时直接取出使用,避免了对象创建时的延迟。
- 提供对象状态管理:通过初始化和释放对象的操作,可以对对象进行状态的管理和控制。在获取对象时,可以选择性地对对象进行重置操作,使其恢复到初始状态,以满足不同的使用需求。
- 控制对象数量:通过设置对象池的最大容量,可以限制对象的数量,避免资源的过度消耗。当达到最大容量时,可以选择等待对象池中有可用对象后再获取,或者根据需求动态扩展对象池的大小。
6.2 对象池的使用(以插件商城练-功房插件为例)
- 对象池初始化:在对象池创建时,可以选择性地初始化一定数量的对象,并将其加入到对象池中,然后启动练功房检测功能。
- 获取对象(Get):从对象池中获取一个可用对象。如果对象池中没有可用对象会需求创建新的对象
如果练功房怪物对象池中的单位数量是为0,则创建一个新的怪物单位并添加到练功房怪物组;否则,从对象池中移除最后一个单位,复活它,并添加到练功房怪物组。 - 释放对象(Release):使用完毕后,将对象释放回对象池中,以便下次重复利用。
- 清空对象池(Clear):清空对象池中的所有对象,释放占用的资源。
注意此处根据逻辑没有做对象删除,可以增加一条删除单位实例的ECA
- 扩展对象池(Expand):根据需求动态扩展对象池的大小,可以预先创建更多的对象并加入到对象池中。此处可以根据需求把扩展对象池的方法抽象为一个函数
- 监控对象池(Monitor):监控对象池的状态和使用情况,例如可用对象数量、使用中的对象数量等。
监测怪物死亡事件,并从练功房怪物组中移除死亡单位。
七、存档使用教程
参考《存档使用教程》
在细节-存档设置中可新建存档槽,比如创建一个表格型存档,设置初始值为一个空的一维表kv1。需要注意,初始数据表格一旦设定,便不可更换,比如开始设置的是【kv1】,后面想修改为【空】是不行的,如需修改,则需要在ECA中遍历【kv1】设置其值为空的方式才可以。
数据类型为基本数据类型(整数,浮点数,字符串等)时,默认为实时型。之前的版本在,只有数据类型为表格且取消勾选【禁用覆盖操作】时,此存档为主动型,需要在ECA中手动上传存档。现在【禁用覆盖操作】无法取消勾选,所以都是实时型存档了。下图是客户端和服务器交换数据的流程图,后面的ECA大家可以结合该图进行理解。
7.1 整数存档(金币)
在游戏中,玩家击杀怪物后获得了50金币,下次打开游戏时,金币UI显示的数量还是50,这个效果可以用玩家存档来实现。
- 添加存档:在存档槽新增一个名为金币数量的整数型存档
- 初始化金币数量:玩家加入游戏后,设置金币UI数量为玩家金币存档数
- 监听金币变化:当玩家属性变化时,如果是gold属性变化,将玩家金币存档数设置为此时的gold值,变更同步更新到金币UI中。
7.2 实时存档-表格
对于实时型存档,直接修改本地存档中的数据即可。注意,这里使用的ECA应该是修改存档,而非设置表格。
重置表格型存档时,将其设为空表是不行的,要么将要修改的键值设为0,要么删除此键值:
7.2 全局存档
全局存档是一种特殊的存档方式,它跟整数存档很相似,但它的数据是绑定在地图上的,而非玩家上,因此游戏一旦发布到平台,也就是上传服务器,那么其数据也就永远跟随地图存在,除非从平台下架才会失去存储的数据。在Y3中,全局存档一般用于制作【世界等级】功能。
7.3 排行榜存档
排行榜存档是一种特殊的整数存档形式,跟全局存档类似,也需要发布地图之后才可实际获取数据。
该存档类型会把进入过地图的所有玩家的排行榜数据根据设置的排序规则进行排序,然后创作者可以通过对应的ECA获取到排序后的名次。例如:
排行榜数据在游戏启动后不会再刷新,使用双槽位可以实现周排行榜。
-
创建两个整数类型存档槽位:A与B,A存储第一周所有玩家的排行榜数据
-
当一周结束时(通过时间戳判断),使用B榜存储玩家在第二周的排行数据
-
在第二周开始时通过ECA清除玩家A槽位数据,以备第三周存储玩家排行数据。需通过在作者之家清除A榜数据(权限需找运营申请)。
-
当第二周结束时,将玩家排行榜数据存储在A榜并清除B槽位数据,循环往复
八、版本管理(待补)
在游戏开发的过程中,版本管理是一个非常重要的环节,好的版本管理可以帮助开发者实现更好的团队合作,更好的分支管理以及可靠的版本回滚。可以在编辑器的"菜单栏-文件"中点击版本管理使用版本管理相关的功能。
使用版本管理,若出现该情况,可以参考以下方式解决:
余下详见《版本管理使用说明》