*本文仅发表于indienova(本文内容有可能被我做成视频,并继续向更多人推荐《节奏医生》这款游戏)
好久不见,还是得自我介绍下,自我上次在这里写文已经快过去一年了。我是《多元窗口》(一个基于Unity的多窗口解谜游戏)的开发者DemonLord-,上一次我在这里简单分享了透明窗口里的UPR渲染技术。而这次我来写文,是因为我玩了《节奏医生》,我有一些属于我个人的,对这款游戏的独特感悟。

首先还是先聊聊这款游戏,我从2021年3月就玩了这款游戏,一直看着这款游戏从第二章到现在的结尾。我非常欣赏本作的单键音游玩法以及对节奏的强调,上手简单到让视力障碍人群都可以游玩本作。我认为本作的设计理念十分优秀,给我一种克制后保留核心的美感。在《Peak》中,游戏用一个体力条将爬山的消耗、角色的生命、道具的回复、受伤的扣血全都结合到一起,各个元素高度精密相连,堪称”减法设计教科书“,而《节奏医生》中,音乐、演出、玩法这三个元素也都跟着节拍一起精密联系。玩法要求玩家时刻注意音乐的节拍,而音乐和演出提示玩家节拍的时机,同时音乐辅佐着演出的进行,演出提供了游玩的挑战与乐趣,甚至许多演出又和节拍的视觉提示融合在一起,这样的设计实在是太过于精妙,太过于优秀了。而更不要说本作的音乐和演出质量相当在线,鲜有这样音乐、画面、可玩性、剧情全都要的水桶型独游,虽说随之而来的是漫长的开发周期,但最后呈现的效果,完全可以说值得粉丝们这么多年的等待。

虽然上面说了那么多内容,并且游戏的结尾也确实触动人心,但这款游戏真正震撼我的,其实是在我第一次玩到2-XN,看到了我探索许久但求而不得的——Unity单进程多窗口解决方案。是的,这款游戏不仅在视听、剧情、玩法都发挥出色,它甚至代码都独一无二。接下来且听我,一位花费了大量时间钻研Unity多窗口技术的开发者,向你解释为什么在Unity中实现单进程多窗口是一大壮举。

Unity引擎本身设计之初,就是一个专门服务于单窗口游戏的引擎,而不像最近的Godot 4能直接创建多窗口。渲染管线、输入系统、UI系统等等都只考虑单窗口,如果强行用Windows下的API(Form.dll)创建一个新的窗口,则新的窗口不会显示任何内容,并且游戏大概率会崩溃,更别提在不同平台下有着复杂的运行环境。几乎所有Unity论坛上讨论到关于“多窗口”内容时,都认为同一个Unity程序开多个窗口是不可能的,都普遍认为应该使用多进程+通讯的方案,也就是我正在使用的方案。在我的方案里,多个Unity窗口通过Netcode在本地进行连接,实现一个本地上运行的多人游戏的效果,再搭配各种WinAPI以及奇怪的技巧,才堪堪让我的游戏能够正常运行(并且因为会开很多游戏,性能消耗巨大)。再早一点的多窗口游戏应该是《OneShot》和《Windowskill》,前者由Unity+管道通讯技术开发,只能实现一点简单的信息传递,而后者则利用了Godot 4的功能,便捷实现了单进程多窗口。可以说,从来就没有人研究出Unity该怎么实现单进程多窗口:要么就用我的方案,牺牲性能与可用的窗口数量,复杂到难以学习;要么就用Godot 4来做,可能要忍受Godot 4在开发深度项目时的各种不足。

但是,《节奏医生》的开发者们真的做到了。在游戏的最后两大关中,在之前多次出现的演出用的窗口真的分裂成2个甚至是3、4、5、6个,并且创建和销毁几乎看不出延迟,每个窗口显示的内容也完全不同。我真的在打完2-XN后紧急去看了一眼文件夹,确定一下开发组有没有偷偷换成Godot。当然,Unity的项目目录结构如假包换。

好在我在这块领域有着比较丰富的研究,可以分享一些进阶的细节。在我翻看项目架构时,我立刻就找到了他们的秘密武器——位于Rhythm Doctor_Data\Plugins\x86_64文件夹下的multiwindow_unity.dll。显然,这就是他们实现单进程多窗口的办法。我进行了简单的互联网搜索,我初步认定这个插件并不是其他人分享的开源项目,也就是说他们的程序应该是自己手搓了一个DLL插件,并且真的能够在Unity中实现近乎完美的多窗口效果,这里的技术难度我简直不敢想象。

当然我能说的也就到此为止了,因为我并不知道这个DLL内部到底是如何实现的。理论上可以用逆向工程(违法)的方式去了解这里的技术,我的好奇心确实很旺盛,但我对逆向工程一窍不通,并且我国有一套完整的法律。所以,我想我一时半会是无法知道这个dll里到底装了什么。我在Steam讨论区那边留言请教,希望开发者能看到并给我一些答复。以及这里的地图里藏的匕首也露出来了,indienova作为发行商,那想必是能够联系到开发组的。我写了那么多,一方面是我玩完《节奏医生》是真的有话憋着难受,另一方面也是真的很希望能够通过Indienova,看看有没有可能联系到开发者里负责这一块的程序。

《多元窗口》诞生伊始,也正是收到了《节奏医生》的启发,那时候我可不知道有个游戏叫做《Outcore》,《WindowsKill》更是还没开始立项。我忘不掉窗口在桌面上移动的咖啡之歌,忘不了《节奏医生》所授予我的这份口味,我便在某个不到7天的Game Jam里,搓出了《多元窗口》最开始的样子。我一路上探寻着这个技术的未来,曾以为“单进程多窗口”终是海市蜃楼,便装作我所用的技术已是唯一的解答。没想到这快五年过去,我的问题由祂而来,而答案亦回到了祂的身上。也正如我的群友所言——《节奏医生》对我而言,既是阿尔法,也是欧米茄。
我想我这辈子大概率是忘不了这款游戏了。

你好,《节奏医生》的开发组目前正忙于修复新发售版本中的问题和调整细节,同时他们也需要一些休息时间,可能需要过一阵子才能响应非紧急的咨询和请求。未来有机会的话我们会问问开发者能否做一些技术分享~
@virmint:感谢,非常理解他们的幸苦,能研究出这么夸张的技术,更别提还有如此优秀的体验。同为开发者,我现在都在想他们是不是应该去中海医院去接受几周的节奏疗法。也十分期待能看见他们在休息过后,能分享各种有趣的幕后故事。到时候恐怕还得麻烦Indienova的发行老师看看情况,能不能帮忙联系一下了。
盲猜dll里调用创建窗口移动窗口这些windows native api
然后unity端用多个camera render到texture
把unity里渲染完的texture传递到dll部分
@Sailing航:我想思路上大概是这样,但是重要的是——他们怎么做到的。在Unity里哪怕新建一个空白窗口都很困难,我完全无法想象要怎么样才能实现这个功能。
@DemonLord-:
最近由 Sailing航 修改于:2025-12-10 09:17:00unity渲染的texture通过dll暴露的接口set到dll端,
dll端只负责用gl相关的函数把texture展示出来就行
至于创建空白窗口之类的则是统一通过dll端调用windows native api
dll只需要暴露给c#诸如 创建窗口,移动窗口,销毁,放大缩小的接口就行
之后就可以在c#里去做所有操作了
举例子 dll暴露如下接口给c#:
my_set_texture(void*pixels)
my_window_init
my_window_destroy
就可以自由在unity里写逻辑了
最终的结构是
unity渲染多个camera target->把每个target最终渲染的texture序列化成像素信息传递给dll->dll把这些像素画在自己管理的窗口里
@Sailing航:十分感谢您的指教!
我以前使用过system.Windows.forms.dll在mono脚本里直接创建新的窗口,但是我发现用这种办法创建新的窗口会直接崩溃。按照您说的,看起来应该是额外去编写一个dll来调用api才对,节奏医生估计也是这样做的。
我在编写dll方面完全没有任何经验,之前也不敢去尝试,也因此才觉得这个效果不可能实现。
太感谢您的指点了,我得去研究下dll的编写了。
@DemonLord-:不客气,欢迎一起交流技术