創作者定義的語句 link

創作者定義的語句(CDS)允許創作者在Ren’Py中添加自己的語句。這個機制允許添加和使用當前Ren’Py不支持的語法。

CDS比直接使用Python代碼更靈活。

例如,從一段對話中隨機獲取一行:

label introduction:
    python:
        greetings = ['你好。', '歡迎光臨。', '有什麼需要幫助的嗎?']
        greeting = renpy.random.choice(greetings)

    "[greeting]"

Ren’Py的處理器並不預先知道這段代碼的功能和執行結果。Ren’Py不會執行任何處理,除非遇到代碼執行報錯。

使用CDS可以實現下列功能:

  • 語法檢查(例如,傳給 renpy.random.choice 的列表元素中是否存在非法文本)。

  • 執行時忽略不正確的數據(對非關鍵函數,一般停止執行比拋異常更好用。)。

  • 預載入(函數需要用到的)可視組件。

  • Lint 中向創作者提供額外資訊(運行時被忽略的錯誤可以使用這種方式記錄)。

例如,實現隨機對話內容的CDS:

python early:
    def parse_random(lexer):
        subblock_lexer = lexer.subblock_lexer()
        choices = []

        while subblock_lexer.advance():
            with subblock_lexer.catch_error():
                statement = subblock_lexer.renpy_statement()
                choices.append(statement)

        return choices


    def next_random(choices):
        return renpy.random.choice(choices)


    def lint_random(parsed_object):
        for i in parsed_object:
            check = renpy.check_text_tags(i.block[0].what)
            if check:
                renpy.error(check)

   renpy.register_statement(
        name="random",
        block=True,
        parse=parse_random,
        next=next_random,
        lint=lint_random,
    )

random 可以直接作為語句使用:

label introduction:
    random:
        "你好。"
        "歡迎光臨。"
        "有什麼需要幫助的嗎?"

使用CDS並不保證執行一定會成功。 但優秀代碼定義的語句,更有利於Ren’Py“理解”創作者的設計意圖。

使用方法 link

CDS必須符合下列規則:

  • 必須在 python early 語句塊中定義。

  • 定義CDS的文件中不能使用該CDS。

  • 定義CDS的文件必須在使用對應語句的文件之前載入。 (由於Ren’Py按照Unicode字元順序載入文件,所以可以在定義CDS的檔案名加個數字前綴,比如01之類的數字。參考 腳本處理階段 中Ren’Py載入文件順序,特別是 game/libs/ 和 game/mods/ 目錄下的細節。)

CDS使用 renpy.register_statement() 函數註冊。 該函數會將調用其他函數,並根據CDS的內容執行運算。

例如,定義了名為 line 的新語句,可在文本中使用引號分隔。

line e "這裡的引號不會顯示" 艾琳說, "也不需要反斜槓轉義符。"

parse函數會處理文本內容。 execute函數會基於處理後的內容運行操作。 lint函數會記錄處理內容中的錯誤。

python early:
    def parse_smartline(lexer):
        who = lexer.simple_expression()
        what = lexer.rest()
        return (who, what)

    def execute_smartline(parsed_object):
        who, what = parsed_object
        renpy.say(eval(who), what)

    def lint_smartline(parsed_object):
        who, what = parsed_object
        try:
            eval(who)
        except Exception:
            renpy.error("角色對象未定義: {}".format(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,
    )

API引用 link

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, execute_default=None, reachable=None) link

該函數註冊了一條創作者定義的語句。

name

一個空格分隔的名稱列表作為語句的開頭,或者空字串定義一個新的默認語句(默認語句會替換say語句)。

block

This may be one of: 該參數可能是下表中的一項:

  • False,表示語句後面沒有語句塊。

  • True,語句後面需要有語句塊,並會處理該語句塊。

  • “possible”,後面有沒有語句塊,都行。

  • “script”,表示後面的語句塊是某個Ren’Py腳本語句的一部分。詳見 next 如何使用此參數控制流程。

  • “script-possible”,若後面出現語句塊則視作“script”處理,如果沒有語句塊則視作False處理。

  • “atl”,表示後續語句塊是某個ATL變換的一部分。使用該參數後,後續語句塊會傳給 execute 執行。

  • “atl-possible”,若後面出現語句塊則視作“atl”處理,如果沒有語句塊則視作False處理。

parse

該參數是一個函數,入參為一個Lexer對象。函數會處理語句並返回一個對象。返回對象會作為參數創給所有其他函數。

lint

調用該函數檢查語句。入參只有一個,即 parse 返回的對象。其調用renpy.error報錯。

execute

當語句執行時,調用該函數。入參只有一個,即 parse 返回的對象。 若語句後面是一個ATL語句塊,則關鍵字參數 alt 會與ATL變換一起傳入並執行。

execute_init

在初始化階段調用的函數,運行在優先度priority 0級別。入參只有一個,即 parse 返回的對象。

predict

預載入語句使用的圖像時調用的函數。入參只有一個,即 parse 返回的對象。其會返回此自訂語句使用的可視組件列表。

next

判斷下一個語句時調用的函數。

如果 block 的值不是字串“script”,該函數的入參只有一個,即 parse 函數返回的對象。 如果 block 的值是字串“script”,就會多一個入參,即語句塊(block)第一條語句名對應的對象。

該函數應該返回一個字串,表示跳轉的腳本標籤(label)名,第二個入參將主控流程切換到標籤對應的語句塊; 該函數也可以返回None,表示繼續執行下一條語句。 在 parse 函數中被調用時,該函數還可以返回 Lexer.renpy_statement()Lexer.renpy_block() 的執行結果。

label

調用該函數決定使用語句的腳本標籤(label)名。如果函數返回的是字串,字串對應的語句標籤可以被call或jump。

warp

調用該函數決定在warp時是否執行該語句。如果函數存在並返回True,warp時就運行該函數,否則在warp時不執行語句。

scry

Ren’Py內部使用。

init

如果該語句應該在初始化階段運行,這項就是True。(如果語句沒有在某個init語句塊中,會被自動放置到init 0語句塊裡。)這項會調用execute函數,還有execute_init函數。

如果設置了 execute_init 函數,最好就不要再指定這個欄位,不然在init語句塊中各種跳轉會導致 execute_initexecute 函數被同時調用。

translatable

若該參數為True,該語句會包含在translation語句塊中,跟語句塊中的say語句效果類似。 只有單行語句才可以將該參數設為True。可用於 nvl clearvoice 等語句,執行結果會隨著對話推進而改變。

init_priority

一個整數,決定 initexecute_init 語句塊中初始化時的優先度。

translation_strings

當所在代碼塊被執行時,調用的一個函數。返回值是一個字串列表,並後續在需要多語言支持的地方使用。

force_begin_rollback

對於想要觸發快速跳過功能的語句,類似 menucall screen 語句,該項應設置為True。

post_execute

本條語句執行完,下一條語句執行前,將執行這個參數的函數。(添加post_execute函數將修改RPYC文件,因此需要強制重新編譯。)

post_label

調用此處的函數,決定執行完上一個語句後跳轉的腳本標籤(label)。如果該函數返回一個字串,就表示需要跳轉的腳本標籤名,可以像其他標籤一樣正常調用或者跳轉。該功能可以用於創建一個唯一返回節點。

predict_all

若該項為True,詞條語句之後的所有語句和分支語句都將預載入。

predict_next

該項是一個腳本標籤(label),在本條語句執行後將運行對應腳本標籤內的語句。

本條語句後面的語句運行後調用該項實現後續語句的預載入,需要的返回值是一個腳本標籤(label)列表或者SubParse對象。當 predict_all 為True時,該項不會被調用。

execute_default

該項是一個函數,調用時機與default語句相同——在初始化階段後,遊戲開始運行前;載入存檔後; 回滾之後;lint檢查前;一些其他可能的時機。

調用該函數時使用單一參數,返回 parse 處理後的對象。

reachable

該項是一個函數,調用後可以允許自訂語句訂製自身如何在lint中被處理。

默認情況下,通過 Lexer.renpy_block() 方法創建一條語句的自訂語句塊、sub-parse語句塊。 這樣創建的自訂語句可以被lint訪問的前提下,後面的語句也是可以被lint訪問到。 如果語句中有一個label函數的話,也可以被lint訪問到。

可以提供一個可訪問判斷函數實現自訂。 該函數有5個參數(在下表中,“label”可以是字串或opaque對象):

  • parse函數返回對象。

  • 如果語句可訪問,則該布林值為True。

  • 語句的標籤(label)。

  • 下一條語句的標籤,如果沒有下一條語句則為None。

  • block 設置為“script”,該參數等於語句塊中的第一條語句的標籤。如果不存在語句塊則為None。

返回結果是一個集合,包含下列資訊:

  • 可訪問語句的subparse對象的標籤。

  • True,表示該自訂語句不需要lint處理,也不需要被lint訪問。(如果通過跟在可訪問語句後面,可能會變成可訪問。)

  • None,表示忽略返回結果。

該函數可以使用不同的is_reachable參數值多次調用,深度訂製自訂語句在lint中的訪問權限和行為。 (例如,跟在自訂語句後面的語句與前者的可訪問權限保持一致。)

Warning

使用空字串重定義say語句通常是個糟糕的主意。因為這會替換Ren’Py原生語句,影響 等效語句。 重定義的say語句不再支持 id` 和多語言系統。重定義default語句的遊戲則不能使用對應的功能特性。

Lexer對象 link

自訂語句的parse方法使用一個Lexer對象:

class Lexer link
error(msg) link
Parameters:

msg (str) – 處理錯誤列表中添加的資訊。

在檢測到的處理錯誤列表(當前位置)中添加一個 msg 元素。這個方法將中斷當前語句的執行,但不妨礙後續語句的處理。

require(thing, name=None) link

嘗試處理 thing ,如果無法完成則報一個錯誤。

如果 thing 是一個字串,嘗試使用 match() 進行處理。

其他情況下, thing 必須是一個lexer對象的其他方法,並且該方法調用時沒有入參。

如果沒有指定 name 的值,方法的名稱將會用於報錯消息(thing`為字串則直接使用該字串)。 否則,報錯資訊使用 `name

eol() link
Returns:

如果Lexer對象處於這行結尾則返回True,否則返回False。

Return type:

bool

expect_eol() link

如果Lexer對象不處於某一行腳本的結尾,則產生一個錯誤。

expect_noblock(stmt) link

調用該方法判斷當前語句後面是否為語句塊。 如果找到語句塊則產生一個錯誤。 stmt 應是一個字串,並被添加到報錯消息中。

expect_block(stmt) link

調用該方法判斷當前語句後面是否需要一個非空語句塊。 stmt 應是一個字串,並被添加到報錯消息中。

has_block() link
Returns:

當前行含有一個非空語句塊時返回True,否則返回False。

Return type:

bool

match(re) link

匹配一個任意的正則表達式(regexp)字串。

Lexer對象中的所有語句都會使用這個方法。首先跳過空白,嘗試在一行中匹配。如果匹配成功,返回匹配到的文本。否則,返回None,但Lexer對象不會發生變化。

keyword(s) link

匹配關鍵字 s

name() link

匹配一個名稱。名稱不會是內建的關鍵字。

word() link
Returns:

匹配目標詞所在的整段文本。

Return type:

str

匹配任何詞,包括關鍵字。

image_name_component() link

匹配一個圖像名組件。與word不同,圖像名組件可以用數字開頭。

string() link

匹配一個Ren’Py字串。

integer() link
Returns:

包含這個整數的字串。

Return type:

str

匹配一個整數。

float() link

:return:包含這個浮點數的字串。 :rtype: str

匹配一個浮點數。

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
Returns:

一個Lexer對象,用於當前行相關聯的語句塊(block)。

advance() link

在一個子塊(subblock)Lexer中,前進到下一行。在第一行之前必須調用這個方法,這樣第一行才會被處理。

renpy_statement() link

調用該方法後,將當前代碼行當作Ren’Py腳本語句處理,如果處理失敗則生成一個錯誤。 該方法返回一個不透明對象。這種不透明對象也可以從 renpy.register_statement() 方法返回,可以傳給 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 }
empty

若為True,允許處理空的代碼塊。 (空代碼塊等於一條 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 }

A function allows you to lex arbitrary strings:

lex_string(text: str, filename: str = '<string>', linenumber: int = 1, advance: bool = True) Lexer link

返回一個Lexer對象,可用於解析指定文本。

text

待解析文本。

filename

一個檔案名。報錯資訊寫入該文件。

linenumber

報錯時記錄的行號。

advance

若為True,解析是會調用Lexer對象的 Lexer.advance() 方法。

lint功能函數 link

在編寫lint函數時,下列函數很有用。

renpy.check_text_tags(s) link

檢查文本標籤 s 的正確性。如果存在錯誤則返回錯誤字串,沒有錯誤則返回None。

renpy.error(msg) link

將字串 msg 作為錯誤資訊報給使用者。通常作為parse或lint錯誤記錄日誌,其他情況會拋出異常。

renpy.try_compile(where, expr, additional=None) link

嘗試編譯一個表達式,如果失敗則將錯誤寫入lint.txt文件。

where

一個字串,表示表達式位置。常見的錯誤資訊格式為“Could not evaluate expr in where”。

expr

嘗試編譯的表達式。

additional

添加到錯誤消息中的額外資訊。

renpy.try_eval(where, expr, additional=None) link

嘗試計算一個表達式,如果失敗則將錯誤寫入lint.txt文件。

where

一個字串,表示表達式位置。常見的錯誤資訊格式為“Could not evaluate expr in where”。

expr

嘗試編譯的表達式。

additional

添加到錯誤消息中的額外資訊。