Ren'Py 使用 NVL 模式制作聊天界面

2026/05/13 | 3分钟阅读 | 更新于 2026/05/13

Ren'Py 使用 NVL 模式制作聊天界面

NVL 模式 可以一次展示多条对话和旁白,看上去很适合拿来改一个聊天窗口出来,也的确有很多人是这样做的。主要工作就是修改 screen nvl, 自行完成布局。不过,我的版本,应该更优雅一些。

为角色自动配置头像

Ren’Py 默认的 ADV 模式有头像 (Side Image) 这个特性,只需要给 Character 传一个 image 参数,就会自动匹配头像文件,那么 NVL 模式可以这样用吗?

嘛,结论是——并不可以。如果一个 Character 的 kind 是 nvl, 同时传入 image 参数,你会得到一些非常怪异的表现。屏幕左下角固定会展示一个头像,这个大概和 say 有关,这个倒是很好解决。

然后,你肯定要想办法为每一条历史消息获得对应的头像,但是历史消息的类型是 NVL Entry , 确切来说是 _NVLEntry. 其中没有 image_tag, 总不能用角色的名字去获取头像(虽然维护一个 dict 也是一个方案)吧?而且,renpy.get_side_image 的表现也很怪异。

关于 get_side_image 的表现

本段内容与正文无关,可以跳过。

renpy.get_side_image 的表现很怪,它和 SideImage 一样,看上去都是获取「当前」的头像。

要这样调用 img = renpy.get_side_image("side") 获取「当前」的角色的头像。

试图传递 renpy.get_side_image("side", "alice") 不一定能获得角色的头像,只有当前的角色有过不是默认的表情,之后,才能通过这东西获得默认的头像。

然后再通过 ImageReference(img) 变成 displayable.

……

总的来说,renpy.get_side_image 看上去并不是一个应该暴露给用户的方法。

使用 who_args 传递图像标签

所以目前的问题是,从历史对话中,获得角色对应的图像标签, 再通过图像标签去查找头像文件。

根据 NVL , NVL Entry 中的 who 只是个字符串。NVL Entry 可以用来传递信息的参数,只有 who_args, what_args, window_args, 甚至不像 DialogueHistory 那样有 show_args .

至少,NVL Entry 会保留 who_args. 给角色 (who) 改名字的文字颜色和字号,和给角色添加个头像看上去没有什么区别——除了这个 avatar 不是 text 自带的属性。不过无所谓啦,并不会影响 text 的正常工作。于是:

define alice = Character("爱丽丝", avatar="alice")

然后可以在 screen 中,取得对应的值:

$ avatar_tag = d.who_args.get("avatar") or "default"

使用 get_registered_image 获取图像

接下来是获取图像文件,有一个很有用的函数:renpy.get_registered_image . 你可以方便的使用自定义的前缀比如 avatar (当然也可以使用 side), 然后使用角色的 avatar tag 去获取头像,这样管理起来比较方便。

renpy.get_registered_image("avatar alice")

顺便一提,根据 Ren’Py 的源代码 ,传入的 name 可以是字符串或者 tuple. 所以 renpy.get_registered_image("avatar alice")renpy.get_registered_image(("avatar", "alice)) 都可以工作。

还有一件事

禁用默认的 screen 样式

对于 text d.what id d.what_id 之类的界面,Ren’Py 会通过 id 添加一些默认样式,但是这个 id 又不能不带。比起手动逐个覆盖自带的样式,一个方案是,设置 prefer_screen_to_id 为 True, 然后自行指定 style.

弹性布局

Ren’Py 的截图,展示了一个聊天窗口

如果也想要像这样,在下方展示菜单,但是选项数量与高度不确定;上半部分展示聊天记录,并且可以滚动。可以使用 vbox 但是调转方向 ,把高度不确定的菜单写在前面,负责滚动的 viewport 写在后面,并设置 yfill 为 True.

为什么只有六条对话

define config.nvl_list_length = None

# game/screens.rpy
## This controls the maximum number of NVL-mode entries that can be displayed at
## once.
define config.nvl_list_length = gui.nvl_list_length

# game/gui.rpy
## The maximum number of NVL-mode entries Ren'Py will display. When more entries
## than this are to be show, the oldest entry will be removed.
define gui.nvl_list_length = 6

支持多种 NVL 模式

NathanGuilhot/Renpy-simple-messaging-system-for-mobile-game 的方案很不错,增加一个全局变量,指定要用哪个 nvl, 然后在 screen nvl 中进行判断。

© 2026 香蕉引擎故障报告

🌱 Powered by Hugo with theme Dream.

关于

要怎么介绍自己呢,🤔。

很早以前是作为 Web 前端在学习的,但是工作第一年就成为了全干工程师。喜欢尝试各种东西,什么都会一点。

一直很喜欢 Ebiten 游戏引擎 ,特别简洁,用它做过一些小东西,可以查看这个分类 。另外特别推荐这个木鱼 ,是一个相对完整的小玩意儿,包含手搓的一个简单的 UI 框架;支持鼠标和键盘操作;有多语言和主题切换功能;同时支持 Web 端和客户端。它的源代码在 bin16/wooden-fish

主题

网站基于 Hugo,当前使用的是 hugo-theme-dream 主题的修改版 ,根据我的需要,做了一些对 PaperMod 的兼容。

我自己也写过主题 ,但是没有别人写的好看。

正在从我的笔记中往外搬运内容

等待更新:

  • 从《锈湖》中学了些什么东西
  • 我拿 React 写解谜游戏的经过
  • 基于 Pocketbase 的 Pocket Memos
  • 数独!