年收入超百亿,Neople总监演讲:席卷中国《DNF手游》如何开发的?
【GameLook专稿,未经授权不得转载!】
Gamelook报道/DNF手游可以说是2024年收入表现最好的新游戏,根据多方数据梳理,GameLook认为《DNF手游》在2024年的累计预估流水收入或超达到了200亿人民币左右,截止本月游戏已在中国市场上线了一整年,《DNF手游》依旧长期占据畅销榜TOP10。
作为一个PC游戏经典IP,《DNF》是如何走向手游平台的呢?前不久的Unity首尔2025活动上,Neople《DNF手游》技术总监徐南赫(서남혁)以“PC到移动设备:’DNF’的技术过渡过程”为题,分享了DNF手游从PC平台切换到移动设备过程中经历的各种技术挑战和解决方案。
为了在移动设备上重现DNF的乐趣,Neople 开发团队针对移动环境重新设计了游戏,同时尽可能保留现有 PC版本的资源。为此,该公司引入了 Unity 引擎,开发了专用工具和脚本解析器,将 PC版DNF资源转换为 Unity 资产,同时致力于将用 C++ 编写的代码移植到 C#。
以下是Gamelook听译的完整演讲内容:
徐南赫:
今天我分享的是《DNF手游》研发团队在做手游版本过程中面临的问题和解决方案,开始之前,我想简单介绍一下我在Neople的经历。
我于2017年2月加入Neople,负责《DNF手游》的技术团队,2021年11月,游戏通过了TDR3评审(即腾讯的技术和研发筛选过程),完成了技术优化。2021年12月,项目进行的如火如荼,我被任命为技术组长,通过引入多线程结构提高CPU利用率和中继服务器技术改进工作,例如通过优化提高网络性能。
但随后,由于各种原因,项目陷入暂停。直到2024年1月,我才再次回到《DNF手游》项目,我目前担任技术总监,负责技术的改进,这也是我们今天分享的主要内容。
今天我们将首先介绍《DNF手游》的技术切换过程。首先是遇见Unity,它是用Unity引擎研发的,我们面对并解决了具有挑战的问题,根据Unity引擎的结构重新设计了游戏结构,确保稳定的运行时性能。第二部分是《DNF手游》的核心战斗,以提升实时多人游戏环境所需要的精准帧率、响应速度和防作弊。
最后,我将简短介绍《DNF手游》的服务运营解决方案,如技术结构和测试自动化,以及响应系统。
开始分享之前,先介绍一下世界上最好的2D横版动作RPG游戏,DNF有用大量具有吸引力的角色、华丽的动作和技能组合,高品质的地牢制作、深度剧情等因素增加了游戏乐趣,就连ChatGPT都承认这一点,所以我就不做更多解释了。
遇见Unity:《DNF手游》引擎选择
接下来,我们说第一个话题,《DNF手游》的技术转变过程。
《DNF手游》是DNF的移动版本,基于Unity引擎研发,以便让玩家在移动设备体验到DNF的动作和乐趣。这张图展示了《DNF手游》的研发历史。
项目的目标是将PC版本的乐趣搬到手游平台,为实现这一点,我们试图在转换到移动环境的同时,尽可能保持PC版本的源架构、资源、子资源结构。
为此,我们很早就在Unity中打造了一个专门工具,并且转换了资源。我们还做了一个专门的解析器来解析规划脚本(planning scripts)来适应Unity环境,我们还将用C++写的架构移植为C#。今天的分享中,我会介绍之前任务中的代码结构转换流程。
首先,为了帮助理解《DNF手游》为Unity做的代码结构的转换流程,我会简单介绍构建和表达NDF世界的主要类(classes)。我要介绍的第一个类就是DNFObject(DNF对象),这个类是几乎组成了DNF世界所有对象的基础类,包括角色、怪物、NPC、抛射物与火器。它管理一个对象的数据与状态,处理该对象行为和呈现的所有需要的功能,并管理其DNF动画进行视觉表达。
接下来是DNF动画,它包含为角色状态或行为制作动画所需的数据,并内部管理多个DNF动画帧。最后是DNF动画帧,这个类包含了每个帧的数据,它包含了构成对象外观的数据,例如纹理、颜色、大小、位置等等。
总的来说,《DNF》用这些类构成和表达游戏世界与渲染过程,后者是在屏幕上展示对象的流程。它是通过直接在基础代码中调用API来实现的,我认为这个架构在韩国很多2000年代中期研发的游戏中都比较常见,当然这句话是我的个人观点。我们对以上介绍的类进行了重新设计以适应Unity引擎的游戏对象、组件。
首先,我们在MonoBehavior中完善了DNF对象和DNF动画,并且为Sprite Render互通性增加了叫做DNFSprite的新类。我们为其做了结构以便DNF动画帧信息可以链接到DNFSprite,最终在Unity屏幕上通过Sprite renderer表达。如果看图片的右边,可以发现组成角色的游戏对象,以及子游戏对象的结构
最后,通过逐个改变DNF对象的结构和逻辑处理流程,我们形成了DNF在Unity环境中运行的基础。但是,让它运行和确保性能适合服务是完全不同的两件事。
如图可见,内存使用呈现稳步上升的曲线,但游戏的fps却起伏不定,而且无法保持稳定。我们尝试了其他的优化方式,但很难达到我们期望的性能。
最后,我们总结认为,单核架构能实现的性能是有局限的,因此决定通过分离计算部分和表达部分来推进《DNF手游》架构的开发,以减少性能瓶颈并提升计算效率。
首先,我们在逻辑层只聚焦于游戏规则和运营逻辑,并移除了存在于其中的所有表达相关的代码。单独表达相关的代码被重新放到了一个描述在规则中的单独类,我们对其进行了重组以便角色分工清晰。
重组的逻辑层可以很清晰地通过自上而下的方式展示和查看,在不同视角之下可以单向传递。运行结果通过中间传输对象传递到视图层,而视图层则仅负责展现它们。
通过这样分离结构,逻辑层可以在独立的逻辑线处理。此外,它为完全分离到separate assembly提供了基础。如果看右侧图片,简单解释,其左侧的逻辑层包含在左侧逻辑层区域的backend.dll中,负责实际的游戏、计算、规则与处理,并在一个固定30fps的逻辑线程运行。计算结果以信息的方式传递到视图层,右侧的视图层是运行在Unity环境中的表达区域,包含在Assembly-Csharp.dll中,写在Unity的主线程中。
基于接收到的信息,动画、特效、对象文本状态等通过Unity表达。通过这样完全分离两个层,可将逻辑结构化,以在并行(处理)中实现性能最大化,视图结构可以聚焦于Unity主循环。
这是《DNF手游》实机界面,可以看到,即便是实时处理多个对象和复杂的前端计算,玩法体验依然流畅。《DNF手游》在Unity中运行也非常好。
但是,完成分离层的工作之后,我觉得我跨越了一座大山,但这一次,一座真正的大山在等待着我们。是的,我们的下一步就是支持形成了华丽渲染效果、复杂节奏和高容量特效处理的内容。
最近,DNF有越来越多的高品质制作,自然,《DNF手游》也需要同样级别的制作水准。但问题是,《DNF手游》是为移动平台制作的,换句话说,这是个完全不同的环境,包括GPU、CPU性能、内存和处理效率等。最终,我们不得不再次跨越高山。
或许,时间最终会解决一切,我有这样一个空洞的希望。但是,这并不意味着我们能等着时间来解决这个问题。
现在,我们将使用Unity引擎的最新技术。
实际上,不夸张的说,《DNF手游》当时仅把Unity引擎当做了一个多平台支持工具。然而,为了向前发展,我们计划主动引入Unity最新技术,为《DNF手游》接下来的旅程做好准备。
首先,我们将Unity引擎版本从2021切换到2022,以充分利用Unity最新的API。我们还计划引入一种可寻址地址系统,以显著提高内存管理的效率。我们还规划了一个能在复杂环境中通过纹理数组有效降低绘制调用的结构 。
最后,我们还考虑通过引入视图层ECS结构以最大化并行处理效果,为实现性能提升打下基础。
《DNF手游》的战斗同步
接下来说我们游戏的一个核心技术:战斗同步。
我们问了很多人,让他们指出一款支持多人实时战斗的动作游戏最重要的一个元素是什么,这也是DNF端游和手游之间最大的差别之一。这部分,我们将分享《DNF手游》的实时战斗结构是如何设计的,以及在这个过程中我们有哪些顾虑以及做了哪些尝试。
DNF端游的实时战斗是以P2P方式完成的,这种情况下,玩家彼此直接相连,并交换游戏状态,不需要中央服务器。用右侧图片简单解释这种同步处理,每个玩家基于自己的输入模拟战斗并带来状态信息,如角色合作或者怪兽HMP交换都可以直接相互传递,因此创造了一个动态化世界。
换句话说,这是一种分享状态的方式,是一个模拟结果而不是分享信息。这种情况下,如果玩家因为网络问题在沟通过程中无法连接游戏,连接就会通过中继服务器转发。这种结构的优势,就是它非常快的响应速度,因为数据是职业交换的,不经过服务器,它具备较少网络支持和减少服务器基础设施成本的优势。
但是,这个方法显然有弊端。首先,它很大可能会出现状态不匹配,当不同的网络环境之间交换数据的时候,哪怕是较小的丢包或者时延也会完全改变游戏状态。
另一个问题是,它很难阻止作弊。因为没有中央服务器,没有万无一失的方法来防止数据篡改。最后,数据量巨大,因为所有的玩家必须彼此交换状态,网络占用必定会快速提升。最终,这些特点导致了它在手游环境中是有局限的。
在需要考虑各种网络状况的手游中,这种P2P的战斗方案并不现实,所以我们选择了基于lockstep(锁步)服务器的帧同步方法,这种结构更加稳定。
接下来我们介绍《DNF手游》使用的基于锁步服务器的帧同步方法,它迫使所有玩家在同一帧处理同样的输入,我用图表的方式解释该结构。
与P2P方式不同,每个玩家并非彼此连接,而是通过一个锁步服务器交流,因为每个玩家并不是直接向其他客户端发送他们的输入,锁步服务器代替他们作为中继,当发生需要扣除服务器所管理物品的事件时,例如使用生命药水或金币,游戏服务器也向锁步服务器传输信息.
所有以这种方式在锁步服务器上收集的信息 都会定期传输给客户端,然后客户端以接收输入信息的方式运行模拟每帧的流程。归根结底,这是一种状态通过先前流程实现结果同步的一种方式。
作为参考,《DNF手游》每秒向锁步服务器发送30个输入状态,锁步服务器将每两个接收到的输入状态转为同步压缩包,并每秒发送15个同步压缩包。结果是,客户端每接收到压缩包可以模拟两帧。
选择这种设计是为了提升锁步服务器性能,以减少网络时延导致的掉帧。
接下来我们总结这种结构的优势,首先是完美的同步变得可能,由于他们在同一个时间点处理同样的输入,客户端之间不会存在状态不连贯。第二,可以做出强大的反作弊功能,如果一个玩家篡改了数据,其他成员的模拟结果就会改变,这样就很容易检测出异常。《DNF手游》运行的结构是在多人游玩的时候阶段性对比关键数据,并通过验证服务器进行最终确认。
第三,传输数据量可以最小化。由于仅传输底层输入数据和模拟结果,因此具有较高的网络效率。作为参考,三人活动中,最大速度达到7KB/S。
最后,中继功能很容易部署,游戏可以通过存储输入进行完整回放。
然而,即便是这种理想的结构也有缺点。第一个就是网络延迟问题,由于所有玩家都是通过服务器交流,一个人的延迟可能影响所有人。然而,《DNF手游》可以通过采用异步锁步同步方法,在一定程度上缓解这个问题,该方法不用等待所有玩家的输入接收完成。
第二是可能存在反应频率的减缓。由于输入通过服务器,与P2P方法相比可能存在延迟。第三个问题,是服务器运营成本较高。所有的输入都需要服务器基础设施来处理并中继输入。第四,研发难度较高。没错,这部分需要大量的研发资源,包括精准同步、网络补偿和错误恢复逻辑。
尽管有上述不足,《DNF手游》还是选择了帧同步的方式来确保精准的战斗同步和稳定的网络体验。所以接下来,我会解释考虑部署帧不同的时候,你需要考虑什么。
所有的帧同步都需要保证有一个确定性结构。这意味着同一个输入始终要有同样的结果,为此,你需要考虑以下因素:
第一是确定执行顺序。这要求视图和逻辑层的分离,因为屏幕输出和实际游戏逻辑是相互交织的,那么执行结果就可能不一样。
第二是分离逻辑层,以便它们不依赖于Unity的周期。如果战斗逻辑直接在结构内处理,那么Unity的MonoBehavior和底层结构可能导致不确定行为,因为帧对帧函数调用顺序或者帧对帧处理顺序可能改变。
第三,你需要在地牢开始时将随机数种子(random seed)初始化为同样的数值。你要在每次模拟时将地牢初始化为同样的种子值,以确保同样的随机结果。而且,我们还需要管理用在不要随机性区域的随机性。
第四,逻辑层不应存在国家概念。因为所有玩家都需要基于同样的数据执行同样的操作,如逻辑层内‘假设我的角色是’这样的条件声明,对不同的玩家可能导致不同结果。
作为参考,看看右侧图表,你会看到服务器在逻辑层是模糊标记的,这是要强调,尽管战斗计算逻辑是在客户端内进行的,但它却是以独立方式运行的,看起来就像是由服务器处理的一样。保持确定性架构的时候,另一个问题也需要考虑在内,那就是浮点运算五阶问题。它不只是意味着真实数字运算的精准度问题,取决于平台、CPU架构、编译器设置是运算顺序的差别,哪怕是同一行代码带来的实值运算也可能产生不同结果,这很快就会导致客户端同步问题。
为解决这个问题,《DNF手游》在构建过程中配置了 Roslyn 分析器,用于根据特定规则批量替换代码中的浮点运算结构。也就是说,通过强制所有浮点运算以一致的方式进行,我们从根源上杜绝了因浮点运算第五阶(或高阶)特性可能引发的非确定性行为。我们还自动插入了函数调用语法中的追踪语法,以便您可以追踪函数调用的过程。这使得在发生同步错误时,能够轻松追踪是哪个计算过程出现了问题。
接下来,我会解释运用在《DNF手游中》为解决基于锁步服务器的框架方法所存在的反应速度迟滞问题而提出的预运动(pre-move)概念,该问题曾被视为这个方法的主要缺陷之一。
预运动(pre-move)首先根据玩家控制角色的本地输入处理运动数据,然后基于锁步输入重新校准模拟结果。
为了支持角色的无缝运动,我们将角色分成三个对象。如果看右侧图片,可以发现角色A是一个本地输入逻辑对象,能即时反馈玩家的操作,它是部分反应(semi-reactive)的。从用户角度,他们可以体验一种即时操作感。然而,由于游戏依然需要通过锁步方式同步,左侧的实际决策是由非角色逻辑对象在锁步输入的基础上处理的,基于从服务器接收到输入生成准确的模拟结果。
最终,这个结果被传送给角色C,即可视对象,并展示在屏幕上。对象C对比基于本地输入的对象A和基于锁步输入对象B的状态,如果差异在可接受范围内,那就反应了A的位置,否则它会基于B进行纠正。需要注意的是,这个方法仅适用于玩家直接操作的角色,因为我们不知道其他玩家的本地输入。
由于其他玩家操作的流体视图仅基于锁步输入,如果发生延迟也可以保证有流畅的动作。为缓解这些问题,航位推算(dead reckoning) 被用于存储来自此前帧的速度与方向信息。
在《DNF手游》中,通过将对象A、B和C以这种方式分开,我们可以向玩家提供一种快速而自然的操作感,同时内部保留精准和确定的帧动态结构。
动图中就是部署之后的动作展示,左侧的时延是33ms,右侧的时延高达130ms。红色的角色是基于之前提到过的锁步输入操作。可以看到,时延越长,与本地输入角色相比,位置错误就越大。
《DNF手游》中,当一个玩家之间的同步被认为很重要的事件发生时,比如击打或者被击打,本地输入角色会被校正到锁步输入角色位置。这可以让我们仅在需要的时候执行精准同步,同时维持响应灵敏度。
接下来,我会解释利用帧锁步结构的回放系统。《DNF手游》主动利用基于逐帧录制的输入数据重新模拟的回放系统,在实际服务运营时,该系统被用在三个关键功能中:
第一个是战斗验证功能,为了确定玩家发送的战斗结果是否可信,服务器模拟输入数据,并且对比和验证客户端的模拟结果与正确的结果匹配。《DNF手游》使用此前介绍的日志层后端DL,以形成一个单独的战斗验证服务器,并在真实服务中运行。
其次是一个被叫做侵入恢复(intrusion recovery)的功能,这是一个重连游戏恢复功能。当一次游戏期间应用被中断或者网络连接丢失的时候,我们支持基于回放数据修复战斗状态,以便玩家在不被打断的情况下继续游戏。
第三是视角与回放功能。由于整个战斗可以通过帧输入录制精准重现,部署一个观战系统或PvP回放系统就很容易。
接下来,我们展示一下运用在实时运营环境中的战斗验证、质押恢复(staking recovery)和我们之前提到过的观战功能。第一个展示的是安全人员使用基于存储在服务器上的回放数据的回放查看器验证战斗内容,这可以很容易检测出异常玩法或者使用作弊的情况。
第二个展示的是重连后的地牢玩法恢复流程,当一名玩家尝试重新进入地牢,玩家客户端会接收来自锁步服务器的录制帧数据,并利用这个数据快速模拟战斗,直到玩家回到地牢之前。第三个展示的是在锦标赛期间实时观看对战双方的能力,这也是基于回放系统部署的,因此它可以实时可靠地广播头部玩家的战斗场景。
最后一部分是服务运营解决方案。我要介绍的第一件事是用来管理技术指标的仪表盘。这个解决方案可以提供一个让你在运营和研发中实时监控各种技术指标的服务。我们建筑的关键指标包括同步误差率、崩溃率,以及性能相关的指标,如内存占用和FPS。
我们在打造和运营一个系统,它让我们可以通过相应指标的仪表盘实时对运营服务期间出现的技术问题快速做出响应。
这里是对Unity测试机器人的介绍。为确保稳定的服务运营,我们利用了一个使用自动化测试机器人的验证系统。代表测试包括功能测试、战斗性能测试和运动与错误检测测试。在此期间收集的玩法数据,与前面提到的仪表盘都实时被监控。
我们要介绍的最后一个运营解决方案是热更新解决方案。与PC平台不一样,手游平台并不允许可执行文件自由补丁。由于补丁文件必须经过审核流程,甚至当一个bug出现之后,最快也要延迟两三天才能解决。
为克服这些困难,我们引入并使用了一个叫做InjectFix的解决方案。InjectFix是一个由腾讯研发的Unity运行时修复解决方案,它可以通过使用DLL插入的方式快速解决bug,而不用给你的可执行文件打补丁,关键功能包括支持C#代码修改以及兼容il2cpp环境。
不过,有一些事情需要注意。哪怕你提前指定插入目标,也只建议在有限领域使用,如UI事件处理,可能会发生性能优化问题。右侧图片是一个简化的实际注入流程示意图,游戏先运行,通过CDN收到一个包含修复dll和资源的补丁文件,然后InjectFix通过运行时注入修复代码的方式工作。如果有人考虑热更新的抗衡手段,我认为InjectFix是非常值得考虑的选择。
以上就是今天的分享。虽然没有讲到尖端技术或者创新解决方案,我们依然希望今天的内容能为像我们那样将PC游戏转移到手游端遇到问题的同行提供一些小提示,谢谢。
如若转载,请注明出处:http://www.gamelook.com.cn/2025/06/571251/