马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
本帖最后由 烈林凤 于 2024-10-23 00:26 编辑
本来不打算把这两个功能加进“renpy进阶学习经验”里的,但这种极为实用的功能不加入这个系列多少有点可惜,于是想想还是加进来了。
这个教程的课题早在10月10日便在交流群里提出了,当天晚上@Gemini菖蒲 便用gpt写了一个找出label中所有say语句数量的方法(ai难得写出正确的代码),为课题的攻破奠定基础,非常感谢!
10月17日我便将代码正式撰写完毕,课题也完全告破,但因为种种事情耽搁到现在才想起来写()
感谢@Aaron栩生阿龙 、@Gemini菖蒲 等各位交流群群u们提供的建议和想法!万分感谢!!!
如果需要转载本帖,请附上本人名称“烈林凤”,严禁将本帖中的代码或本帖以任何形式进行售卖,非商用场合无需通知本人或署名,直接使用就好,若需嵌入付费项目,请与我联系。
关于本帖中有任何不理解的地方都可以留言,但请先尝试运行之后再提问,谢谢!
我所使用的是8.2.1版本的renpy,请尽量使用高版本,谢谢!
接下来进入正题——
本期的主题为“高精度流程进度条与章节回放/选择功能(renpy进阶学习经验六)”
主要使用了“replay”和“renpy.is_seen”两个功能相关的代码
文档——
画廊、音乐空间和场景回放 — Ren'Py 中文文档
其他函数和配置变量 — Ren'Py 中文文档
若在测试本教程代码时发现遇到了bug,请先在renpy启动器中“删除持久化数据”并“强制重新编译”!再重新进行测试!
所有代码如下——
[RenPy] 纯文本查看 复制代码 ## 设定一个持久化数据,用于记录label名称和say语句数量
default persistent.read_label_say_count = {"test_label":0,"test_label_1":0}
init python:
def get_label_say_count_max(label_name=[]):
'''获取label中say语句数量的最大值'''
label_say_count_max = {}
for i in label_name:
## 将label的say语句数量初始化为0
label_say_count_max[i] = 0
## 遍历label名称中含有的所有节点,获取say语句数量
for node in renpy.game.script.lookup(i).block:
## 如果节点是say语句,则将say语句数量+1
if isinstance(node, renpy.ast.Say):
label_say_count_max[i] += 1
## 返回label名称和say语句数量的字典
return label_say_count_max
def get_read_label_say_count(label_name=None):
'''获取当前label名称和say语句数量'''
current_label = []
current_label_name = ""
## 如果翻译文件标识符存在,且当前语句没有被阅读过
if label_name and renpy.is_seen(ever=True) == False:
## 以下划线为间隔符,将获取到的l翻译文件标识符分割为列表
current_label = label_name.rsplit("_")
## 若翻译文件标识符末尾是正整值(相同的语句末尾会额外添加出现的次数)
if current_label[-1].isdigit():
current_label.pop()
current_label.pop()
else:
current_label.pop()
## 重新组合翻译文件标识符,拼合成当前label名称
current_label_name = '_'.join(current_label)
## 若当前label名称存在于字典中,则将say语句数量+1
if current_label_name in persistent.read_label_say_count:
persistent.read_label_say_count[current_label_name] += 1
screen count_say_lines_in_label_screen():
## 将其改为和主菜单相同的UI风格
tag menu
style_prefix "count_say_lines_in_label_screen"
if main_menu:
add gui.main_menu_background
else:
add gui.game_menu_background
frame:
style "game_menu_outer_frame"
use navigation
textbutton "返回":
align(0.9, 0.9)
action Return()
## 获取所需的label名称和say语句数量,将label名和所需章节名一一对应
default label_say_count_max = get_label_say_count_max(label_name=['test_label','test_label_1'])
default label_name_dict = {"test_label":"测试章节","test_label_1":"测试章节2"}
vbox:
xycenter (0.5, 0.5)
spacing 20
for i in label_name_dict.items():
vbox:
hbox:
xsize 500
text "[i[1]]章节进度" align (0.0, 0.5)
## 若当前没有在回放当中,并且当前label名称的say语句数量等于最大值,则显示开始回放按钮
if (_in_replay == None) and (persistent.read_label_say_count[i[0]] == label_say_count_max[i[0]]):
textbutton "开始回放":
align(1.0, 0.5)
action Replay(i[0], scope={}, locked=None)
## 若当前在回放当中,则显示结束回放按钮
elif _in_replay == i[0]:
textbutton "结束回放":
align(1.0, 0.5)
action EndReplay(confirm=True)
else:
textbutton "不可回放":
align(1.0, 0.5)
action NullAction()
hbox:
xycenter (0.5, 0.5)
bar:
xysize (500, 30)
value persistent.read_label_say_count[i[0]]
range label_say_count_max[i[0]]
## 显示当前label名称的say语句已读数量与最大值
text str(persistent.read_label_say_count[i[0]]) + "/" + str(label_say_count_max[i[0]]) size 25
label ceshi_24:
"111在test_label中有个say语句。"
"222在start_label中有个say语句。"
jump test_label
label test_label:
"This is a test label."
"This is another test label."
"This is a third test label."
"test1"
"test2"
"test3"
"123"
$ renpy.end_replay()
jump test_label_1
label test_label_1:
"This is a test label."
"This is another test label."
"This is a third test label."
"test11"
"test22"
"123"
$ renpy.end_replay()
return
接下来进行详解——
首先是第一个是如何实现高精度流程进度条
在这一部分中,首先最困难的便是如何获取到label语句下所有say语句的数量
如果只是以label是否被看完作为进度,那无需多考虑,只要通过renpy.seen_label()方法来判断label是否被执行过即可。
但是,如果需要以say语句是否被看过作为进度,那就非常麻烦了,因为renpy并没有原生的方法可以直接获取到所有label的所有say语句
这时候就需要调用一个内部方法——renpy.game.script.lookup()
这个方法来源于源码,文档中没有任何相关的描述,各位只需要知道,可以通过该方法可以获取到label中所有say语句数量即可
定义一个名为“get_label_say_count_max”的函数,用于获取label中say语句数量的最大值,入参为需要查询的所有label的名称的列表,遍历该label名称列表,调用renpy.game.script.lookup()方法,如果是say语句则将键值对的值+1
最后将字典返回,便完成了获取label中所有say语句数量的过程。
注意!这是源码的内部函数,以后renpy启动器更新可能会废除或改写该方法,更有可能编写成原生接口!若遇到8.4+版本及以上出现该处报错的情况,请及时留言联系我!
至此,便完成了该功能最困难的一步
其他的部分均可以通过原生的方法实现,后续一路畅通
接着我们来解决如何获取当前已读多少say语句的问题
这个问题其实可以分为两部分——获取需要记录的label名称和label下有多少say语句被已读
此时此刻,可能会有人问了:帖主帖主,是不是要使用renpy.count_newly_seen_dialogue_blocks()或者renpy.count_seen_dialogue_blocks()了啊?将所有已读say语句数量减去不需要的label中所有的say语句数量。
nonono,如果使用这两个方法,会产生问题——
1.比如说你不希望start等特殊label或其他自定义的label中的已读的say语句也被记录其中 ,那你必须将这些label的say语句数量全部获取一遍,再减去用该方法获取到的值
2.还有,已读say语句的数量可能为0,而减去的这些label中所有say语句数量,可能会导致最后的值为负数,这可能会导致额外的代码作为补丁才能使用,否则可能会导致后续功能报错(之后会讲到)
3.再然后,减去后获取到的值无法直接按label进行划分,导致我们需要大量额外的代码作为补丁进行章节划分
简单点来说,会导致代码维护困难、拓展性降低、堆砌成史山代码()
此时,我们隆重引进一个renpy原生方法——renpy.get_translation_identifier()
该方法会获取并返回返回当前语句的翻译文件标识符。
例如在label_test中的某个不重复的say语句,返回的参数大致如下——
test_label_db972873
但如果是重复的say语句,可能会返回如下参数——
test_label_8836ffa1_2
其中前面的test_label便是我们需要的内容,那该如何舍去后面的say语句编号呢?
这就需要调用python的rsplit方法了,从后往前查找下划线(“_”),以下划线为间隔分成成列表
最后用python的join方法将所需的字符串拼接在一起即可。
在此要额外穿插说明一下接下来会用到的一个字典——persistent.read_label_say_count
这个字典内储存着我们需要的所有label名称,与label对应的已读的say语句
同时,这个变量没有写在init python当中,而是单独写了一个default,因为它使用了持久化数据persistent,保证游戏开启时不会将我们记录的数据清除
让我们将这些整合到一个名为get_read_label_say_count的函数当中——
这个函数有一个入参,是当前语句的翻译文件标识符。
因为当前语句有可能不是say语句,会返回None,因此需要用if判断获取到的值是否存在,且使用renpy原生方法renpy.is_seen判断当前语句是否被看过
通过判断后,将获取到的翻译文件标识符以下划线为间隔,将字符全部存进current_label列表当中
判断列表最后的一个元素是否为整值,这里使用了python的isdigit方法,如果是整值则代表末尾存在重复次数的标识。
若不是整值则只需要去掉一次最后的元素,即可得到保存了正常label名称的列表,如果是整值则需要去掉两次即可。
通过join方法,将列表的所有元素以下划线为间隔再次组成字符串,便得到了最终需要使用的参数——当前label的名称
最后,我们判断该label是否是我们需要的(即是否存在字典当中),如果是需要的,则将对应的值+1
在say screen中使用on,当对话出现时便调用该函数,并以renpy.get_translation_identifier()作为入参
至此,我们便完成了最核心的,获取所需label已读say语句数的函数
而想要做进度条的方法则非常简单了,比如说,在自定义的screen中这么写——
[RenPy] 纯文本查看 复制代码 screen bar_screen():
## 定义一个列表,调用get_read_label_say_count函数,获取当前label名称和say语句数量
default label_say_count_max = get_label_say_count_max(label_name=['test_label','test_label_1'])
for i in label_name_dict.items():
bar:
xysize (500, 30)
value persistent.read_label_say_count[i[0]]
range label_say_count_max[i[0]]
[RenPy] 纯文本查看 复制代码 init python:
def get_label_say_count_max(label_name=[]):
'''获取label中say语句数量的最大值'''
label_say_count_max = {}
for i in label_name:
## 将label的say语句数量初始化为0
label_say_count_max = 0
## 遍历label名称中含有的所有节点,获取say语句数量
for node in renpy.game.script.lookup(i).block:
## 如果节点是say语句,则将say语句数量+1
if isinstance(node, renpy.ast.Say):
label_say_count_max += 1
## 返回label名称和say语句数量的字典
return label_say_count_max
def get_read_label_say_count(label_name=None):
'''获取当前label名称和say语句数量'''
current_label = []
current_label_name = ""
## 如果翻译文件标识符存在,且当前语句没有被阅读过
if label_name and renpy.is_seen(ever=True) == False:
## 以下划线为间隔符,将获取到的l翻译文件标识符分割为列表
current_label = label_name.rsplit("_")
## 若翻译文件标识符末尾是正整值(相同的语句末尾会额外添加出现的次数)
if current_label[-1].isdigit():
current_label.pop()
current_label.pop()
else:
current_label.pop()
## 重新组合翻译文件标识符,拼合成当前label名称
current_label_name = '_'.join(current_label)
## 若当前label名称存在于字典中,则将say语句数量+1
if current_label_name in persistent.read_label_say_count:
persistent.read_label_say_count[current_label_name] += 1
至此,便完成了本教程的第一部分内容
接下来是第二部分,章节回放/选择功能
这一部分与上面的功能结合起来编写
首先,我们先仿写一个默认菜单的界面——
[RenPy] 纯文本查看 复制代码 screen count_say_lines_in_label_screen():
## 将其改为和主菜单相同的UI风格
tag menu
style_prefix "count_say_lines_in_label_screen"
if main_menu:
add gui.main_menu_background
else:
add gui.game_menu_background
frame:
style "game_menu_outer_frame"
use navigation
textbutton "返回":
align(0.9, 0.9)
action Return()
接下来再在screen.rpy中修改两处——
[RenPy] 纯文本查看 复制代码 screen quick_menu():
## 确保该菜单出现在其他屏幕之上,
zorder 100
if quick_menu:
hbox:
style_prefix "quick"
xalign 0.5
yalign 1.0
textbutton _("{size=30}进度") action ShowMenu('count_say_lines_in_label_screen')
[RenPy] 纯文本查看 复制代码 ## 导航屏幕 ########################################################################
##
## 该屏幕包含在标题菜单和游戏菜单中,并提供导航到其他菜单,以及启动游戏。
screen navigation():
vbox:
textbutton _("进度") action ShowMenu('count_say_lines_in_label_screen')
保证随时可以查看章节游玩情况
之后在自定义的界面中写以下代码——
[RenPy] 纯文本查看 复制代码 ## 获取所需的label名称和say语句数量,将label名和所需章节名一一对应
default label_say_count_max = get_label_say_count_max(label_name=['test_label','test_label_1'])
default label_name_dict = {"test_label":"测试章节","test_label_1":"测试章节2"}
vbox:
xycenter (0.5, 0.5)
spacing 20
for i in label_name_dict.items():
vbox:
hbox:
xsize 500
text "[i[1]]章节进度" align (0.0, 0.5)
## 若当前没有在回放当中,并且当前label名称的say语句数量等于最大值,则显示开始回放按钮
if (_in_replay == None) and (persistent.read_label_say_count[i[0]] == label_say_count_max[i[0]]):
textbutton "开始回放":
align(1.0, 0.5)
action Replay(i[0], scope={}, locked=None)
## 若当前在回放当中,则显示结束回放按钮
elif _in_replay == i[0]:
textbutton "结束回放":
align(1.0, 0.5)
action EndReplay(confirm=True)
else:
textbutton "不可回放":
align(1.0, 0.5)
action NullAction()
hbox:
xycenter (0.5, 0.5)
bar:
xysize (500, 30)
value persistent.read_label_say_count[i[0]]
range label_say_count_max[i[0]]
## 显示当前label名称的say语句已读数量与最大值
text str(persistent.read_label_say_count[i[0]]) + "/" + str(label_say_count_max[i[0]]) size 25
强烈推荐各位使用“章节回放”而不是“章节选择”!!!
章节回放不可以存档,且独立于上下文,可以随时进行播放且不影响当前进程,可以随时退出,无任何副作用,相比“章节选择”,可以有效避免玩家通过这种方式刷结局、成就等!
在此引用文档的中的话——
场景回放也可以使用 Start() 行为。这两种模式的差别如下:
回放可以从任何界面启动,而Start只能使用在主菜单或者主菜单显示的界面。
当回放结束,主控流程会回到回放启动的点。那个点可能是在主菜单或者游戏菜单中。如果某个游戏运行过程中调用了回放,游戏状态是会被保留。
在回放模式下禁用存档。重新加载由于需要存档,也是禁用的。
在回放模式下,调用 renpy.end_replay() 会结束回放。在普通模式下,renpy.end_replay()不产生任何效果。
画廊、音乐空间和场景回放 — Ren'Py 中文文档
若需要使用章节回放,则要在label的末尾处(在jump或call跳转之前),使用renpy.end_replay(),写以下代码——
[RenPy] 纯文本查看 复制代码 $ renpy.end_replay()
这样,当回放运行到这一段时就会返回到原本的界面,且此代码对于正常游玩模式没有影响。
想要运行“章节回放”功能,需要在按钮上使用Replay()行为,第一个入参是你想要回放的label,比如说——
[RenPy] 纯文本查看 复制代码 textbutton "开始回放":
align(1.0, 0.5)
action Replay("test_label")
这样,点击按钮后变会从test_label处开始回放
再通过_in_replay来判断按钮显示文本和所使用的行为(开始回放Replay(),结束回放EndReplay(),不可回放/无行为NullAction())
如果是想要做“章节选择”功能,则只需要把Replay()换成Start()即可,第一个入参是你想要运行的label,比如说——
[RenPy] 纯文本查看 复制代码 textbutton "选择章节":
align(1.0, 0.5)
action Start("test_label")
若是使用章节选择,便不必再在label末尾处写上renpy.end_replay()了
结合之前写的“高精度流程进度条”,加入自身所需的功能,便能得到这个——
若有非常多的章节,则在最外层的vbox外再套个viewport即可(实现上下滚动的效果)
至此,章节回放/选择功能的部分也全部讲完了
呀,努力写了一个晚上的教程,终于肝完了,这篇教程估计是本系列最详细,且目前难度最高的一篇了
“renpy进阶学习经验”从一年前更新到现在,我的技术飞速增长,已经到了不用python就活不下去的程度了,所以说想要遵循以前的想法“尽量不使用太难懂的python内容”多少有点不太现实了()
不过说到底,这些不过都是我学习时发现的经验而已嘛!觉得挺不错且实用的便写出一篇篇详细教程(懒得写详细教程的或者没有什么好讲的都叫“xxx的解决方法”)
今后我也依旧会保持这种随心的状态为各位更新教程的,承蒙各位厚爱!!!
友链:b站——爱发电——github
最终,本篇教程到此全部结束,祝各位使用愉快!
|