为了挽救童年的记忆,开发者决定用古老的编程方式:用高清Flash重制游戏对Flash的支持结束标志着一个时代的结束。快进两年,Adobe已从其官方网站上删除所有早期版本的FlashPlayer存档,并阻止运行基于Flash的内容。微软还终止了对Adob??eFlashPlayer的支持,并禁止它在任何微软浏览器上运行。AdobeFlashPlayer组件在2021年7月通过WindowsUpdate被永久删除。当Flash下架时,在世界的某个角落,这位“老同志”还在炫耀自己的热情。Hapland是一款2005年推出的Flash益智游戏,也是很多人的童年回忆。在游戏中,玩家需要借助这个世界的人的帮助,想办法开启关卡,同时又不让自己被怪物吃掉或者被地雷炸死。这个游戏的图形是在Flash中绘制的,代码是在Flash中编写的,所有的动画都是在Flash时间轴中完成的。可以这样理解:这款游戏“骨子里有Flash”。作为游戏开发行业的一员,RobinAllen发现人们似乎特别喜欢Hapland游戏,因此他想对这款基于Flash的游戏的Steam版本进行一些修复,包括绘制更好的图形和提高帧率以60帧/秒。并添加一些额外的“秘密”等等。这个时候怎么办?作者详细描述了实验过程。一些失败的经历失败尝试1:我首先尝试的是让Flash将游戏导出为可执行文件,但失败了,因为它的性能和2005年一样糟糕。我想做一些以当代帧率运行的东西.我想摆脱FlashPlayer。失败尝试2:其次,我花了太多时间摆弄Adob??eAIR(Flash桌面运行时)和Starling(一个在GPU上绘制Flash场景的库)。最后我放弃了这个,部分原因是AIR有很多问题而且很糟糕,但也因为我不想以奇怪的Adob??e结果结束;我想拥有自己的东西,可以做我想做的事。例如,如果我想迁移到Linux怎么办?前进的方向很明显:我必须制作自己的Flash播放器。计划以下是Hapland的运作方式。这里有一个精灵树,在Flash中,动画精灵可以将代码附加到播放箭头到达那里时运行的特定帧。Hapland经常使用它。游戏角色的行进路径是一个时间轴很长的动画。角色经常有逐帧动作,比如关门后打开门,或者在雷区没有爆炸的情况下触发它。时间轴中的小“a”是帧动作。幸运的是,.fla文件只是XML。我只需要解析它,将相关数据导出为简单的自定义格式,然后编写一个播放器来读取它、绘制场景、处理输入并运行动画。Hapland仍然是一个Flash项目,在Flash编辑器中编写和维护;只有FlashPlayer会被替换。栅格化矢量Flash确实支持栅格图形,但实际上是为矢量图形设计的。这就是Flash电影加载速度很快的原因,即使是通过拨号连接也是如此。所有Hapland图形都是矢量图形。另一方面,GPU不太喜欢绘制矢量图形,而更喜欢大批量的纹理三角形。所以,我需要栅格化这些矢量。我决定离线光栅化它们并将光栅文件打包到游戏中。在游戏运行时将它们光栅化并变成这个微小的可执行文件会很有趣,但我不想有那些额外的移动部件。我喜欢在我的开发机器上运行尽可能多的代码,这样我就可以随时关注它。Flash以XML格式存储矢量图形。您可能会争辩说XML对于图形数据来说不是一个糟糕的选择,但您不是Macromedia的产品经理。检查一下:在.fla文件中看到的矢量数据。我不是在抱怨,它只是让我的工作更轻松。虽然我无权访问规范,但将其光栅化不是问题。自PostScript以来,矢量图形的Bezier曲线模型已经无处不在。所有这些API的工作方式都相同。经过反复试验,我编写了一个程序来解析这些形状定义并使用Mac的CoreGraphics库将它们呈现为PNG。CoreGraphics是一个有问题的选择。我选择它是因为我在Mac上工作并且有很多依赖项。但它确实成功了,所以我总是不得不在Mac上光栅化图形,甚至是Windows版本。如果再次这样做,我可能会选择跨平台库。渲染完这些PNG后,exporter会把它们组装成图集吗?不,它只是按高度对所有内容进行排序,然后像文档中的文本一样逐行排列。它远非最佳,但已经足够好了。为简单起见,图集为2048×2048像素,这是OpenGL3.2实现必须支持的最小纹理大小。Hapland3中的图例集。栅格化形状非常慢,因此为了保持合理的构建时间,我需要跳过渲染未更改的内容。Flash使用的压缩XML格式确实有每个文件的最后修改字段,但Flash似乎没有正确使用它们,因此您不能依赖它们。相反,我只是对每个形状的XML进行哈希处理,并且只有在它发生变化时才进行重建。即使那样也会失败,因为Flash有时喜欢在未更改的对象中重新排列XML标记,但同样,它已经足够好了。在汇编器中编写二进制文件导出器,以将动画数据写入自定义二进制格式。它只是逐帧遍历时间轴,并写出每一帧的所有更改。这里的想法是写入汇编列表而不是直接写入二进制文件,我喜欢这样。没有CPU指令,只有数据,这使得调试更容易,因为我可以查看汇编文件以查看生成的内容,而不是在十六进制编辑器中浏览字节。output.bin139249EC:BD31E8FF09DDBEDE:C95A1D363FC04E31:52FD41C68B5DC020:191F5F1F54978C27:341F30EAA9A9E055989:ABC5F24:3A98FDB9DE15F2D4:2AB7412C4E9D37D9:E2134B01363F4008:AC3CFF84E9AEC52C:112F69CF63CE85C15B15:1A76:779971B0606EC4C7:731FEA1F310D0C39:B0867042Output.asm;左侧时间线_132:;---左侧,第0帧---.frame_0:;---左侧,第0帧,第22层---dbQuaddd0.152926,0.162029,0.184475,1.000000;colordd799.599976,-20.950001dd799.599976,556.650024dd46.000000,556.650024dd46.000000,-20.950001;---左侧,第0帧,第21层---;instanceofshape[LeftSide][WallShadows][Frame0]ddShapedw1560你更愿意调试哪个?我可以让导出器将字节写入一个文件并将单独的文本列表写入另一个文件而不使用汇编器,但我没有这样做,因为:1)汇编器已经存在;2)我没有必要调试它们;3)他们支持标签。出口商的其余部分大多不够有趣;它只是遍历树并做变换矩阵、颜色效果等操作,然后继续游戏程序本身。我选择用C++编写这个,因为我已经知道它并且新事物让我害怕。场景图Hapland非常适合场景图。这是Flash使用的模型,而Hapland就是围绕它设计的,因此尝试使用不同的模型是没有意义的。我将场景作为节点树存储在内存中,每个节点都有一个变换,自己绘制并接受鼠标点击。每个具有自己行为的游戏对象都是其自己类的实例,派生自Node.js。“面向对象”现在在游戏开发圈子里并不流行,但我使用的是Flash,所以显然不关心这个。存在Hapland使用的Flash功能,例如颜色变换和蒙版。我没有实现像Flash这样的任意蒙版,我只是实现了矩形剪辑并编辑了我所有的图形,所以所有的蒙版都是矩形。框架脚本Hapland的几乎所有逻辑都包含在附加到时间轴框架的ActionScript中。如何导出所有这些东西?我不想在我的游戏中包含ActionScript解释器。一个简单的框架动作。最后,我们使用了一个小技巧,我的导出器从每一帧读取ActionScript并应用一堆正则表达式来尝试将其转换为C++。例如,crate.lid.play()可能变成crate()→lid()→play();。这两种语言在句法上非常相似,这对于许多更简单的框架动作来说工作得很好,但它仍然留下了相当多的错误代码,除了手动重写所有剩余的框架动作之外别无他法。与C++中的所有框架脚本一样,它们在构建时被提取并成为每个符号的Node子类上的方法。还会生成一个调度方法,以便在正确的时间调用,如下所示:voidtick()override{switch(currentFrame){case1:_frame_1();休息;案例45:_frame_45();休息;案例200:_frame_200();休息;}}最后要指出的是,脚本系统最终成为某种静态类型,这有点痛苦。游戏输出的最终游戏对象如下所示:structBigCrate:Node{BigCrateLid*lid(){return(BigCrateLid*)getChild("lid");}BigCrateLabel*label(){return(BigCrateLabel*)getChild("label");}voidswingOpen(){...}voidsnapShut(){...}voidburnAway(){...}};因此,即使一切仍然是大量的自动字符串名称查找,类型安全的单板将防止您在错误的对象上调用错误的函数,从而使您免于遇到动态语言中那种烦人的错误。AspectRatioHD重置游戏会遇到屏幕拉伸的问题。很多原来的Flash游戏都是网页游戏,连全屏运行的能力都没有,所以只是采用了设计师喜欢的宽高比,大多在3:2左右。现在最常见的纵横比似乎是16:9,16:10在笔记本电脑上也很流行。我希望游戏在其中任何一个上看起来都不错,没有任何黑条或拉伸。做到这一点的唯一方法是从原件上剪下零件,或向其添加零件。于是,我为游戏画面画了两个矩形,一个比例为16:9,一个比例为16:10。然后游戏根据屏幕的宽高比在它们之间进行插值,并使用插值的矩形作为视图边界。只要所有重要的游戏元素都在这些矩形的交叉点内,并且它们的公共边界矩形不超出场景的边缘,这就可以正常工作。Hapland2的16:10和16:9帧,而不是原来的3:2。颜色空间问题经过一些测试,我发现Flash在感知空间而不是线性空间中进行alpha混合和颜色转换。这在数学上是可疑的,但另一方面我们也应该知道很多绘图程序都是这样工作的,你希望你的消费级工具按照人们期望的方式运行,尽管这对数学家来说是个问题.得罪。但从根本上说,这是错误的!它可能会导致诸如抗锯齿之类的问题。当您光栅化矢量图形并要求抗锯齿输出时,光栅化器将输出一个alpha值,即所谓的“覆盖值”。这意味着如果给定像素被矢量形状半覆盖,则该像素将以alpha=0.5输出。但在Flash中,当某些东西的alpha为0.5时,这意味着它在感知上处于前景色和背景色之间的中间位置。这根本不是一回事!绘制在不透明黑色像素之上的半覆盖白色像素不应是可感知的50%灰色。这不是光的工作原理,也不是矢量光栅化的工作原理。光栅器不能在不知道背景颜色的情况下说“这个像素应该感知背景颜色和前景颜色之间的xx%”。在感知(sRGB)空间中完成混合。上衣:黑色透明白色;中间:白色透明黑色;底部:灰色在线性(物理上准确)空间中完成的相同混合。请注意,50%的覆盖率看起来与50%的灰色不同。因此,我们的抗锯齿光栅化形状使用一个alpha定义,而我们的Flash导出使用另一个定义来实现alpha透明度、渐变和颜色变换。但是我们的渲染管道中只有一个alpha通道。那么渲染器应该如何解释alpha值呢?如果它将它们解释为感知混合因素,则半透明对象看起来是正确的,但所有事物的抗锯齿边缘看起来都是错误的。如果它将它们解释为覆盖率值,则反之亦然。有些东西看起来总是不对劲!这里我只看到两个严肃的解决方案:1)设置两个alpha通道,一个用于叠加,一个用于感知混合;2)光栅化所有没有AA的形状,将所有内容绘制到一个非常大的帧缓冲区,然后通过过滤将其缩小。我必须承认,这些想法都没有被实践过。这些半透明的东西在Flash和游戏中看起来不对劲,我只是逐渐调整图形直到游戏看起来不错。Flash中的透明对象从来都不是我想要的那样,但数量不多,也没什么大不了的。为了确保其他一切都正确,我制作了一个“颜色测试”图形,其中包含一堆不同强度的颜色、10的色调旋转效果等,让游戏显示它,并确保它在Flash中正常工作。这变成了比较颜色的问题。帧速率最初的Flash游戏标称帧速率为24FPS,但实际上它们以FlashPlayer想要的任何帧速率运行。使用Flash,您可能要求24FPS并获得15FPS,或者要求30FPS并突然获得24FPS,这似乎一点也不严格。我想以60FPS的速度重制这款游戏,这意味着要考虑Hapland创建它时期望以24FPS左右的速度运行的事实。Flash的动画工具是基于离散的帧而不是连续的时间。我首先让导出器将所有帧加倍,为每个时间轴帧导出两帧,这直接将24FPS增加到48FPS,但仍然不是60,所需的动画仍然快25%。解决方法是老式的手动工作:完全浏览游戏,然后手动向现在看起来太快的动画添加额外的帧。在这一点上,我们已经得到了Hapland游戏的一个非常好的C++转换,肯定会在现代计算机上运行至少一两年。但我就是无法摆脱我应该尝试提供一些额外价值的感觉,因此添加新的价值是不可避免的。除了重新绘制大量旧图形和动画外,我还进行了一些重大更改。及时节省我认为Hapland3需要不那么压倒性。这个游戏的关卡很长,有很多地方你会死,然后必须重新开始,也许这在2006年很有趣,但我们现在是成年人了,我们没有时间做那个了。保存状态是模拟器应该有的,如果你按下“保存状态”,它会通过将控制台的内存转储到一个文件来记录当前游戏的整个状态。然后,如果您搞砸了,请按“加载状态”,然后您将回到您要重试的位置附近。在原始Flash游戏中实现保存状态是不可行的,因为Flash不让程序员访问其整个状态。但由于这次我使用的都是我自己的代码,所以这是可能的。我有一个叫做Zone的东西,它只是一个分配器,将它的所有内存分配到一个固定大小的块中。所有场景节点都分配在当前区域内。为了实现保存和恢复,我只需要两个区域,活动区域和一个单独的“保存状态区域”。为了保存状态,我将活动区域memcpy到保存状态区域。要加载状态,我会以另一种方式返回到memcpy。重复关卡哈普兰游玩的时间并不特别长,虽然一共有三个,但我们总是想多给玩家几个小时的游戏时间。所以我决定给每个游戏一个“SecondQuest”——原始关卡的修改版,布局和谜题略有不同。制作这样的SecondQuest比制作一款全新游戏的工作量要少,但仍能带来一些额外的价值。创建SecondQuest意味着我需要重新开始Flash益智游戏开发,这是大约15年来的第一次,老实说感觉很好。复古的FlashUI很棒,按钮有棱角,图标很真实,空间利用的很好。使用旧式UI让我觉得自己像一位考古学家,正在发现某种被遗忘的罗马技术。失传的UI设计艺术,它很简洁。这是什么魔法?尽管Flash有问题、速度慢,并且缺少一些极其基本的功能,但我基本上不讨厌使用它,尽管现代应用程序更舒适。为了防止第二个任务看起来和第一个太相似,他们需要有新的背景,整个场景也被水平翻转了。哈普兰3。Hapland3的第二个任务。音乐对于BGM,我为每个游戏制作了快速的环境配乐,使用我自己硬盘中的内容并制作了一些额外的音乐。有一次在日本度假时,我无缘无故地在山顶上进行了野外录音,能用它来做点什么真是太好了。我在网上找了个音乐家做片头曲,自己录了一些吉他和弦做片尾曲,被效果淹没了,不能说我学吉他不好。在工具上,我根据音乐使用Logic或Live。我发现Logic更适合录音,Live更适合声音设计。成就系统在Steam上。玩家总是喜欢看成就。将成就系统上传到Steam是一件痛苦的事情,你不能只定义一个列表并将其提供给他们的命令行工具,你必须费力地通过Steam合作伙伴站点缓慢、混乱的PHP框架,然后通过一。似乎如果你是一个重要的大型游戏工作室,你就不必忍受这个,他们为你提供了一个批量上传工具,但我显然不是其中之一。所以我查看了它发出的HTTP调用,保存了我的登录cookie,并编写了我自己的。经过几次修改后,我确定了一组适度的成就:一个用于完成每个Hapland游戏,一个用于每个SecondQuest,两个用于解锁更大的秘密。任何没人能发现的愚蠢、晦涩的秘密都无济于事,你必须对发生的事情感到高兴。SteamworksUI下的成就系统。Notarization的问题虽然我主要在自己的Mac上开发游戏,但Apple在开发过程中发明了“Notarization”。如果你在新版本的MacOS上运行任何应用程序,它都会向Apple发出网络请求,询问该应用程序的开发者是否向Apple支付年费。如果开发者不缴纳年费,MacOS会弹出一个对话框,强烈暗示该应用已损坏并拒绝启??动。因此,Windows将是游戏的第一个,也许是唯一的发布平台。LibrariesUsed对于最终交付给最终用户的软件,我们通常希望将依赖性保持在最低限度,但是使用一些高质量的软件也是必要的。除了OpenGL和标准操作系统之外,这里是HaplandTrilogy可执行文件最终链接到的库的完整列表:SteamSDKcute_soundstb_vorbisstb_image有什么,所以有时它会让你想说:嘿,看我做了什么!
