跳到主要内容

界面包

界面包可以改变故事的外观,并加入你自己的小块界面:状态条、按钮、开场卡片、背景、带地图或笔记本的标签页。世界可以自带界面包,你也可以保存自己的包,用在任何故事里。

界面包是一个放纯文件的文件夹:一份清单、一个可选的样式表,以及带少量 JavaScript 的 HTML 页面。不需要安装,也不需要构建。

界面包不会在 DSH 页面中运行代码。它的脚本运行在不透明来源的沙盒框架里,无法访问页面、你的登录状态或浏览器存储;fetch、图片等普通资源加载只能读取包自带的文件。框架只能通过一组固定的小型接口与页面交流,并且只会被告知玩家角色本来就能知道的内容。界面包仍然是受信任的代码:它可以让自己的框架跳转到外部地址,也可以把收到的数据放进该地址。完整说明见界面包格式参考。

界面包能做什么​

部分是什么是否需要 JavaScript
主题替换 DSH 的部分颜色,浅色和深色各一套。否
皮肤一个样式表,改变故事自身文字的样式:叙述、台词、建议行动。否
状态条(hud)输入框上方的一条,显示地点、时间、状态。是
操作条(dock)紧靠输入框上方的一条,适合放按钮。是
开场卡片(opening)在第一个回合播放之前显示的卡片。是
背景(backdrop)文字后面的一层。是
标签页(tab)聊天旁的一个页面:地图、笔记本、物品栏。是
侧栏页面(sidebar)右侧栏中任意类型的页面,从侧栏的引导页进入:实时地图、状态面板、某样东西的渲染。是

一个包可以包含其中任意几项,多数只用一两项。

右侧栏中的页面​

DSH 的右侧栏以一个引导页开始,里面为它能显示的每样东西列一个入口:工作区文件、终端。如果故事的界面包带有 sidebar 页面,这里会多出一个入口,名称取自页面的 title。选择它,就会在侧栏中打开界面包的页面,上方是我们的一条细栏,带 返回 按钮,可回到引导页。细栏以下全部归界面包所有:页面显示什么、怎么显示,由作者决定。

"surfaces": { "sidebar": { "file": "map.html", "title": "地图" } }

页面与其他页面一样是沙盒页面,得到同样的 rp 对象,所以可以用 rp.watch 实时跟随故事。在侧栏的其他标签页显示时,它仍保持运行,所以地图或动画不会每次都从头开始。其他页面可以用 rp.open('sidebar') 把它调出来(需要在清单的 actions 中列出 open):示例的状态条里就有一个这样做的“地图”按钮。

在框架内部,页面可以使用 canvas、WebGL、音频、worker、WebAssembly,以及对包自己文件的 fetch,例如它自带的数据文件或库。它无法读取 DSH 页面、你的登录状态或浏览器存储,普通的外部 fetch、图片和 WebSocket 也会被阻止。自行跳转是例外,因此只应使用你信任的包;见框架。

试试示例​

初始世界《黑水观测站》自带一个名为“野外笔记”的界面包。添加这个世界并开始一个故事,你会看到一个显示地点、时间和在场者的状态条,还有一个 界面 标签页,里面有你已知的地点、在场的人、随身物品、建议行动、故事回顾,以及一个随故事保存的笔记框。它的文件在世界的 ui/notebook/ 中,可以复制这个文件夹作为起点。

野外笔记界面标签页,其中显示已知地点、人物、物品栏和笔记

插件自带的 Scene 界面包​

插件自带一个小界面包 Scene,适用于任何世界:一个显示地点、时间和在场人物的状态条,以及右侧栏里你所知地点的实时地图。DSH 启动时会把它放进你的角色扮演文件夹的 ui 文件夹(你改过的副本不会被覆盖;删掉的会回来)。要在某个故事里使用,可以在故事的对话设置里选择 Scene,或在开始故事时把界面包写成 scene。它会跟随界面语言(英文或中文),你自己的界面包也可以通过 rp.theme() 读到这个语言。

写的时候就能看到效果​

在世界编辑器的界面部分,可以看到这个世界携带的界面包:每个包要求什么、包含什么、每个问题出在哪个文件,以及预览。预览一次显示一个界面位置(状态条、标签页、右侧栏页面等),放在一个框架里,可选窄、中、宽三种宽度,内容就是由这个世界开始的故事的样子:第一个可扮演的角色、他所在的位置、身边的人和他知道的地点。在界面包里点一个按钮,这一部分会告诉你它请求了什么(“以玩家身份说……”“请求打开侧栏”);不会发送到任何地方。界面包存下的东西留在内存里,重新载入后消失。皮肤和主题在故事里生效,预览里不应用;由于没有进行过任何回合,“到目前为止的故事”是空的。修改文件后点重新载入再看。同一部分也可以编辑这些文件:新建界面包会创建带清单的文件夹,新建文件添加文本文件,文件上的编辑会在文本框中打开它;保存时会像开始故事时那样检查界面包,并告诉你还有什么问题。

界面包放在哪里,故事用哪一个​

位置用途
<世界>/ui/<包>/随世界发布的包。故事开始时会检查并复制到该故事中,所以世界之后改动也不会影响已开始的故事。
<故事>/ui/<包>/故事实际使用的副本。修改它只影响该故事。分支会保留其来源故事的界面包。
<角色扮演目录>/ui/<包>/你自己的包。只有在故事设置中指定名称时才会使用。

默认情况下,故事使用它自带的第一个包。在故事的 对话设置 标签页中,界面包 部分会显示正在使用哪个包,可以从列表中选择另一个(包括 无,即显示普通聊天),并指出任何问题所在的文件和行号。

故事在开始时复制世界的界面包,之后对世界的修改不会传到故事中。当世界的包与故事的副本不同,这一部分会提供 从世界更新:添加新的包,替换已改动的包,被替换的副本保存在故事的 .trash 文件夹中。只要世界中有任何一个包存在问题,就不会做任何改动。

文件改动后会重新读取,所以你可以一边编辑故事中的副本,一边在几秒内看到页面跟随变化。有问题的包会被撤下,直到修好为止。

做一个自己的包​

最小的可用包只有一份清单和一个页面。

pack.json

{
"name": "地点与时间",
"api": 1,
"surfaces": { "hud": { "file": "hud.html", "height": 40 } }
}

hud.html

<!doctype html>
<p id="line"></p>
<script>
rp.watch((view) => {
const place = view.place ? view.place.name : '无处'
const time = view.time ? view.time.text : ''
document.getElementById('line').textContent = `${place} — ${time}`
})
</script>

把两个文件放进 <世界>/ui/place-and-time/,用这个世界开始一个故事,状态条就会出现在输入框上方。rp.watch 会立即以玩家能知道的内容调用你的函数,此后每次变化再调用。

几个让界面包外观正确的习惯:

  • 使用 DSH 的颜色。 框架里的颜色以 CSS 变量提供,所以 color: var(--dsw-alias-label-primary) 和 background: var(--dsw-alias-bg-layer-1) 会跟随浅色和深色主题。不要写死白色或黑色。
  • 状态条保持简短。 状态条用 rp.resize(height) 申请高度;状态条可为 24 到 320 像素,开场卡片为 120 到 640 像素。
  • 只列出用到的操作。 让按钮发言或填写输入框需要在清单的 actions 中列出 say、fill 或 choose,未列出的会被拒绝。
  • 用相对地址 引用自己的样式表、脚本、数据和图片。无法从互联网加载任何内容,所以需要的东西(包括库)都要随包一起提供。
  • 用文本,不用故事里的标记。 显示名字和描述时用 textContent,不要用 innerHTML。

改变故事文字的样式​

皮肤不需要 JavaScript,就能改变故事自身文字的样式。在清单中指定它("skin": "skin.css")。故事的每个元素都带有 data-rp 属性,选择器的每一部分都必须指明其中之一:

[data-rp="utterance"] { border-left: 3px solid var(--dsw-alias-brand-primary); padding-left: 10px; }
[data-rp="utterance"] [data-rp="words"] { font-family: Georgia, serif; }
[data-rp="speaker"] { color: var(--dsw-alias-brand-primary); }

可以设置样式的元素以及皮肤必须遵守的规则见参考。有错误的皮肤会整体被拒绝,并指出行号。

让助手来写​

世界创作助手可以为你编写界面包。请描述你想看到什么、放在哪里,例如“一个显示时间和在场者情绪的状态条”。它会先读一份简短的指南,再逐个文件写入,并反复检查直到没有问题。

它使用 DSH 自带的文件工具,而在这个对话中,这些工具只能触及世界界面包的普通文本文件(<世界>/ui/<包>/:html、css、js、json、txt 和 svg,每次调用最多 200 KB)。路径中的符号链接会被拒绝,因此看似位于包内的已批准路径不能把工具引到世界之外。它无法访问世界的其他任何文件,请求更宽的权限也不会改变这一点。每次改动都会先征求你的同意,DSH 会把改动显示为差异。图片和字体需要手动添加。

你可能想用的插件(第三方)​

不代表认可

下面提到的插件由其他人开发。提到它们,只是因为它们能配合创作助手使用的文件工具,部分用户可能会想用。本插件的维护者并不认可这些插件,这些插件的作者也并不认可本插件。 安装之前请先了解插件做了什么,并自行决定;安装风险由你自己承担。我们只在所注日期用这里写明的版本试过一次,无法保证它们与任何一方的后续版本继续兼容。

因为助手使用 DSH 自带的 read、write 和 edit 工具,所以为这些工具开发的插件可以审阅或限制它的行为。我们在 2026 年 10 月 14 日、DSH 0.2.0-rc.2 上试过两个:

  • dsh-diff-approval(0.34.0 版)会把助手做的每一处改动汇总成待处理列表,显示差异,并允许按块或按文件保留或回退。在我们的试用中,回退会把文件恢复原样。安装:dsh plugin add dsh-diff-approval。它还会给助手增加一个自己的工具 diff_approval_annotate,可在你的审阅列表中的文件上添加批注;它不会写文件。
  • dsh-permissions(2.0.2 版)提供针对工具和路径的规则。如果保留它每次 write 前都询问的默认设置,每处改动你会被问两次:一次来自它,一次来自本插件。安装:dsh plugin add dsh-permissions。

其他审阅或权限类插件也许同样可用,但我们没有试过。有一类我们不会用于角色扮演对话:给整个工作区做快照的撤销插件,因为对于角色扮演对话,工作区就是你的角色扮演目录,里面有每个故事的数据库。

分享界面包​

世界的界面包随世界文件夹一起走。拿到文件夹的人也就拿到了界面包,他们开始的每个故事都会复制它们。

界面包是编写者写的代码。沙盒让它无法访问你的文件、登录状态、浏览器存储和 DSH 页面,也会阻止普通的网络资源加载;但界面包可以在自己的框架里显示任何内容,也可以让框架跳转到外部 URL。该 URL 可能带上桥接接口提供的故事数据。使用之前请先阅读并信任界面包;沙盒用于隔离,不会把恶意代码变成安全代码。故事的 对话设置 可以为该故事关闭界面包(选 无)。

如果有什么不工作,请见故障排查。