mobile

Apotheca ⸸

又在折腾你那个视觉小说了

デンシの呪文
2.5k 6 mins ... ...

  懒得再给自己沉迷游戏找什么借口总之最近打游戏打得有点想吐于是又开始折腾纯 web 视觉小说了。但是考虑到我写剧本的效率没准最后会变成纯网页的 replay 视频……

ʚ ⸸ ɞ

  很久很久以前被香油推荐了 ink,理论上是个现成的轮子但很多我想要的功能没实现而且文档是全英文还要花钱买。这次想起来就去搜了结果找出来的是另一个 ink.js,可以预见之后不管遇到任何问题不管求助搜索引擎还是 Gemini 我都会在这两个同名库(天哪为什么会有两个同名库)之间像羽毛球一样被打得飞来飞去,于是最终决定:金轮子银轮子不如自己造个狗轮子。
  好像有什么奇怪的 DNA 打结了(。

  总之今天的任务就是完整地构思一下我需要的视觉小说(网页 replay)功能列表……
  如果真的能做成公开库那就命名成 reink.js 好了给社会添乱

界面篇

  基本的界面构成:

main-box・游戏注入区
stage・包含所有非文字部分
bg・底部全屏覆盖
chara・立绘显示,自由位置或使用 flex?
CG・全屏覆盖
weather・天气特效
text・文本与对话,自由位置覆盖
button・选项和骰子动画,自由位置覆盖
menu・菜单,全屏覆盖
  • main-box 游戏主界面
    通过切换 class inline 来决定是否全屏显示
  • bg 背景显示
  • chara 立绘显示
    通过 id 来控制不同的角色
    需要支持叠加层控制立绘差分,支持单独设置叠加层座标
  • weather 天气叠加层
    使用 webm 格式背景图添加天气效果
  • text 文字层
    通过添加 class 决定此文本属于旁白还是对话

特效

  基于以上 html 结构通过添加不同的 class 调用预设 css 滤镜与动画,理论上可以作用于任何元素也可以自由控制特效范围。
  嗯暂时不考虑内存占用的问题,但是天气特效直接调用视频了应该不至于特别烫吧……
  或者是不是也可以直接用全屏特效然后给文字部分设置 filter: none!important?等搓出来再研究吧,特效目前不是最高优先级……。

资源注册篇

  这部分写成不同的 json 吧……

基本信息|info

  基本信息录入部分,用于 UI 显示。
  原则上不把需要更改的变量放在这里,但是基于代码简化的原则可能还是会支持更改的……?但是重新加载游戏的时候会恢复初始值。

  • title 游戏 / 小说标题,默认修改网页文件的 title
  • gameId 查询页面上对应 id 的 div
  • author 作者名,支持多人注入游戏内容
  • 也支持自定义属性比如美术 / 音乐负责人或者 logo 之类,在 UI 里用对应的 name 调用即可

角色信息|chara

  • id 用于剧本的标识符,为所用元素设置对应的 data-name
  • img 立绘位置
    输入格式:...imgdir/main.png
    • emote 表情差分
      输入格式:[emoteName: file.png]
      留空则自动查找 main.png 同目录下的对应命名的文件

剧本调用篇

  点击页面后逐行处理剧本。
  设计上 [] 在实际代码中不输入。

文本

  如果把所有切换封装成一个点击函数的话应该只在文本这里有 return?如果当前处理的行不是文本就继续下一行。

  • 后缀 => [nodeId] 跳转至对应编号的节点
  • 前缀 - 此行生成选项
  • 前缀 【角色 id】 此行生成对应角色的对话
    实际判断时只检查
  • 前缀 + 此行生成旁白,无前缀默认生成旁白

角色

  • # chara [id] 添加对应角色的立绘
  • # chara [id] emote=[file] 切换差分
  • # chara [id] remove 移除立绘

背景・CG・天气

  • # bg/cg/weather [name] 切换相应层背景图为对应文件
  • # bg/cg/weather clear 切换相应层背景图为 none

特效

  为了方便使用,特效与动画的 CSS 文件中指定的 classname 应当都以 fx- 开头。
  由于 CSS filter 特性特效无法叠加,如需要叠加特效需要新建一个 fx-class 手动合并效果。但是动画应该可以?

  • # fx [id] [fx-class] 为对应 id 的元素添加对应 class
    如省略 fx- 前缀则自动添加
  • # fx [id] end [class] 为对应 id 的元素移除对应的 class
    如 [class] 留空则移除所有带 fx- 前缀的 class
  • # fx [id] clear 移除所有元素中带 fx- 前缀的 class

判断逻辑 1.0

  • 初始化并获取文本文件
  • 按换行符逐行拆分
  • 在点击鼠标时判断当前 line
    • 如本 line 为文本则导入文本并暂停,直到下一次点击
    • 如本 line 为注释则什么都不做,进入下一行
    • 如本 line 为指令(以 # )开头则执行对应指令

剧本例

  理想的剧本格式。

---
// 注释:检测 `# act` 来判断场景切换,做清空文字和转场特效的代码
// 所以 `---` 应该只起到一个排版的作用,多打两个回车也行
# act [nodeID] 章节名称
// 留空则不显示
# bg 场景名.png
一段旁白。我希望它还能支持一些基本的 markdown 语法但先让字能吐出来再说……
另一段需要额外点击才会出现的旁白。
	- 选项 A => 1.1
	- 选项 B => 2.1
	// 缩进没有特殊含义,只是排版用
---
# act 1.1 分支 A
一段旁白,如果这里没有旁白的话大概就直接上立绘了?
# chara C
+ 遇到了角色C子。如果逻辑设计得正确无论句首有没有加号应该都不会影响性能,但是现在的逻辑好像有点问题……
【我】你好。
# chara C smile
【C子】再见。
=> end1
// 空对话也可以跳转
---
# act 2.1 分支 B
+ 我也是旁白。
# fx 我 fx-扭曲
// 支持中文 id 与 classname,CSS 自带的特性 
【我】饿了……去便利店看看有没有吃的吧。
=> 2.2
---
# act 2.2
# bg 便利店.png
【我】买什么好呢?
	- 饭团 => end2
	- 再思考一会 => 1.1
	// 因为 1.1 没有设置 BG 所以这里可以在便利店直接和 C 子进行一模一样的对话诶
---
# act end1
+ 因为发呆太久饿得低血糖了。
# end
// 进入 end 后点击回到主界面
---
# act end2
+ 幸福地吃上了饭团。
# end

  啊因为暂时只做视觉小说和 replay 用就先不搞数值判断了……

展望未来(?

文本预处理

  在初始化过程中将文字剧本直接转换为 JS 对象,减少运行时的资源开支的同时也能非常方便地实现存档功能……
  理想的对象格式:

const gameScript = {
  "nodeID": {
    name: "章节名称",
    commands: [
      { type: 'background', file: '场景名' },
      { type: 'CG', file: '绝美CG' },
      { type: 'weather', file: 'snow' },

      { type: 'CG', action: 'clear' },

      { type: 'chara', id:'C', emote:'smile' },
      { type: 'chara', id:'C', action: 'remove' },

      { type: 'FX', selector: '.chara #C', action: 'add', class: 'fx-name' },
      { type: 'FX', selector: '.chara #C', action: 'remove', class: 'fx-shake' },
      { type: 'FX', selector: '.chara #C', action: 'clear' },

      { type: 'wait', time: 500 },
      { type: 'goto', target: '1.1' },
      { type: 'end' },

      { type: 'clearAll', target: 'img' },
      { type: 'clearAll', target: 'text' },
      { type: 'clearAll', target: 'media' },
      { type: 'clearAll' },

      { type: 'dialogue', charaId:'C', text: '再见。' },
      { type: 'narration', text: '一段旁白。...' },
      { type: 'narration', text: '另一段需要额外...' },
      { type: 'options', choices: [
          { text: '选项 A', target: '1.1' },
          { text: '选项 B', target: '2.1' }
      ]}
    ]
  },
  
  ...
};

  大概就这样,具体还有待优化,比如移动旁白位置(做出类似 a case of distrust 那样的效果)和 FX 本质上都可以通过 add / romove class 来执行,但是 Gemini 建议我比起追求极致性能不如保留代码可读性,那我觉得也行……?
  啊或者移动旁白位置这个行为本身就用 FX 来执行就没有任何问题了。

对话模式

  极简的只包含对话的类聊天记录页面的文本演绎(非常符合我的日常创作片段质量……)
  不需要任何选项和演出。哎感觉又是很适合做 replay 的一个模板,我们的团什么时候能开上啊……
  需要的改动:

  • main-boxdata-type 为标记进入对话模式
  • 为每个角色注册 icon 属性作为头像
    可选:注册 nick 属性作为昵称

Copyright 2025. All rights reserved.

魔女の部屋