Python語句

Ren’Py使用Python程序語言編寫,並且支持在Ren’Py腳本中包含Python語句。對Python的支持可以用在很多方面,從立一個flag到創建新的可視組件等。本節內容主要涵蓋Ren’Py腳本如何透過各種python語句直接調用Python的方法。

從Ren’Py 8.4版本開始,Ren’Py支持Python 3.12。Ren’Py使用的Python版本可能會在未來的版本中更新。Ren’Py腳本應該相容Python 3.12及更高版本。

Note

如果你熟悉Python,將會是一項優勢。 但並不是任何關於Python的東西都可以直接使用。 例如,Ren’Py中沒有帶的Python包(package)都不能在Ren’Py中使用。

Python中還有幾種數據結構,在存檔時可能會有問題。 詳細資訊請閱讀 存檔、讀檔和回滾 頁面,尤其是 不保存什麼 部分。 (對待文件、socket埠、疊代器、task、future執行緒和generator生成器需要特別小心。)

最後,雖然很多語句都有等效的Python代碼,但等效Python往往不如直接用Ren’Py語句。 例如,使用Ren’Py中的 show 語句時,圖像可以提前預載入,但如果用 renpy.show`() 函數則不能預載入。

Python

python語句包含一個Python的語句塊(block),當主控流程達到該語句時就會執行對應的語句塊。一條基本的python語句非常簡練:

python:
    flag = True

在必要時,python語句可以更複雜:

python:
    player_health = max(player_health - damage, 0)
    if enemy_vampire:
        enemy_health = min(enemy_health + damage, enemy_max_health)

這裡有兩個標識符可以改變python語句的表現:

hide

若出現了 hide 標識符,python語句將會以匿名者視角運行Python語句塊。在該python語句塊執行完畢後,匿名者視角就會遺失。

hide標識符允許Python使用不能保存的臨時變數——但也意味著儲存(store)對象不能直接接入儲存區(store),而必須以欄位(field)的形式接入。

in

in 標識符包含一個變數名。Python會使用那個變數名所代表的儲存區(store),而非默認儲存區。

單行Python語句

最常見的情況就是只有一行Python語句,運行在默認儲存區內。例如,一行Python可以用來初始化或者更新一個flag。為了讓編寫只有一行的Python更精煉,這裡提供了單行Python語句。

單行Python語句以美元符號($)開頭,一行語句內容可以包羅萬象。這是一些單行Python語句樣例:

# 立一個flag。
$ flag = True

# 初始化一個變數。
$ romance_points = 0

# 變數自增。
$ romance_points += 1

# 調用Ren'Py函數。
$ renpy.movie_cutscene("opening.ogv")

單行Python通常運行在預設的儲存區。

init python語句

init python 語句在初始化階段運行,早於其他遊戲資源載入。在其他方面,這種功能可以用於定義類(class)和函數(function),或者初始化樣式(style)、配置變數、持久化數據。

init python:

    def auto_voice_function(ident):
        return "voice/" + ident + ".ogg"

    config.auto_voice = auto_voice_function

    if persistent.endings is None:
        persistent.endings = set()

init 1 python:

    # bad ending始終是解鎖狀態。
    persistent.endings.add("bad_ending")

在 init 和 python 之間可以放一個運行優先度數值。如果沒有指定優先度,預設使用0。init語句按照優先度數值從低到高的順序運行。優先度相同的情況下,按照檔案名的unicode字元順序。文件內,從頭到尾順序運行。

為了避免與Ren’Py引擎產生衝突,創作者最好選用-999到999範圍內作為優先度。負整數的優先度主要用在庫(library)和主題設置。普通的init語句應該使用0或者正整數作為優先度。

init python語句也可以使用 hide 或 in 分句。

在init python語句中被賦值的變數不會用於存檔、讀檔,且不接受回滾。因此,在初始化完成後,這些變數值就不該改動。

Warning

在Ren’Py內創建的類,沒有繼承自任何類或顯式繼承自 object 類,及其以上類的所有子類,都不支持 __slots__ 屬性。 在舊版本Ren’Py中回滾以上類的實例會出現奇怪的表現,在新版本中則會報錯。

如果要定義可以存檔的類,創作者需要顯式繼承 python_object,不過那個類不支持回滾。

define語句

define語句在初始化時將一個變數賦值。此變數視為一個常量,初始化之後不應再改變。例如:

define e = Character("艾琳")

等價於(但會遺失一些好處,詳見下文):

init python:
    e = Character("艾琳")

define語句可以選擇使用一個命名儲存區(詳見下面的例子),將儲存區名放在變數前面,用英文句號(.)連接。舉例:

define character.e = Character("艾琳")

define語句可選擇帶一個索引值,使其可以在一個字典中設置元素:

define config.tag_layer["eileen"] = "master"

除了使用等號 = ,define語句還可以使用另外兩個運算符。 += 運算符用於添加元素,還主要用於列表合併。 |= 運算符主要用於集合合併。例如:

define config.keymap["dismiss"] += [ "K_KP_PLUS" ]
define endings |= { "best_ending" }

define語句可以選擇使用一個優先度數值,放在 define 和變數名之間。如果沒有指定優先度,預設使用0。例如:

define -1 config.image_directories = [ "images", "dlc/images" ]

使用define語句的一個優點是,在聲明時它會記錄下檔案名和該行腳本編號,供啟動器(launcher)的導航(navigation)特性使用。 另一個優點是,Lint 可以檢查define後面的值,例如是否重複定義為不同的值。

通過define語句定義的變數會被當作一個常數,不會保存或讀取,也不該被修改。 (Ren’Py不做強制要求,但修改那些參數會導致不可預見的情況出現。)

default語句

default語句給一個變數賦值,前提是該變數在遊戲啟動或者新遊戲載入時未定義。舉例:

default points = 0

如果變數 points 在遊戲啟動時未定義,這條default語句等價於:

label start:
    $ points = 0

如果變數 points 在遊戲載入時未定義,這條default語句等價於:

label after_load:
    $ points = 0

default語句可以選擇使用一個命名儲存區(詳見下面的例子),將儲存區名放在變數前面,用英文句號(.)連接。 如果儲存區名稱不存在,則創建該名稱的儲存區。 舉例:

default schedule.day = 0

與 define 語句相同,Lint 會對 default 語句進行檢查和最佳化。

Note

強烈推薦使用 default 設置遊戲中可能發生變化的每一個變數。 如果使用 init python 或 define 定義某個變數,玩家在遊戲過程中該變數發生了變化, 如果回到主選單或開啟新遊戲後,該變數與 init python 中的設置不一致,可能會導致一些遊戲內容提前“洩漏”。 若有在start腳本標籤(label)中定義變數,會出現讀檔後某些變數不存在的問題。

儲存區變數名

Ren’Py儲存Python變數的地方稱作儲存區(store)。請務必保證你使用的儲存區名沒有衝突。

define語句將一個值聲明為一個變數,也可用作定義一個角色對象。這也意味著角色和flag不能同名。

下面這段有問題的腳本:

define e = Character("艾琳")

label start:

    $ e = 0

    e "你好,世界。"

    $ e += 1
    e "你得了一分!"

無法運行,因為變數 e 同時用作角色和flag。

其他也常常需要放入儲存區的對象是轉場(transition)和變換(transform)。

以下劃線 (_) 開頭的變數名是預留給Ren’Py內部使用。詳情可以查看 預留變數名目錄 。

其他的命名儲存區

命名儲存區提供了一種將Python函數和變數有效組織成模組(module)的方法。利用Python的模組化功能,你可以將命名衝突的可能性降到最小。 每個儲存區都對應一個Python模組。預設的儲存區名為 store,使用 store.named 可以訪問對應名稱的儲存區。

使用 python in (init python 或 python early) 語句塊, 或者 default、define 及 transform 語句,都可以創建命名儲存區。 使用 from store.named import variable 可以將變數導入儲存區。 儲存區自身可以使用 from store import named 導入腳本中。

命名儲存區可以通過 python 或 init python (python early) 語句中的 in 分句讀取, 使對應的python語句都在該命名儲存區內運行Python。

舉例:

init python in mystore:

    serial_number = 0

    def serial():

        global serial_number
        serial_number += 1
        return serial_number

default character_stats.chloe_substore.friends = {"Eileen",}

label start:
    $ serial = mystore.serial()

    if "Lucy" in character_stats.chloe_substore.friends:
        chloe "露西是我的朋友!"
    elif character_stats.chloe_substore.friends:
        chloe "我有很多朋友,但露西並不是其中之一。"

    python in character_stats.chloe_substore:
        friends.add("Jeremy")

請注意,與Python一樣,import模組會重新綁定變數或類的名稱,而不是創建別名。更新從store命名空間導入的變數,並不會更新該store中同名變數的值。例如:

init python in mystore:

    value = 0

init python:
    from store.mystore import value

label start:

    $ value = 1

    "[value] equals 1, but [mystore.value] remains 0."

python in 語句塊中,預設的“outer”儲存區可以使用 renpy.store 或 import store 讀寫數據。

命名儲存區與默認儲存區在存檔、讀檔和回滾方面的情況一樣。 特殊命名空間,比如 persistent、config、renpy 等,不支持在其內部再創建子儲存空間。

常量儲存區

定義儲存區時,可以將內建變數 _constan 設置為True,這樣儲存區就會變成常量儲存區:

init python in mystore:
    _constant = True

儲存區為常量時,其內部儲存的所有變數都不會存檔時保存,並且不參與回滾。

常量儲存區中的變數僅能在初始化階段修改。 在init語句塊(包括 define、transform 等語句)中完成初始化後,儲存區內所有數據都被當作常量。

Ren’Py沒有辦法強制保證常量儲存區內的數據不會發生改變,所以創作者需要自己想辦法確保初始化階段之後不再會修改常量儲存區的數據。

定義和使用常量儲存區,可以將部分不會變化的數據分離出來,減少存檔、讀檔和回滾時的數據操作,降低系統開銷。

以下儲存區預設為常量儲存區:

_errorhandling
_gamepad
_renpysteam
_warper
audio
achievement
build
director
iap
layeredimage
updater

JSONDB

class JSONDB(filename, default=None)

JSONDB是一個兩級資料庫,使用Json儲存數據。 其可以被遊戲開發者在資料庫中儲存數據,用於遊戲腳本的版本控制。 例如,可以儲存每一條say語句的相關資訊,控制對應say語句顯示的內容。

JSONDB不適合儲存用戶行為導致的數據變化。 持久化數據 或普通存檔文件更適合那種需求。

資料庫應該只包含在Python中可以序列化為Json的數據類型,包括列表、字典(字串作為鍵)、字串、數值、True、False和None。 請參閱 Python文件 , 了解不同數據類型間的可互操性(interoperability),以及數據格式轉換的方法和各種可能遇到的坑。

兩級資料庫即使用字串作為鍵(key)的字典結構。 第一級字典是只讀的,當使用某個鍵查詢第一級字典時,第二級字典才會被創建,創建時可以選擇預設的內容。 第二級字典是可讀寫的,當第二級字典的某個鍵發生改變時,遊戲中對應的內容會在資料庫中保存。

與其他持久化數據類似,JSONDB不受回滾操作的影響。

JSONDB實例應該在初始化階段(在init python語句庫或使用define語句)創建,並自動保存在硬碟上。 該實例創建後是至少包含一個鍵值對的字典。例如:

define balloonData = JSONDB("balloon.json", default={ "enabled" : False })

以上代碼創建了一個JSONDB實例,並以預設值儲存在文件balloon.json中。 第二級字典的數據能直接作為普通字典使用。

screen say(who, what):

    default bd = balloonData[renpy.get_translation_identifier()]

    if bd["enabled"]:
        use balloon_say(who, what)
    else:
        use adv_say(who, what)

    if config.developer:
        textbutton "Dialogue Balloon Mode":
            action ToggleDict(bd, "enabled")

JSONDB構造器使用以下入參:

filename

資料庫實際儲存的對應檔案名。默認以game目錄作為基礎路徑。 推薦文件副檔名為“.json”。

default

若該入參不是None,其應該是一個字典性數據。當第二級字典創建時,該入參的數據會淺拷貝作為新字典的初始值。 僅當至少有一個鍵值對發生改變時,新字典才會作為整個資料庫的一部分而保存。

第一方和第三方Python模組(module)和包(package)

Ren’Py可以導入(import)純python的模組和包。創作者需要用在遊戲中的第一方的模組和包,可以直接放置在game文件夾裡。第三方的包可以放在game/python-packages文件夾裡。

例如,如果要安裝requests包,創作者可以用命令行進入遊戲所在目錄,然後運行如下命令:

pip install --target game/python-packages python-dateutil

無論何種情況,模組和包都可以導入(import)一個init python語句塊(block)中:

init python:
    import dateutil.parser

並非所有的Python包都與Ren’Py相容。創作者需要自己先安裝並確保對應的包可以正常工作。

回滾和isinstance

在.rpy文件裡定義的Python語句會格式轉換,使其允許回滾。從.py文件導入(import)的文件則不會發生這種格式轉換。因此,在Python中創建的對象無法使用回滾(rollback)操作,且在創建之後就最好不要更改。 作為變換的一部分,Ren’Py將dict、list和set類替換為支持回滾的版本。這些類是內建類的子類,所以所有方法都會工作。然而,這些類無法使用isinstance函數檢測其是否為python內建的dict、list和set類的實例。 為了解決這個問題,Ren’Py提供了Python內建的dict、list和set類的異名。

class python_dict

python_dict類是Python內建的dict類。

class python_list

python_list類是Python內建的list類。

class python_set

python_set類是Python內建的set類。