将 Flash 游戏转换为 HTML5 时需要了解什么?
已发表: 2022-03-10随着 HTML5 使用的兴起,许多公司开始重做他们最流行的标题,以摆脱过时的 Flash 并使他们的产品符合最新的行业标准。 这种变化在赌博/赌场和娱乐行业尤为明显,并且已经发生了几年,因此已经转换了相当多的游戏选择。
不幸的是,在浏览 Internet 时,您经常会偶然发现看似仓促的工作示例,这会导致最终产品的情人质量。 这就是为什么游戏开发人员最好花一些时间来熟悉 Flash 到 HTML5 转换的主题,并了解在开始工作之前要避免哪些错误。
选择 JavaScript 而不是 Flash 的原因之一,除了明显的技术问题外,还有一个事实是,将游戏设计从 SWF 更改为 JavaScript 可以产生更好的用户体验,从而使其具有现代感。 但是怎么做呢? 您是否需要专门的 JavaScript 游戏转换器来摆脱这种过时的技术? 好吧,Flash 到 HTML5 的转换可能是小菜一碟——这里是如何处理它。
推荐阅读: HTML5游戏设计原理
如何提升 HTML5 游戏体验
将游戏转换到另一个平台是改进游戏、解决问题和增加受众的绝佳机会。 以下是一些可以轻松完成且值得考虑的事情:
支持移动设备
从 Flash 转换为 JavaScript 可以覆盖更广泛的受众(移动设备用户); 对触摸屏控制的支持通常也需要在游戏中实现。 幸运的是,Android 和 iOS 设备现在都支持 WebGL,因此通常可以轻松实现 30 或 60 FPS 的渲染。 在许多情况下,60 FPS 不会造成任何问题,随着移动设备的性能越来越高,问题只会随着时间的推移而改善。- 提高性能
在比较 ActionScript 和 JavaScript 时,后者比第一个更快。 除此之外,转换游戏是重新审视游戏代码中使用的算法的好时机。 通过 JavaScript 游戏开发,您可以优化它们或完全去除原始开发人员留下的未使用代码。 - 修复错误并改进游戏玩法
让新开发人员查看游戏的源代码有助于修复已知错误或发现新的和非常罕见的错误。 这将使玩游戏对玩家的刺激性降低,这将使他们在您的网站上花费更多时间并鼓励尝试您的其他游戏。 - 添加网络分析
除了跟踪流量之外,网络分析还可用于收集有关玩家在游戏中的行为方式以及他们在游戏过程中卡在哪里的知识。 - 添加本地化
这将增加观众人数,并且对于来自其他国家的孩子玩您的游戏很重要。 或者你的游戏不是英文的,你想支持那种语言?
为什么跳过游戏内 UI 的 HTML 和 CSS 会提高游戏性能
当谈到 JavaScript 游戏开发时,将 HTML 和 CSS 用于游戏中的按钮、小部件和其他 GUI 元素可能很诱人。 我的建议是在这里要小心。 这是违反直觉的,但实际上利用 DOM 元素在复杂游戏中的性能较差,而这在移动设备上更重要。 如果您想在所有平台上实现恒定的 60 FPS,则可能需要从 HTML 和 CSS 中退出。
通过使用常规图像( Phaser.Image
类),利用.crop
属性进行修剪,利用Phaser.Text
类进行简单操作,可以在 Phaser 中轻松实现非交互式 GUI 元素,例如生命值条、弹药条或分数计数器文本标签。
可以使用内置的Phaser.Button
类来实现按钮和复选框等交互元素。 其他更复杂的元素可以由不同的简单类型组成,如组、图像、按钮和文本标签。
注意:每次实例化 Phaser.Text 或 PIXI.Text 对象时,都会创建一个新纹理来渲染文本。 这个额外的纹理会破坏顶点批处理,所以注意不要有太多。
如何确保已加载自定义字体
如果要使用自定义矢量字体(例如 TTF 或 OTF)渲染文本,则需要确保在渲染任何文本之前浏览器已经加载了该字体。 Phaser v2 没有为此提供解决方案,但可以使用另一个库:Web Font Loader。
假设您有一个字体文件并在您的页面中包含 Web Font Loader,那么下面是一个如何加载字体的简单示例:
制作一个将由 Web Font Loader 加载的简单 CSS 文件(您不需要将其包含在 HTML 中):
@font-face { // This name you will use in JS font-family: 'Gunplay'; // URL to the font file, can be relative or absolute src: url('../fonts/gunplay.ttf') format('truetype'); font-weight: 400; }
现在定义一个名为WebFontConfig
的全局变量。 像这样简单的事情通常就足够了:
var WebFontConfig = { 'classes': false, 'timeout': 0, 'active': function() { // The font has successfully loaded... }, 'custom': { 'families': ['Gunplay'], // URL to the previously mentioned CSS 'urls': ['styles/fonts.css'] } };
到此结束,请记住将您的代码放在上面显示的“活动”回调中。 就是这样!
如何让用户更容易保存游戏
要在 ActionScript 中持久存储本地数据,您将使用 SharedObject 类。 在 JavaScript 中,简单的替换是 localStorage API,它允许存储字符串以供以后检索,在页面重新加载时幸存。
保存数据非常简单:
var progress = 15; localStorage.setItem('myGame.progress', progress);
请注意,在上面的示例中, progress
变量是一个数字,将被转换为字符串。
加载也很简单,但请记住,检索到的值将是字符串,如果它们不存在,则为null
。
var progress = parseInt(localStorage.getItem('myGame.progress')) || 0;
在这里,我们确保返回值是一个数字。 如果不存在,则将 0 分配给progress
变量。
您还可以存储和检索更复杂的结构,例如 JSON:
var stats = {'goals': 13, 'wins': 7, 'losses': 3, 'draws': 1}; localStorage.setItem('myGame.stats', JSON.stringify(stats)); … var stats = JSON.parse(localStorage.getItem('myGame.stats')) || {};
在某些情况下 localStorage 对象不可用。 例如,当使用file://
协议或在私有窗口中加载页面时。 您可以使用 try 和 catch 语句来确保您的代码将继续工作并使用默认值,如下例所示:
try { var progress = localStorage.getItem('myGame.progress'); } catch (exception) { // localStorage not available, use default values }
要记住的另一件事是,存储的数据是按域保存的,而不是按 URL 保存的。 因此,如果存在多个游戏托管在单个域上的风险,那么保存时最好使用前缀(命名空间)。 在上面的示例中, 'myGame.'
是这样的前缀,您通常希望将其替换为游戏名称。
注意:如果您的游戏嵌入在 iframe 中,则 localStorage 不会在 iOS 上持续存在。 在这种情况下,您需要将数据存储在父 iframe 中。
如何利用替换默认片段着色器
当 Phaser 和 PixiJS 渲染你的精灵时,它们使用一个简单的内部片段着色器。 它没有很多功能,因为它是为速度量身定制的。 但是,您可以根据需要替换该着色器。 例如,您可以利用它来检查过度绘制或支持更多的渲染功能。
下面是如何为 Phaser v2 提供您自己的默认片段着色器的示例:
function preload() { this.load.shader('filename.frag', 'shaders/filename.frag'); } function create() { var renderer = this.renderer; var batch = renderer.spriteBatch; batch.defaultShader = new PIXI.AbstractFilter(this.cache.getShader('filename.frag')); batch.setContext(renderer.gl); }
注意:重要的是要记住默认着色器用于所有精灵以及渲染到纹理时。 另外,请记住,对所有游戏中的精灵使用复杂的着色器会大大降低渲染性能。
如何使用默认着色器更改着色方法
自定义默认着色器可用于替换 Phaser 和 PixiJS 中的默认着色方法。
Phaser 和 PixiJS 中的着色通过将纹理像素乘以给定颜色来工作。 乘法总是会使颜色变暗,这显然不是问题; 它与 Flash 着色完全不同。 对于我们的一款游戏,我们需要实现类似于 Flash 的着色,并决定可以使用自定义默认着色器。 以下是此类片段着色器的示例:
// Specific tint variant, similar to the Flash tinting that adds // to the color and does not multiply. A negative of a color // must be supplied for this shader to work properly, ie set // sprite.tint to 0 to turn whole sprite to white. precision lowp float; varying vec2 vTextureCoord; varying vec4 vColor; uniform sampler2D uSampler; void main(void) { vec4 f = texture2D(uSampler, vTextureCoord); float a = clamp(vColor.a, 0.00001, 1.0); gl_FragColor.rgb = f.rgb * vColor.a + clamp(1.0 - vColor.rgb/a, 0.0, 1.0) * vColor.a * fa; gl_FragColor.a = fa * vColor.a; }
此着色器通过向色调添加基色来使像素变亮。 为此,您需要提供所需颜色的底片。 因此,为了获得白色,您需要设置:
sprite.tint = 0x000000; // This colors the sprite to white Sprite.tint = 0x00ffff; // This gives red
我们游戏中的结果如下所示(注意坦克在被击中时会闪烁白色):
如何检查透支以检测填充率问题
也可以利用替换默认着色器来帮助调试。 下面我解释了如何使用这样的着色器检测过度绘制。
当屏幕上的许多或所有像素被多次渲染时,就会发生过度绘制。 例如,许多对象占据相同的位置并在另一个上渲染。 GPU每秒可以渲染多少像素被描述为填充率。 现代桌面 GPU 对于通常的 2D 用途具有过高的填充率,但移动 GPU 的速度要慢得多。
有一种简单的方法可以找出屏幕上每个像素被写入多少次,方法是将 PixiJS 和 Phaser 中的默认全局片段着色器替换为这个:
void main(void) { gl_FragColor.rgb += 1.0 / 7.0; }
此着色器使正在处理的像素变亮。 数字 7.0 表示需要多少次写入才能将像素变为白色; 您可以根据自己的喜好调整此数字。 换句话说,屏幕上较亮的像素被写入了几次,而白色像素至少被写入了 7 次。
此着色器还有助于查找由于某种原因仍被渲染的“不可见”对象和周围有过多透明区域需要剥离的精灵(GPU 仍需要处理纹理中的透明像素)。
左图显示了玩家如何看待游戏,而右图显示了将过度绘制着色器应用于同一场景的效果。
为什么物理引擎是你的朋友
物理引擎是负责模拟物理体(通常是刚体动力学)及其碰撞的中间件。 物理引擎模拟 2D 或 3D 空间,但不能同时模拟两者。 典型的物理引擎将提供:
- 通过设置速度、加速度、关节和马达来移动物体;
- 检测各种形状类型之间的碰撞;
- 计算碰撞响应,即两个对象在碰撞时应该如何反应。
在 Merixstudio,我们是 Box2D 物理引擎的忠实拥护者,并曾多次使用它。 有一个 Phaser 插件可以很好地用于此目的。 Box2D 也用于 Unity 游戏引擎和 GameMaker Studio 2。
虽然物理引擎会加速您的开发,但您必须付出代价:降低运行时性能。 检测碰撞和计算响应是一项 CPU 密集型任务。 您可能会被限制在手机场景中使用几十个动态对象,或者面临性能下降以及帧速率降低到 60 FPS 以下的情况。
图像的左侧部分是游戏场景,而右侧显示了相同的场景,顶部显示了 Phaser 物理调试叠加层。
如何从.fla文件中导出声音
如果您在.fla文件中有 Flash 游戏音效,则无法从 GUI 导出它们(至少在 Adobe Animate CC 2017 中不能),因为缺少用于此目的的菜单选项。 但是还有另一种解决方案——一个专门的脚本可以做到这一点:
function normalizeFilename(name) { // Converts a camelCase name to snake_case name return name.replace(/([AZ])/g, '_$1').replace(/^_/, '').toLowerCase(); } function displayPath(path) { // Makes the file path more readable return unescape(path).replace('file:///', '').replace('|', ':'); } fl.outputPanel.clear(); if (fl.getDocumentDOM().library.getSelectedItems().length > 0) // Get only selected items var library = fl.getDocumentDOM().library.getSelectedItems(); else // Get all items var library = fl.getDocumentDOM().library.items; // Ask user for the export destination directory var root = fl.browseForFolderURL('Select a folder.'); var errors = 0; for (var i = 0; i < library.length; i++) { var item = library[i]; if (item.itemType !== 'sound') continue; var path = root + '/'; if (item.originalCompressionType === 'RAW') path += normalizeFilename(item.name.split('.')[0]) + '.wav'; else path += normalizeFilename(item.name); var success = item.exportToFile(path); if (!success) errors += 1; fl.trace(displayPath(path) + ': ' + (success ? 'OK' : 'Error')); } fl.trace(errors + ' error(s)');
如何使用脚本导出声音文件:
- 将上面的代码另存为.jsfl文件在您的计算机上;
- 使用 Adobe Animate 打开一个.fla文件;
- 从顶部菜单中选择“命令”→“运行命令”,然后在打开的对话框中选择脚本;
- 现在弹出另一个对话框文件,用于选择导出目标目录。
并做了! 您现在应该在指定目录中有 WAV 文件。 剩下要做的就是将它们转换为例如 MP3、OGG 或 AAC。
如何在 Flash 中使用 MP3 到 HTML5 转换
好的旧 MP3 格式又回来了,因为一些专利已经过期,现在每个浏览器都可以解码和播放 MP3。 这使得开发更容易一些,因为最终不需要准备两种单独的音频格式。 例如,以前您需要 OGG 和 AAC 文件,而现在 MP3 就足够了。
尽管如此,关于 MP3,您需要记住两件重要的事情:
- MP3 需要在加载后解码,这可能很耗时,尤其是在移动设备上。 如果您在所有资源加载后看到暂停,则可能意味着 MP3 正在解码;
- 无缝播放循环播放的 MP3 有点问题。 解决方案是使用 mp3loop,您可以在 Compu Phase 发布的文章中阅读有关它的内容。
那么,为什么要将 Flash 转换为 JavaScript?
如您所见,如果您知道该怎么做,Flash 到 JavaScript 的转换并非不可能。 有了知识和技能,您就可以停止使用 Flash 并享受用 JavaScript 创建的流畅、有趣的游戏。 不要试图修复 Flash - 在每个人都被迫这样做之前摆脱它!
想了解更多?
在本文中,我主要关注 Phaser v2。 但是,现在有一个更新版本的 Phaser 可用,我强烈建议您检查一下,因为它引入了许多新鲜、酷炫的功能,例如多相机、场景、tilemaps 或 Matter.js 物理引擎。
如果你足够勇敢并且想在浏览器中创造真正非凡的东西,那么 WebGL 是从头开始学习的正确选择。 与各种游戏构建框架或工具相比,它的抽象级别较低,但即使您从事 2D 游戏或演示,也可以实现更高的性能和质量。 在学习 WebGL 基础知识时可能会发现许多有用的网站是 WebGL Fundamentals(使用交互式演示)。 除此之外,要了解有关 WebGL 功能采用率的更多信息,请查看 WebGL Stats。
永远记住,没有太多的知识——尤其是在游戏开发方面!