Skip to content
云风 edited this page Dec 16, 2025 · 3 revisions

Welcome to the soluna wiki! google translate this page into English

soluna 是一个基于 sokol 的 2D 游戏引擎。它以 Lua 为编程语言,整合了 ltask 作为多线程框架。sokol + lua 是其名字的由来。

构建

soluna 目前支持 Windows/macOS/Linux/Web ,可以使用 luamake 构建各个平台的版本。在 Windows 平台,也可以使用 GNU Make 构建:默认使用 mingw 环境,可以用 make CC=cl 切换为 msvc 环境。

在构建完毕后,引擎所有相关代码和资源都会打包到一个执行文件中,没有额外的数据文件依赖。你也可以直接下载预编译版本

使用

非浏览器环境

执行引擎执行文件可以运行游戏。

游戏由若干 lua 代码文件和相关的资产文件(如数据、图片等)构成。可以是一个本地目录,也可以是一个 zip 包。引擎运行时,默认检查当前目录下是否有 main.zip ,若有则将其作为需要启动的游戏;若没有则将当前目录作为游戏所在地。也可以从命令行传入 zip 包文件名改变需要启动的游戏,或传入一个游戏环境文件指定游戏。

游戏环境

游戏环境指游戏运行的若干配置,其默认值在 src/data/settingdefault.dl 被打包到引擎执行文件中。可以在游戏包中放入一个环境文件 main.game 覆盖这些默认配置。或在命令行中指定游戏环境文件名。

也可以在命令行中通过 key=value 指定需要改写的配置项。

entry

入口文件名是游戏启动时第一个被加载运行的 lua 代码文件。默认为 main.lua ,即引擎默认会在游戏包中查找名为 main.lua 的文件运行它。如果引擎在运行时找不到这个 entry (入口)文件,则会报告 Can't load entry main.lua 并退出。

如果命令行指定 soluna test/window.game 启动,则会以 test/window.game 为环境启动游戏(并将当前目录设定为 test)。因为在 test/window.game 中指定了 entry:window.lua ,所以 test/window.lua 就成为了游戏入口。

也可以通过命令行指定 soluna entry=test/window.lua 也可以以它为入口文件启动游戏。这里的命令行参数 entry=test/window.lua 指将 entry 的默认值 main.lua 修改为 test/window.lua

浏览器环境

  1. 使用 soluna (Windows/macOS/Linux) 进行游戏开发
  2. 将游戏源代码 (lua) 打包成 main.zip
  3. 创建 index.html, 引用 soluna.js, soluna.wasm 和 main.zip 部署
  4. 补充一个细节: soluna wasm 使用了 shared buffer array(pthread) 因此要求部署页面启用跨源隔离(COOP & COEP), 具体可见 https://emscripten.org/docs/porting/pthreads.html#pthreads-support. 简单起见, 你也可以像 deepfuture 的 gh pages 那样引用一个 service worker 自动拦截请求: https://github.com/cloudwu/deepfuture/blob/d89b8577e4dae8fc75fefd4a118de45aa304e762/.github/assets/index.html#L252

index.html 可以自由编写, 由开发人员自行决定。一般来说需要做的工作有: 1. 定义 Module; 2. 引用 soluna.js

以下是一个最简单的 demo :

(在这个例子中, 我们将游戏源码和字体资源分装成两个 zipfile, 这样的好处是, 字体一般不需要更新, 而游戏源码可能会发生变更。可以有效的利用浏览器/cdn 缓存资源。最终他们会合并成一个 fs)

(更多的关于 Module 的说明请参考: https://emscripten.org/docs/api_reference/module.html)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Soluna Web (Lua test)</title>
  <style>
    body { margin: 0; background: #000; }
    canvas { display: block; width: 100vw; height: 100vh; }
  </style>
</head>
<body>
  <canvas id="canvas" oncontextmenu="event.preventDefault()"></canvas>

  <script>
  var Module = {
    arguments: ["zipfile=/data/main.zip:/data/font.zip"], // 传递给 soluan 的参数, 告知游戏代码位于 /data/ 下, 多个 zip 最终会被合并成一个 zip
    canvas: document.getElementById('canvas'),
    preRun: [function () { // 前置钩子, 确保资源下载完成了再开始运行 soluna
      const runDependency = 'load-main-zip'; // 添加前置依赖
      const fontDependency = 'load-font';

      Module.FS_createPath('/', 'data', true, true); // 在 memfs 中创建 /data 目录

      Module.addRunDependency(runDependency);
      fetch('/main.zip') // 从后端下载 main.zip
        .then(function (response) {
          if (!response.ok) {
            throw new Error('HTTP ' + response.status + ' while fetching main.zip');
          }
          return response.arrayBuffer();
        })
        .then(function (buffer) { // 将下载的资源载入 memfs
          const data = new Uint8Array(buffer);
          Module.FS.writeFile('/data/main.zip', data, { canOwn: true });
          console.log('main.zip loaded:', Module.FS.readdir('/data'));
        })
        .catch(function (err) {
          console.error('Failed to load main.zip', err);
          throw err;
        })
        .finally(function () {
          Module.removeRunDependency(runDependency); // 资源下载完成, 解决前置依赖
        });
      Module.addRunDependency(fontDependency);
      fetch('/font.zip')
        .then(function (response) {
          if (!response.ok) {
            throw new Error('HTTP ' + response.status + ' while fetching font.zip');
          }
          return response.arrayBuffer();
        })
        .then(function (buffer) {
          const data = new Uint8Array(buffer);
          Module.FS.writeFile('/data/font.zip', data, { canOwn: true });
          console.log('font.zip loaded:', Module.FS.readdir('/data'));
        })
        .catch(function (err) {
          console.error('Failed to load font.zip', err);
          throw err;
        })
        .finally(function () {
          Module.removeRunDependency(fontDependency);
        });
    }],
    onRuntimeInitialized: function () {
      console.log('Soluna runtime ready');
    },
    onExit: function (status) {
      console.log('Program exited with status', status);
    },
    onAbort: function (what) {
      console.error('Program aborted:', what);
    },
    print: console.log,
    printErr: console.error
  };
  </script>
  <script src="soluna.js"></script> <!-- 引用 soluna.js 它会自动加载 soluna.wasm -->
</body>
</html>

Clone this wiki locally