创作者定义的语句(CDS)允许创作者在Ren’Py中添加自己的语句。这个机制允许添加和使用当前Ren’Py不支持的语法。 CDS比直接使用Python代码更灵活。在大多数情况下,CDS用于某些重复使用的地方。例如,使用一个入参调用某个函数。 Ren’Py并不知道该函数的功能和执行机制,所以Ren’Py不会执行任何处理,除非遇到代码执行报错。 发生异常的情况下,使用CDS允许创作者检查语法是否正确(比如入参是否是合法的字符串),忽略不正确的数据(对于非关键函数的报错,跳过往往比抛异常更有效),预加载可视组件(如果函数使用可视组件), 并使用lint检查代码时打印额外日志信息(如果runtime忽略就可以在这里生成报告)。CDS并不保证所有执行能可以成功,只要传作者编写的语句代码越优质,Ren’Py就能“理解”并实现创作者希望需要的功能。
创作者定义的语句必须在python early语句块(block)中定义。还有,包含创作者定义的语句的文件必须在使用这个语句的文件之前加载。由于Ren’Py按照unicode顺序加载文件,通常合理的做法是,在注册创作者自定义语句的文件加上前缀“01”之类一个不大的数字。
创作者定义的语句不能在定义语句的文件中使用。
创作者定义的语句使用 renpy.register_statement() 函数注册。
renpy.register_statement(name, parse=None, lint=None, execute=None, predict=None, next=None, scry=None, block=False, init=False, translatable=False, execute_init=None, init_priority=0, label=None, warp=None, translation_strings=None, force_begin_rollback=False, post_execute=None, post_label=None, predict_all=True, predict_next=None) link这个函数注册了一条创作者定义的语句。
判断下一个语句时调用的函数。
如果 block 的值不是字符串“script”,这个函数的入参只有一个,即 parse 返回的对象。如果 block 的值是字符串“script”,就会多一个入参,即语句块(block)第一条语句名对应的对象。
这个函数应该返回一个字符串,表示跳转的脚本标签(label)名,第二个入参将主控流程切换到标签对应的语句块;这个函数也可以返回None,表示继续执行下一条语句。
menu 和 call screen 语句,该项应设置为True。该项是一个脚本标签(label),在本条语句执行后将运行对应脚本标签内的语句。
本条语句后面的语句运行后调用该项实现后续语句的预加载,需要的返回值是一个脚本标签(label)列表或者SubParse对象。当 predict_all 为True时,该项不会被调用。
parse方法使用一个Lexer对象:
Lexer linkerror(msg) link在检测到的处理错误列表(当前位置)中添加一个 msg 元素。这个方法将中断当前语句的执行,但不妨碍后续语句的处理。
require(thing, name=None) link尝试处理 thing ,如果无法完成则报一个错误。
如果 thing 是一个字符串,尝试使用 match() 进行处理。
其他情况下, thing 必须是一个lexer对象的其他other方法,并且该方法调用时没有入参。
如果没有指定 name 的值,方法的名称将会用于报错消息(thing`为字符串则直接使用该字符串)。
否则,报错信息使用 `name 。
eol() link如果Lexer对象处于这行结尾则返回True。
expect_eol() link如果Lexer对象不处于某一行脚本的结尾,则产生一个错误。
expect_noblock(stmt) link调用该方法判断当前语句后面是否为语句块。 如果找到语句块则产生一个错误。 stmt 应是一个字符串,并被添加到报错消息中。
expect_block(stmt) link调用该方法判断当前语句后面是否需要一个非空语句块。 stmt 应是一个字符串,并被添加到报错消息中。
has_block() link当前行含有一个非空语句块时返回True。
match(re) link匹配一个任意的正则表达式(regexp)字符串。
Lexer对象中的所有语句都会使用这个方法。首先跳过空白,尝试在一行中匹配。如果匹配成功,返回匹配到的文本。否则,返回None。
keyword(s) link匹配关键词 s 。
name() link匹配一个名称。名称不会是内建的关键词。
word() link匹配任何词,包括关键词。返回匹配目标词所在的整段文本。
image_name_component() link匹配一个图像名组件。与word不同,图像名组件可以用数字开头。
string() link匹配一个Ren’Py字符串。
integer() link匹配一个整数,返回包含这个整数的字符串。
float() link匹配一个浮点数,返回包含这个浮点数的字符串。
label_name(declare=False) link匹配一个脚本标签(label)名,可以是绝对或关联名称。 当 declare 为True时,设置为全局脚本标签名。 (注意该方法实际上不能定义脚本标签——定义脚本标签需要使用 label 函数。)
simple_expression() link匹配一个简单Python表达式,并将其作为字符串返回。 常用于需要一个变量名的情况。不建议修改得到的结果。 正确的做法是将返回结果直接用作计算。
delimited_python(delim) link匹配一个以 delim 结尾的Python表达式,比如‘:’。 常用于获取某个分隔符之前表达式的情况。不建议修改得到的结果。 正确的做法是将返回结果直接用作计算。 如果在行尾未匹配到分隔符则产生一个报错。
arguments() link在使用括号内的入参列表之前必须先调用该方法。如果入参没有指定值就返回None,否则返回一个对象。
返回对象有一个 evaluate 方法和一个可选的 scope 字典,返回一个元组。返回元组的第一个元素是固定位置入参的元组,第二个元素是关键字入参字典。
rest() link跳过空白,返回一行的其他内容。
checkpoint() link返回一个不透明对象,这个对方表现出Lexer当前状态。
revert(o) link当 o 是一个checkpoint()返回的对象时,将Lexer恢复为调用checkpoint()时的状态。(用于回溯。)
subblock_lexer() link返回一个Lexer对象,用于当前行相关联的语句块(block)。
advance() link在一个子块(subblock)Lexer中,前进到下一行。在第一行之前必须调用这个方法,这样第一行才会被处理。
renpy_statement() link调用该方法后,将当前代码行当作Ren’Py脚本语句处理,如果处理失败则生成一个错误。
该方法返回一个不透明对象。这种不透明对象也可以从get_next()方法返回,可以传给 renpy.jump() 和 renpy.call() 函数处理。
除非这种不透明需要作为语句处理结果的一部分,一般不进行存储。
包含该方法的语句执行完毕后,主控流程会切换为CDS语句之后的语句。(很可能是使用post_execute创建的语句。)
renpy_block(empty=False) link该方法将当前语句块中剩余的代码行都当作Ren’Py脚本处理,并返回一个SubParse对象,该对象相当于后续整个代码块的第一条语句。 代码块中所有语句将串联起来并顺序运行,然后主控流程切换到CDS之后的那条语句。 注意该方法只处理当前代码块。在很多情况下,我们还需要处理当前语句的子块(subblock),正确的做法如下:
def mystatement_parse(l):
l.require(':')
l.expect_eol()
l.expect_block("mystatement")
child = l.subblock_lexer().renpy_block()
return { "child" : child }
pass 语句。)
若为False,空代码块将触发报错。catch_error() link该方法是一个上下文装饰器(context decorator),与 with 语句协同使用,捕获和记录lexer上下文语句块内的报错,然后继续执行语句块后面的内容。 这是一个样例,使用该方法并在一个子块(subblock)中记录多个错误:
def mystatement_parse(l):
l.require(':')
l.expect_eol()
l.expect_block("mystatement")
strings = [ ]
ll = l.subblock_lexer()
while ll.advance():
with ll.catch_errors():
strings.append(ll.require(ll.string))
ll.expect_noblock("string inside mystatement")
ll.expect_eol()
return { "strings" : strings }
在编写lint函数时,下列函数很有用。
检查文本标签 s 的正确性。如果存在错误则返回错误字符串,没有错误则返回None。
renpy.error(msg) link将字符串 msg 作为错误信息报给使用者。通常作为parse或lint错误记录日志,其他情况会抛出异常。
renpy.try_compile(where, expr, additional=None) link尝试编译一个表达式,如果失败则将错误写入lint.txt文件。
renpy.try_eval(where, expr, additional=None) link尝试计算一个表达式,如果失败则将错误写入lint.txt文件。
这里创建了一种新的语句“line”。“line”语句允许不带引号的文本行。
python early:
def parse_smartline(lex):
who = lex.simple_expression()
what = lex.rest()
return (who, what)
def execute_smartline(o):
who, what = o
renpy.say(eval(who), what)
def lint_smartline(o):
who, what = o
try:
eval(who)
except:
renpy.error("Character not defined: %s" % who)
tte = renpy.check_text_tags(what)
if tte:
renpy.error(tte)
renpy.register_statement("line", parse=parse_smartline, execute=execute_smartline, lint=lint_smartline)
使用时这样写:
line e "这里的引号不会显示" 艾琳说, "也不需要反斜杠转义符。"