唠唠闲话

随着 Adobe Flash Player 在 2020 年底停止更新和支持,那些曾经陪伴我们成长的经典 Flash 游戏和动画似乎注定要被时代遗忘。从 4399 小游戏到 QQ 宠物,这些曾经的网络文化标志物,都因为 Flash 的消逝而逐渐淡出人们的视野。像 Q 宠这样在 2018 年就已经停服的游戏,许多人对它们的怀念之情愈加浓厚。而随着人工智能的发展,虚拟现实的兴起,相信未来会有更多的人希望重温这些经典。但现在,借助 Ruffle 开源项目,我们有机会让这些经典的 Flash 作品再次闪耀。

Ruffle 教程

Ruffle 是什么?

Ruffle 是一个基于 Rust 开发的开源项目,旨在无缝地在所有现代操作系统和浏览器上运行 Flash 内容,无需任何额外操作。通过 Ruffle,无论是经典游戏还是动画,都可以在现代环境中运行。

Ruffle 是一个非常活跃的开源项目,当前已有 11473 个 commit,162 个贡献者,且每天都在更新,对 Flash 的支持也越来越完善。

插件客户端

无论是 Windows、macOS(支持 M1/M2 芯片)还是 Linux,都可以在 Ruffle 官网 找到并下载相应的客户端。不过对于 Windows 用户,如果是在本地运行 Flash,更推荐直接下载 Flash Player。

20240303115425

对于希望在浏览器中直接体验 Flash 内容的用户,Ruffle 也提供了 Chrome、Firefox、Edge 和 Safari 的浏览器扩展插件。安装后,当访问包含 SWF 文件的网站时,Ruffle 会自动介入,使这些内容得以在现代浏览器上运行。

Ruffle 浏览器插件示意图

嵌入网页

如果希望让用户能直接访问带 flash 游戏的网页,且无需安装插件,可以将 Ruffle 添加到网站中。作为示例,我们先写一个简单的 HTML 页面,并引入 Ruffle:

下图 html 文件定义了一个全屏的容器,其中 container 放置游戏页面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>游戏名称</title>
<link rel="stylesheet" href="style.css">
<style>
/* 确保html和body元素占满整个屏幕 */
html, body {
height: 100%;
margin: 0;
overflow: hidden; /* 隐藏滚动条 */
}
/* 使容器和Ruffle播放器全屏和自适应 */
#container {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
#container ruffle-player {
flex-shrink: 0; /* 防止播放器被压缩 */
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="//unpkg.com/@ruffle-rs/ruffle"></script>
<script>
window.addEventListener('load', async () => {
const ruffle = window.RufflePlayer.newest();
const player = ruffle.createPlayer();
const container = document.getElementById('container');
container.appendChild(player);
player.load('game.swf');
});
</script>
</body>
</html>

代码中的这段 JavaScript 代码,用于引入 ruffle 脚本并加载游戏。

1
2
3
4
5
6
7
8
9
10
<script src="//unpkg.com/@ruffle-rs/ruffle"></script>
<script>
window.addEventListener('load', async () => {
const ruffle = window.RufflePlayer.newest();
const player = ruffle.createPlayer();
const container = document.getElementById('container');
container.appendChild(player);
player.load('game.swf');
});
</script>

第一行通过 https://unpkg.com/@ruffle-rs/ruffle 引入脚本,第二段 js 代码创建一个 Ruffle 播放器,并加载游戏,游戏文件为 game.swf

如果网络访问 unpkg.com 慢,可以在下载页 选择 Web Package,下载到网站目录,然后用相对路径加载:

1
<script src="path/to/ruffle.js"></script>

当然,也可以使用自定义的 CDN 资源,比如写个 Nginx 脚本分享 /var/www/cdn 目录下的内容

1
2
3
4
5
6
7
8
9
10
server{
listen 80;
server_name cdn.example.com;
index index.html index.htm;
add_header 'Access-Control-Allow-Origin' *;
location / {
root /var/www/cdn;
try_files $uri $uri/ =404;
}
}

这里加上一行 add_header 'Access-Control-Allow-Origin' *; 以避免跨域问题。

跨域问题

通常,为了加速网站的访问,我们会将游戏文件和 Ruffle 插件托管在如七牛云这样的流量分发平台上,但这可能会触发跨域资源共享(CORS)问题。跨域问题的发生是因为出于安全考虑,浏览器限制了一个源(如你的网站)如何访问另一个源(如 CDN)的资源。如果不正确配置 CORS,浏览器将阻止你的网页请求这些资源,导致游戏或动画无法加载。

例如,将 swf 游戏文件托管七牛云加速访问,如果没有适当设置 CORS,就会看到类似下图的错误:

CORS 错误示例图

为了解决这个问题,需在托管资源的服务器上设置适当的 CORS 策略,比如前边 Nginx 的配置。而对于七牛云,可以参考跨域资源共享设置指南

播放器配置

接下来,我们详细介绍 Ruffle 播放器的一些配置选项,这些选项可以帮助你更好地控制 Flash 内容的加载和显示。

多文件游戏

如果你的 Flash 游戏由多个 swf 文件组成,并且这些文件之间有相互调用关系,你需要在 Ruffle 的配置中设置 base 参数。

例如,如果你的游戏主文件是 game.swf,而它依赖于同一目录下的其他 swf 文件,你可以这样配置 Ruffle:

1
2
3
4
5
6
7
8
9
10
11
12
<script>
window.addEventListener('load', async () => {
window.RufflePlayer.config = {
base: "your/path" // 指定 swf 文件的基础路径
};
const ruffle = window.RufflePlayer.newest();
const player = ruffle.createPlayer();
const container = document.getElementById('container');
container.appendChild(player);
player.load('your/path/game.swf');
});
</script>

这样,Ruffle 会从指定的 your/path 路径加载 game.swf 和它依赖的其他 swf 文件,而不是从当前目录下查找。类似地,如果游戏文件以链接形式获取,前边 base 也需要相应修改。

修改内嵌字体

内嵌字体乱码是 Ruffle 被吐槽较多的一个问题,但去年年底 Ruffle 更新了一个特性:Add defaultFonts and fontSources config options,支持修改字体。

按官方文档的说法,支持对 Flash 默认三类字体的替换:

1
2
3
4
5
6
7
8
9
10
There are 3 default fonts in Flash, _sans _serif and _typewriter - in Ruffle they are normally all set to Noto Sans, but you can replace it such as:

window.RufflePlayer.config = {
fontSources: ["fonts.swf"], // load up fonts here
defaultFonts: {
sans: ["Caveat"], // then replace them here.
// serif: ["Font Name 1", "Font Name 2"], // Multiple fonts can be provided
// typewriter: ["Noto Sans"], // typewriter is normally a monospace font
}
}

具体地,先按照官网的图文教程,将字体转换为 swf 文件,然后在 Ruffle 配置中指定它们。

目前,第一步需单独用 Animate 软件进行操作转化。这里我按教程将“站酷快乐体”转化成了 swf 文件,支持中文字符。可通过如下配置引用,亲测有效:

1
2
3
4
5
6
window.RufflePlayer.config = {
fontSources: ["https://cdn.wzhecnu.cn/adventure/myfont.swf"], // load up fonts here
defaultFonts: {
sans: ["站酷快乐体2016修订版"], // then replace them here.
},
}

效果对比图:

20240303191024

Ruffle 后续会考虑对字体文件的直接支持,比如使用 .ttf 文件。

For now we only support SWFs containing fonts, but in the future we’ll allow actual font files too (such as ttf).

页面放缩

和 flash Player 不同,Ruffle 的右键没有控制缩放的选项,这些需在代码中设置。

举个例子,设置“显示全部”,以及强制缩放:

1
2
3
4
window.RufflePlayer.config = {
scale: "showAll",
forceScale: true
}

这样可以避免主画面太小,以及出现画面外辅助用的素材。

更多设置

以上的播放器设置可以整合到一块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script src="//unpkg.com/@ruffle-rs/ruffle"></script>
<script>
window.addEventListener('load', async () => {
window.RufflePlayer.config = {
fontSources: ["https://cdn.wzhecnu.cn/adventure/myfont.swf"], // load up fonts here
defaultFonts: {
sans: ["站酷快乐体2016修订版"], // then replace them here.
},
scale: "showAll",
forceScale: true,
base: "your/path" // 设置CDN基路径
}
const ruffle = window.RufflePlayer.newest();
const player = ruffle.createPlayer();
const container = document.getElementById('container');
container.appendChild(player);
player.load('your/path/xmxd.swf');
});
</script>

更多选项请参考 Ruffle 使用教程:Using Ruffle。实际上,可以先下载 Ruffle 的客户端,在本地调试后,将客户端里修改的参数填写到配置里。

相关链接

Ruffle 官网:Ruffle
常见问题:FAQ