Lost Gate

这个世界实在太无趣了… 既然如此,那就再创造一个世界如何?

断断续续自学UE4差不多有一年时间了,最近总算把一个游戏的核心玩法的DEMO制作完毕,算是告一段落。 老规矩,先上一下结业成果:

* 请修改画质,推荐使用1080P + 60FPS 观看

下面开始正文:

首先,本文假设各位看客都具备其他主流平台的开发经验(如Unity)和任意主流编程语言的基本功。如果您是纯净水新手,那么本文不一定适合你。

STEP。1 UE4总览

先下载UE4,安装,随便熟悉下界面之类的。

随后打开官网DOC:https://docs.unrealengine.com/4.27/zh-CN/WhatsNew/

目前UE4的资料相对于Unity来说,非常的少,官网是最佳的学习入口。很多所谓教材也是搬了一些官网的资料做的。

文档包含海量的内容,在此不建议每个都看。UE4具有超多的组件,其中很多是属于为了解决特定问题而存在的(如子系统、布料、地形之类的),对于新人来说,上来就接触过多的概念,可能会导致一种焦虑:为什么UE4这么复杂,我该怎么下手?

对此,我的惯用学习思路是做减法,只关注自己需要的内容。

例如,对于客户端程序来说,重点阅读:编程和脚本编写,创建交互体验 这两个章节即可,其他内容可以粗略看看。 不需要咬文嚼字,也不需要跟着动手做范例。只要了解概念了就可以(推荐阅读时长:4~8小时)。
其中UE还为Unity的用户准备了一个知识迁移指南,方便各位跳槽

https://docs.unrealengine.com/4.27/zh-CN/Basics/UnrealEngineForUnityDevs/

STEP。2 学习蓝图,并制作一个DEMO

当阅读后,脑海里应该有了一些概念。 接下来就该动手了。

UE4具有蓝图和C++两种编程方式。 假如你的目标和我一样,是为了做商业大型游戏,那么就趁早放弃以蓝图为主,或者纯蓝图项目的打算。

原因是: 1.蓝图不方便做团队协作,SVN、GIT兼容性不好,Merge困难。 如果为了热更新,那么Lua显然更好。 2. 表达效率低,尤其是数学计算相关的环节,代码几行就可以实现的效果,蓝图需要铺开一大摊子。 3. 无法操作内存,指针,更不用说ECS之类的架构。对于大型项目,构建底层框架非常困难。 4. 性能相当于C++较低,即便是编译里选了转化C++,也要明显的低。请注意,在大型项目中,核心玩法哪怕是性能提高10%,也是一个巨大的进步,而蓝图天生就拉下了一大截。

但是完全不用蓝图也不现实, 在UMG(UI组件)和动画蓝图等等环节,依然需要用到,所以C++为主,辅以蓝图,是我最推荐的。

所以,接下来我推荐先了解蓝图。跟着教程,做一个蓝图小DEMO。

先仔细阅读官网的蓝图章节,如果有编程基础的话,估计1,2个小时就可以上手。

https://docs.unrealengine.com/4.27/zh-CN/ProgrammingAndScripting/Blueprints/

随后,跟着官网的这个DEMO,动手实现

https://docs.unrealengine.com/4.27/zh-CN/ProgrammingAndScripting/Blueprints/QuickStart/

当然也可以到B站随意搜一个热门的蓝图项目范例,跟着做。(不出意外的话,应该大部分都是打手枪的游戏,嗯,没错,UE就是FPS游戏改造而来的引擎)

在完成demo后,对于UE应该有了一个基础的认知。 此时可以适当了解一下常见组件:混合空间,蒙太奇,粒子系统,材质等等。 在官网文档中,搜索相关关键字即可。

STEP。3 学习使用C++构建游戏

对C++不熟的,先补习C++。这里推荐一个网站,简洁无水分:

https://www.runoob.com/cplusplus/cpp-tutorial.html

如果有C#等其他语言的基础,预计3~7天即可完成学习(或复习)

为了巩固,建议用C++做一个DEMO实际演练。我个人是写了一个控制台的BadApple字符动画。

完成C++学习后,打开官网这个教程,使用C++完成一个FPS的demo:

https://docs.unrealengine.com/4.27/zh-CN/ProgrammingAndScripting/ProgrammingWithCPP/CPPTutorials/FirstPersonShooter/

完成后,就算是入门了。在进行下一个阶段以前,建议继续巩固知识,自行尝试给FPS DEMO加入新的功能(如,修改为TPS越肩视角,加入敌人,AI,拾取物,胜负判定等)。

STEP。4 正经的制作游戏

按照我的经验,差不多需要2~3个月的业余时间,就可以到达这个步骤。

此时,依照自己的喜好或者理想,选择一个自己特别熟悉的游戏,进行模仿。

(如果此游戏在网上有逆向的资源可供使用,那就更好了。在不商业使用的情况下,是没关系的。当然,也可以去虚幻商店获取素材)

不建议自行设计游戏DEMO,或者模仿自己不熟悉的游戏。 这会浪费大量时间去了解目标游戏的细节,还会导致DEMO最终品质不佳。

选择好以后,开始构建框架和工具流。

工欲善其事必先利其器。建议先编写底层模块,如 定时器,事件管理器,网络封装,渲染封装,物件池等。 这样可以巩固C++,并了解到UE特有的魔改C++环境,例如各类UBT标记,反射,GC规则,委托。 遇到诡异的设计,不要着急,这是UE C++,不是原生C++,很可能就是“设计如此”,你只需要欣然接受就可以。

底层搭建完毕,就是编写游戏逻辑的部分了。此时,因为项目不同,以及所遇到的问题的不同,就看各位自行发挥了。

—————— Extra ——————

Unity与UE的一些对应关系

Unity的常用组件在UE都可以找到对应关系,如Actor与GameObject类似,只是Actor不自带transfrom。

UE的距离单位= Unity * 100

Unity一般通过挂载一个MonoBehavior来展开逻辑。UE这边,也可以拖一个Actor展开,但建议使用GameMode。

Unity和UE都支持按键和轴映射,但是一个冷知识是:使用按键绑定互斥轴时,对轴状态切换处理不同,当两轴之间迅速来回切换的话,Unity必然会有至少一帧的轴值为0的状态,而UE不一定会有。切换时,插入0轴值,可以让按键的切换逻辑顺序比较可靠, A按下B弹起> A弹起B弹起 > A弹起B按下 (目前仅发现对使用按键输入且格斗游戏的序列指令判定有影响,其他游戏应该无影响)

Unity人形移动一般使用CharacterController, 而UE使用Character+ CharacterMoveComponent 。

推荐拓展阅读:

《inside UE4》,了解UE4的一些工作原理和底层。当然你自己阅读源码也可以
https://zhuanlan.zhihu.com/p/22813908

如何搜索UE4的资料:

建议放弃百度,使用谷歌以及UE社区的问答中心去搜索

https://answers.unrealengine.com/index.html

关于动作游戏的制作思路:

这是一个展开后可以说得很复杂的方向, 在此先推荐一篇文章,可以参考其中的思想:

https://blog.uwa4d.com/archives/USparkle_ACT.html

未完待续。


UE4 游戏框架StarsEngine FV


推荐使用1080P + 60FPS 观看。

经过5个月的爆肝,总算基本完成了UE4版本的框架。

ACT类游戏 帧同步及预表现技术分享


在前段时间,把2018年末所做的一个研究重新归纳总结了下,剪辑了一段视频,上传到了B站。 毕竟B站作为一个无广告的视频平台,很适合做技术Vlog。
视频地址
https://www.bilibili.com/video/BV1Fg4y1i7ii/

有一些B友(??)对这个演示的技术细节很感兴趣,今天对此写一篇文字分享

先用一个例子,概括一下状态同步和帧同步的技术原理

我们现在要在ACT类游戏中,实现这么一个游戏情景的同步:

A 向 前方移动了3米,随后转身面向B ,然后使用技能 Skill 100 攻击了B ,B被击硬直,同时掉血1000点。

状态同步的流程:
A 向前本地移动,同时发送自身移动的行为给服务器,并伴随间歇性上报坐标,服务器对A的行为进行合法校验,判断速度,位置连续性等,校验合法后将这个结果转义后广播给周围的玩家。这个广播的内容是: A 向 角度XXX开始移动。 那么在B的屏幕上就会看到 A朝 xx角度开始移动。
A继续之后的行为, 包括转身,攻击等,都和上面的流程类似。 本身向服务器申报一个行为,服务器校验并转义,广播给周围的玩家。
而B收到攻击,属于A攻击后的结果,由服务器运算后得出并广播。
可见,在状态同步中,A是传达自身的意图,结果由服务器运算决定。当A没有操作时,不会产生通讯。(心跳包等除外)

帧同步的流程:
A 发送 W键(假定这是向前走路的按键)按下的状态给服务器,并持续30帧,随后发送 A 键 , 随后发送 J 键 (假定是攻击)…… 服务器做什么呢? 服务器只负责分发给其他客户端,在同步层面不需要进行其他运算。 那么B在收到了 A的操作指令后,在本地对A进行运算,以此达到与A 所看到相同结果的目的。

在帧同步中,所有客户端共享自身的操作,同时也运算其他客户端的指令。由于游戏进程的推进依赖服务器发来的操作指令,所以发送指令的频率决定了游戏的逻辑帧率,一般设定为30fps即可满足ACT类游戏。一旦服务器停止操作数据的推送,则客户端逻辑便会处于完全停止的状态。

*关于 状态同步和帧同步的衍生同步方式: P2P
这是一种没有服务器的概念的同步方式,由每个客户端相互分发 状态/操作指令 进行游戏,一般采用UDP降低延迟,帧同步对顺序有要求,那么可使用现成的UDP组件,我使用的是ENet。
先说基于P2P的状态同步,只能算半个P2P,因为需要一个主机进行随机数、攻击判定的运算,充当服务器。也有作为辅助C/S结构而使用的情况,例如DNF中的组队AI、《猎人手游》中的关卡逻辑 都有采用类似的设计。这种本质依然是C/S结构。
P2P的帧同步,是可以做到无中心化,或者说即便有主机,也只是做游戏流程控制。 每个客户端在每帧收到所有客户端的指令后才进行本帧的运算,如收不到,则等待,逻辑处于停止,也就是“锁帧”。一个客户端网络卡了,所有客户端都遭殃。不过这种同步方式一般用于局域网游戏,卡顿的情况会少很多。早期的红色警戒,星级争霸,街机厅的双台对打游戏等都采用这种帧同步方式。

ACT游戏的帧同步意义在哪?状态同步又不是不能用

ACT游戏,不是APRG,也不是MOBA,是有格斗游戏特性的动作游戏,标杆代表是DNF。特性是密度极高的逻辑判定,对精准性要求也非常高,例如:浮空,硬直,上中下段,倒地追击,霸体,投技等等。 可能在极短的时间里,就会触发以上所有特性(试想一下,一个10帧的投技面对一个还剩3帧的霸体,哪怕只有20ms的通讯延迟,也丢掉了1~2帧的判定时间)。如不能准确快速的判定并给与结果,会严重影响手感。
所以许多ACT游戏会把大量逻辑运算在本地客户端进行,配合服务器校验。DNF即是这种处理方式。

先回到同步的选型上,实际上状态同步的泛用性极强,而且非常符合思考逻辑,大部分游戏采用状态同步就可以满足。

ACT使用状态同步也没有问题的,但是帧同步可以额外提供以下几点优势:

1. 多人游戏玩法的反作弊(PVP,组队PVE)
上面说过,基于状态同步,服务端参与战斗运算的力度决定了这个游戏的反作弊效果,但无论如何,服务端都要留有一定的阈值,不然由于网络波动等因素,容易产生误判。LOL和DNF都是基于状态同步,且反外挂做得都还不错。但LOL属于MOBA,运算量与DNF这类不在一个量级,即便手感有一定延迟也感觉不太出(尤其是采用预表现后)。而DNF的反外挂主要是腾讯的外挂式反外挂组件TP (??) ,和强大的律师团队(南山必胜客了解一下… 嗯味道不错,在南山有就好几家…)。其本身的运算依然在本地,所以“微调”挂、卡输入法等骚操作一度非常流行。
而帧同步从原理上,就不存在任何的偏差。多人游戏中,只要一方的计算结果不同,就会判定为作弊。服务器要做的反作弊,就是间隔性搜集每个客户端的关键属性即可。当然缺点也有,帧同步只能应用于多人游戏,单人模式是无可奈何的。而状态同步反而可以在单人模式中依然有一定的反作弊能力。

2. 网络传输体积较小,可应对大规模同屏战斗
当同场游玩的角色达到一定规模时,同步的通讯传输量级会迅速上升,例如PVP团战,RTS游戏等,可能会出现几十、上百的战斗单位,若采用状态同步,由于描述状态需要较多字段,其通讯量相当恐怖。而帧同步仅需要传输一个4字节的int(或者更少,取决于需要传输的按键数)。

3. 可保存操作指令,进行完整的战斗回放,体积非常小。
这个很好理解,把每帧记录的输入信息保存,进行模拟传输即可回放一场完整的战斗。由于每帧仅需4字节,按30fps的逻辑帧率,10分钟的战斗录像,也仅需1kb多,存储在云端也没有负担。星际,魔兽的战斗回放文件特别小,就是使用这个原理实现的。放到现在的网游,手游,回放战斗录像可以实现许多有意思的功能,例如赛季前100名对战记录,战略游戏的攻城回放等,无疑可以增强游戏的分享动力、社交性。

4. 可与其他客户端达到完全同步效果,画面一致,0偏差。
5. 可以本地运算伤害,NPC的AI,寻路等等,甚至可以去掉服务端。


帧同步的难点在哪?

帧同步的核心点,是要求所有客户端在同一时刻运行结果完全一致,所以从广义上来说,要做到两点:
1. 服务端每帧发送给所有客户端的指令,要完全一致,且顺序完全相同。
2. 客户端接收到每个帧指令后,运算结果完全一致。

第1点毫无难度,直接略过。
第2点继续拆解,每个客户端运行结果一致,这要求排除所有可能导致运行结果不一致的因素,常见的情况和解决办法有:
– 每个客户端机器性能差异,如加载速度,帧率,解析文件速度等等
解决办法:将此类逻辑与核心游戏逻辑分离。分离有两个概念,一个是时间上,可以先做这些行为,同时阻断核心逻辑(加载、解析文件),待全部准备好,再进行核心逻辑的运行。另一个则是view与model+control的分离,也就是游戏哪怕只有纯逻辑无渲染,也可以跑起来。

– 每个客户端逻辑update/tick的先后顺序不同
解决办法:使用tick管理器统一管理,不使用原生Tick/Update
– 随机数产生的值不同
解决办法:所有客户端使用同一个随机种子
– 使用基于非逻辑帧进行的各类运算(如补间动画,timer等等)
解决办法: 将这类逻辑使用统一Tick运算
– 浮点精度不同
解决办法: 使用定点数库
………………
…………
……

如何将帧同步应用到项目?

客户端方面,首先需要一个统一的Tick管理器,驱动游戏的所有关键逻辑。
所谓关键逻辑就是帧同步要驱动的内容,核心战斗就是,而像UI、摄像机的行为一般就不属于关键内容。
整个游戏的原生Update/Tick尽量都不要使用,全部注册到Tick管理器。 此外,注册时,要提供根据优先级排序的功能。
管理器完事后,可以先使用原生Update驱动测试。
如果一切正常,那么第一步完成。

目前客户端的每帧流程为 :
原生Update >> Tick管理器 >> 若干游戏关键逻辑(排序执行)>> 根据玩家输入运行逻辑


接下来是将游戏的输入操作进行“调制”和“解调”。 以某ACT游戏来说,所有的玩家输入有:方向键(16方向)+ 攻击+ 跳跃+翻滚+技能10个 。
在改造帧同步以前,这些输入将直接被执行为具体的操作行为。

而现在,则需要将输入状态搜集起来,尽可能的压缩体积,使用最小的体积记录所有输入的状态。一个byte有8个bit,那么一个int就可以存储32个键位的状态,每帧传输量相当小(具体可以搜索bitmap算法即可,本文不在赘述)。

这个步骤是完成了“调制”。 接下来反向操作,即是“解调”,解出来的操作信息,存入一个虚拟的输入判定器,游戏所有的输入判定,都从这里获取。 完事以后,先将调制解调在本地对接起来测试,如果一切正常则此步骤完成。

目前客户端的每帧流程为 :
原生Update >> Tick管理器 >> 搜集输入信息并压缩 >> 解压输入信息且放入虚拟输入判定器>> 若干游戏关键逻辑(排序执行)>> 根据虚拟输入判定器运行逻辑

到了这个步骤,你会发现,如果能凭空捏造一些输入,游戏也可以按你的思路运行。测试这个阶段成果的最佳方式就是“回放操作”。

将每帧搜集的输入帧搜集起来,存储为录像文件。 然后再读入,逐帧“解调”,运行,如果感觉像在看录像,恭喜你,帧同步的基础工作已经完成。

如果回放不完美,有了偏差呢? 那就是遇到了上面所描述的各种因素,导致了不同步。 不要小看一点点不同步,在每秒60帧的游戏里,很快就产生蝴蝶效应,不同步会迅速放大到无法接受。

本着步步为营的态度,我建议你先停留在这一步,不断使用一个时间较长、行为复杂的录像文件进行回放测试。在录像文件里插入当前帧的物件状态是一个好主意,回放的时候,就可以逐帧对比,一旦不同步,即可停止回放,结合上下文分析导致不同步的原因。 这会是一个很棒的单元测试环境,也是本人所推崇的方法论,简化测试环境和干扰项,有助于保持清醒。

当这个步骤完美后,剩余的步骤就相当简单了。

请将客户端的流程修改为如下:

原生Update >> 搜集输入信息并压缩 >> 发送给服务端
是的,因为Tick管理器从Update那里断开了,所以目前客户端不会再执行关键逻辑

而服务器也该干活了,他的工作就是建立一个固定帧率的循环,搜集每个客户端的输入信息,也就是那个int。每帧将这些帧信息广播给所有客户端,且保证顺序一致。如果有客户端卡了,这一帧没有发送输入信息怎么办? 那就当做没有输入去广播。(也有等待这个玩家的方式,也就是锁帧)

客户端收到服务端的指令后,“解调”输入指令,并驱动一次Tick管理器,游戏便运行了一帧。假设服务器是固定30帧,那么客户端在网络流畅的情况下,就是一个30fps的游戏。

最终的同步流程为:

客户端: 原生Update >> 搜集输入信息并压缩 >> 发送给服务端
服务端: 固定Tick>> 搜集客户端输入信息 >> 发送给所有客户端
客户端: 收到服务端指令>> 解调输入指令 >> 驱动Tick管理器一次 >> 若干游戏关键逻辑(排序执行)>> 根据虚拟输入判定器运行逻辑

由于加入了网络同步,可能会发生新的一系列不同步的情况。
情况复杂了,手工排查起来也许会困难。
这里建议制作一个本地AI,驱动游戏不停的进行对战测试并录像,一旦发生不同步,就保存录像和日志进行分析。
本人就有一个不同步的bug,在运行了3天的不间断的测试后,才触发……


关于帧同步中的网络波动及预表现问题

帧同步及其依赖一个稳定的网络环境,如果延迟过大,或者丢包率较高,那么每帧的lag波动将会非常明显。在不做任何处理的情况下,画面以及操作反馈都会频繁卡顿,体验极差。

平滑网络波动是最基本的提升体验办法,原理也相当简单,就是设置一个若干帧的缓冲区。 假设缓冲区为5帧,那么在5帧内的波动都不会受到影响,玩家会得到一个固定延迟的手感,对于一些交互频率不高的游戏,是可以很快适应的(其实 假如不是专业设备 ,从键盘输入到显示器最终呈现,在本地也可能会有数帧的延迟)

那么对于ACT来说,显然只是这样处理,并不能获得良好的体验。

预表现技术则可以弥补这个问题: 原理讲起来就是字面意思, 本地先对玩家的操作进行预表现,当帧同步数据收到后,再计算并与本地预表现进行拟合。

预表现的难点集中在,如何精准的预判玩家接下来的行为,以及拟合的自然度。

ACT游戏,难点在于攻击判定、自身位移这类操作。这里,要使用状态同步的思想,本地只进行技能演出,不做出任何攻击判定,也就是不要干涉帧同步的内容(嗯,这是一个废话)。至于位移,是拟合的主要内容。由于每个游戏的设计,核心玩法不同,这里无法给出一个统一的方案,不过大体上就是使用Lerp将本地参数动态平滑接近帧同步的计算结果。

那么这里一个问题是,主角只有一个,怎么同时接受两个分裂的处理呢?

方法也很简单,Copy一份逻辑版本的主角即可,这个主角没有View层,所以消耗也不大。
对了,使用预表现策略时,帧缓冲可以关掉,或者降低缓存值。此时,通信的及时性更重要。

好了,本次分享就是以上内容了。

在文章更新的途中,有朋友提问:网上都说帧同步的反作弊难做,怎么到你这就是简单了呢?

我的回答: 首先要明确游戏类型,在一些热门游戏类型中,帧同步确实存在反作弊的问题。例如:MOBA,FPS 。 由于帧同步是本地运算所有角色的状态,即便是view层屏蔽了玩家,也可以通过一些手段拿到逻辑数据,这就等于掌握了全局信息,开了上帝之眼。 而ACT游戏,大部分情况是同屏战斗,信息不对等的影响非常小,基本可以忽略不计。

UE4 学习手记(2)


因为猎人版本问题,搁置了一段时间。 前几天继续探索UE4。

因为蓝图是UE4的一大特色,所以决定先用蓝图构建一个FPS Demo来熟悉它。

不得不说,蓝图确实在一定程度上实现了“图形可视化编程”,大部分代码的编程思想都可以用蓝图表达出来。而之前与C++结合的那些诡异问题也不见了。

用纯蓝图去构建一个核心机制不复杂的游戏,或者DEMO还是挺快的。

不过我依然喜欢Pure Code !

Project UU 演示


新的坑开始了,还是兼任制作人和战斗策划。 一年前被迫转型制作人,现在还要继续…… 教练,我想写代码!

以上是战斗演示,下面是编辑器演示,已经相当成熟。

UE4 学习手记(1)


一直以来都想尝试下全视角ACT游戏。

考虑到画面,运行效率,以及探索新知识。这次决定使用虚幻4引擎。

这是我第N次冲动了,记得10多年前就很想复刻魔兽世界,但无奈as3效率太低,只能作罢。 这次学习目标是塞尔达传说:荒野之息。 它有着非常丰富的ACT、场景互动、开放世界、演出效果等要素,是标杆般的存在,任天堂真的是世界主宰!

万事开头难,UE4和Unity,虽然都是U字辈,但区别太大了。必须得学习一段时间的基础,才可以动手。

以下是初探UE4几天后,得出的感想

  1. UE4的 IDE,在部分功能层面远远强于Unity,比如美术资源相关的各个环节,几乎都有图形化的编辑器。在IDE主体布局上,与Unity差不多。
  2. UE4的IDE,在一些基础功能上却“落后”得吓人,比如在IDE 里创建C++类很简单,但是移除一个类?对不起,请关闭VS和UE4编辑器,打开文件管理器删除.h和.cpp ,然后删除项目的Binaries文件夹,在打开UE4编译一次,再重构VS项目。 What?! 当我百度了这个答案的时候,我以为这是搞笑。随后去官网看了官方人员的答复,我才逐渐接受了这个现实:这不是玩笑…
  3. UE4的蓝图非常强大,图形编程就能完成一个游戏!.. 然而,我还是崇尚pure code 项目,毕竟代码表达逻辑的能力要灵活且效率高得多。不得不提,代码与蓝图的结合上,UE4存在很多难以理解的BUG(暂且叫BUG吧,我不相信这是设计如此……),例如在一个Actor 里有两个component,A和B。 B是A的子项。 以这个Actor创建一个蓝图类。 此时蓝图显示正确,B在A的节点里。 此时修改Actor C++,把B和A平行(即取消B为A的子项)。编译后,发现蓝图结构也变化了,是正确的。但是此时再修改C++,把AB关系再恢复为B是A的子项,编译。 这时候蓝图仍然维持在AB是平行关系! 这时候去play in Edior,运行结果是错的。而当你使用standalone模式(单独生成一个EXE)去运行,发现是这个模式是正确。说明IDE内的蓝图是错误的。只有重新启动Editor,蓝图才会变得正确。 这个例子只是众多“修改不生效”的情况之一 … 希望是我不够了解UE4,不然这可够吐血的。一个开发环境如果不能可靠的反应结果,那多么可怕! 现在我的解决办法是:使用Standalone模式进行预览,同时加-Log参数,打印Log。虽然启动比PIE慢一点,但绝对可靠。此外,如果使用Pure Code,也可以避免。
  4. UE4的C++和正经的C++不太一样,更像是一个包装后的高级语言,然而还是带了C++的一些繁琐操作,比如头文件,include什么的。习惯了C#的简洁高效以后,还真是需要点时间适应。
  5. UE4的开发思想与Unity基本类似,都是基于DisplayList。但是基类明显多了非常多,一些轮子不用再自己造。从 Cocos2dx,Unity,UE4,感觉内置的轮子越来越多了。其实个人还是更喜欢Unity这种程度,功能简洁,灵活,方便拓展。当然了,UE4也可以当Unity用,官方DOC里有一篇很接地气的指南,告诉Unity用户怎么把经验转移到UE4。
  6. UE4的渲染画面确实更“高级”,这个必须承认。 至于效率,还没有做大型DEMO,暂时无法验证。

Spine 2D动态立绘学习手记



一直想抽空学下,起码要搞清楚原理,日后LostGate也许会用到。

前后花了几天业余时间,先看了下源码。感觉以前做过flash的话,很容易上手,毕竟都是矢量动画嘛。

先上个效果图,还没有乳摇,不过有那感觉了。

《猎人手游》上线了,回顾难忘的2018~2019年度



不知不觉blog停更一年多了,在这段时间里,经历了职业生涯最难熬、最黑暗的时期。

————-   2018年 梦魇降临  —————-

2018年初,《猎人》项目签约腾讯精品独代,团队士气高涨,计划6月左右上线。

2018年7月,《猎人》项目进入上线前筹备阶段 —— 本该如此。

天宅人祸接踵而至:

版号停发,游戏行业进入寒冬期。

由于猎人版号终审是8月,与腾讯的绿色通道也是擦肩而过,上线遥遥无期。

一开始,大家还期待只是短暂的停发,总是开玩笑下个月就能恢复了,不然吃shit。

然而年底很快就到了,依然没有消息…

另一个坏消息却到了: PR2测试成绩不合格(PR2是腾讯上线前的一次大规模测试)。

游戏成绩不好,这点确实不能怪版号。

事实上,抛开无法开付费导致的负面影响(开收费有利于留存),留存数据依然不怎么好。

在接连的挫折下,团队的核心骨干有不少都开始动摇,士气低落。

而资本市场,也受到游戏寒冬的影响,融资变得困难。公司不得不做了第一次裁员。

在经历了一系列痛苦消沉以后,我开始分析游戏的问题。

最后,我觉得问题有:

1。《猎人》是用了一个ACT的表现套用了MMORPG的体系,这可能有一定的矛盾

2。 在开发期间,项目的主导权不够明确(没有指定制作人),参考了太多游戏,导致最终体验很迷,四不像。

3。 游戏的核心体验部分, 主角战斗技能设定、主角数值、主角天赋设计、怪物设定、关卡设定,是分别不同的人在负责。

而这些负责人,许多是缺少ACT游戏经验的 —— 这也不能怪他们,毕竟游戏改过方向。

但是如果想法大相径庭,就会产生严重的不协调感:

例如 一个让主角免受硬直的,吸收反伤盾天赋,就可以毁掉主角间的技能逻辑平衡,这显然是MMORPG中的典型设计,套用到了ACT游戏中。 ACT是一个非常特别的分支,内核与ARPG都有巨大的差异,更不用说回合制了,需要团队对ACT都有相当的了解才行。

4。 完成度不够高,更具体点就是不够吹毛求疵。如果要做2019年的优秀游戏,必然要对游戏中的任何一点细节都不能放过。

最终核心团队决定往DNF方向修改。

我曾经是DNF狂热玩家,拿过QCG冠军,段位尊10,拿过服务器称号(达人)。

为了统一游戏的风格和思想,我毛遂自荐,做了制作人。

——————- 2019年  ———————

虽说是制作人,其实要做大量实际的开发工作。

首先就是核心玩法:战斗系统。

既然是系统,就包含了主角,怪物,数值,关卡等。

这一系列必须有统一设定—— 这就是制作人的工作。

主角的编辑是最耗时间的,从摸索使用编辑器,到全部主角,全部伙伴,全部怪物 翻做、修正一遍,差不多花了大半年的时间。

在这过程里,音效设定,特效也跟着重做了一遍。接下来是天赋和技能数值设定…

……

2019年进行了4次小规模测试,每次数据都有质的提升。这大概就是最好的回报了吧。

2019年8月,版号依然没有消息。我们和运营决定不开收费,强行PR2测试。

2019年10月初,PR2结论下来了,我们顺利通过。其中留存数据非常亮眼,次留68%,七留 27% ,月留9%

2019年10月底,延迟了1年多的版号下来了,我忍不住在公司内呐喊:”卧槽!终于等到这一天!”  。 这种在万般煎熬后释放的感觉,只有经历过的才能明白。

2019年12月4日,《猎人》公测。 首月收入1.4亿 。 显然,这个收入完全低于我们的预期。 原因是:导量仅有230万!(首月)。对比之下,侍魂是540万,魔力宝贝M 600万,(2021年修正并补充:一人之下 1200万)。

万万没想到,我们从未怀疑过的TX发行能力竟然翻车了…  人生就是这样,你以为经历了八十一难取到经了。然而还有个乌龟等着你(看过西游记的应该懂)…

虽然《猎人》没能大获成功,但是整个团队都松了一口气。这是作为游戏人发自内心的喜悦,毕竟难产了近3年半的游戏终于落地。

———————- 2020年 ——————-

今年除了发海外版,也打算重新拾起LostGate。
(2021年回来补充: 并没有拾起LostGate- -!)

放几个猎人的视频

https://www.bilibili.com/video/av76720420

https://www.bilibili.com/video/av76884542

战斗系统的构成笔记(中):战斗数值公式、配置的解析还原



客户端演出系统完成后,战斗系统的表皮就算完成了。

但要编写庞大的敌人数据库、遇敌数据、阵形数据等,对个人而言,是一件难以估量的事。

而还原魔力的本身的战斗配置是最好的选择,一方面,可以进一步还原魔力的世界,另外可以节省出时间进行二次创作。

而战斗公式自然也从原版中进行反推。 幸运的是,网络上有许多前辈推算过魔力的公式。不幸的是,存在着大量的纰漏、错误。

在这里感谢小D(数据偏执狂、女装大佬)给我的帮助,提供了世界观测者的核心公式,总算完成了大部分战斗公式,在此分享战斗伤害公式(网上许多资料推算错漏太多):

 

//************ 最终物理伤害 *********
public static double getPhyDamage(CharacterBaseAttr atker, CharacterBaseAttr target, bool cri, bool pvp)
{

//******* step 1 max(1,fix(max(0,[种族修正+属性修正 -1])*中值基础伤害*浮动系数))
//基本伤害
double phyBaseDmg = getBasePhyDmg(atker, target);
//种族修正
double raceFix = getRaceFix(atker, target);
//属性修正
double attrFix = getAttrFix(atker, target);
//浮动系数
double rndFix = getRndFix();

double dmg_base = Math.Max(1d, Math.Max(0d, (raceFix + attrFix – 1)) * phyBaseDmg * rndFix);

//******* step 2 //fix(必杀伤害+单位状态修正*单位优先修正*武器修正*第一轮计算结果)

//必杀伤害
double criDmg = cri ? getCriFix(atker, target) : 0d;
//状态修正
double stoneFix = getStoneStateFix(target);
//单位优先修正
double typeFix = pvp ? 1d : getFighterTypeFix(atker, target);
//武器修正
double weaponFix = getWeaponFix(atker);

//修正后的基础伤害
double dmg_fixed = Math.Floor(criDmg + stoneFix * typeFix * weaponFix * dmg_base);
//******* step 3 最终伤害=多级取整(技能修正*精灵变身修正*武器种类修正*防守状态修正 *第二轮计算结果)

dmg_fixed *= atker.phyDmgFix;

return dmg_fixed;
//第三部整合到技能逻辑里,不在这里运算了。

}

//魔法伤害公式

public static double getMgcDamage(CharacterBaseAttr atker, CharacterBaseAttr target, double[] dmgCfg, int skLv, double atkWin, double atkWater, double atkEarth, double atkFire, bool cri, bool pvp)
{

//最终伤害=floor(其他修正* floor(抗魔修正*单位优先修正*人物属性修正*(种族修正+魔法属性修正)*精神比修正
//*(精神贡献系数+魔攻贡献系数)*设定伤害*0.88*浮动系数))

//种族修正
double raceFix = getRaceFix(atker, target);
//属性修正
double attrFix = getAttrFix(atker, target);
//浮动系数
double rndFix = getMgcRndFix();
//状态修正
double stoneFix = getStoneStateFix(target);
//单位优先修正
double typeFix = pvp ? 1d : getFighterTypeFix(atker, target);
//其他修正
double exFix = 1d;
//设定的基础伤害
double baseDmg = dmgCfg[skLv – 1];

//精神贡献系数 min(80,四舍五入(攻方精神/(msp/80)))/80
double msp = 103d + skLv * 20d;
double sprFix = Math.Min(80d, Math.Round(atker.spr / (msp / 80d))) / 80d;

//魔攻系数 0~0.5

double mgcAtkMin = getMgcAtk_Min(skLv);
double mgcAtkScope = getMgcAtkPlusScope(skLv);
//实际有效值,低于最小魔攻则认为是0
double mgcAtkPlusValue = Math.Max(0, atker.mgcAtk – mgcAtkMin);
double mgcAtkFix = 0.5d * Math.Min(1d, mgcAtkPlusValue / mgcAtkScope);

double damb = mgcBaseDmg_single[skLv – 1];

//精神修正比(暂无)
double sprOverPowerFix = getSprOverPowerFix(atker, target, atker.charType == CharType.Hero);
//魔法属性修正
double mgcAttrFix = getMgcAtkAttrFix(atkWin, atkWater, atkEarth, atkFire, target);

//抗魔修正
double mgcDefFix = getMgcDefFix(skLv, target);

//最终结果

double result = Math.Floor(
exFix * Math.Floor(mgcDefFix * typeFix * attrFix * (raceFix + mgcAttrFix) * sprOverPowerFix * (sprFix + mgcAtkFix) * baseDmg * 0.88d * rndFix)
);
result *= atker.mgcDmgFix;

return result;

//factor 种族、属性、魔法属性综合修正
//factor1 魔攻修正
//factor2 抗魔修正
//factor3 精神比修正
//factor4 战斗修正 穿插在float内 不参与取整 顺位待明确
//dam=fix[fix[factor3*特殊舍入[(min[80,准四舍五入[(Sp1-2)/step]]*dstep]+特殊舍入[damb*factor1])]*factor*factor4*float]*factor2
//必杀伤害
//double criDmg = cri ? getCriFix(atker, target) : 0d;
}

 

有了公式以后,角色的属性计算、成长计算、战斗计算就可以进行了。这样角色就可以通过设定等级、加点即可推算出与原版一致的属性,而不用设定99999之类的测试属性了。

接下来是配置的解析,还是不少的:

 

enemyBase

怪物的基础模版,例如 树精的base就叫树精,但任务中的 xxx的树精,base也是树精。

enemy

怪物表,可以是enemyBase的实例,记录了怪物的等级随机范围,掉落物品,特性(2动,是否boss等)。特别说的是,召唤id居然在这个表,也就是召唤技能施放的时候需要读取enemy身上的召唤数据,感觉挺奇葩的。

group

阵形表,表明该组合每个站位会有哪些怪物登场, 当然这些怪物读取自enemey表

encount

遇敌区域表,在地图中标记遇敌区域,指定遇敌率,以及遇敌后的group

enemyai

敌人的AI表,结构很简单,从上到下,根据概率判断是否执行某个技能。 与当今行为树这类AI相比,简单不少,但应对回合游戏倒是够用了,还很容易配置。

看看这个类结构,我觉得不需要过多解释了……

public class EnemyAI
{
public int id = 0;
//条件
public int[] condition = new int[10];
//目标
public int[] target = new int[10];
//技能
public int[] skill = new int[10];
//概率
public int[] priority = new int[10];

}

enemytalk

敌人的战斗对话表,定义了在各种情况下所BB的内容。 例如:死亡后,  “呼~玩得好累啊”

skill

这是我自行整合的表,原本的配置叫tech,因可读性问题,转义为了unity格式。

含义如它的名字,定义各类技能的基本信息。

 

解析以上表以后,即可在世界地图中真实遇敌,还原原版的敌人。

 

 

Email
57085445@qq.com

讨论群
LostGate同人游戏 184379459
魔力宝贝官方手游群 540221885