Python語句 link
Ren’Py使用Python程序語言編寫,並且支持在Ren’Py腳本中包含Python語句。對Python的支持可以用在很多方面,從立一個flag到創建新的可視組件等。本節內容主要涵蓋Ren’Py腳本如何透過各種python語句直接調用Python的方法。
Ren’Py 7現在支持Python 2.7。Ren’Py 8支持Python 3.9。
Note
如果你熟悉Python,將會是一項優勢。 但並不是任何關於Python的東西都可以直接使用。 例如,Ren’Py中沒有帶的Python包(package)都不能在Ren’Py中使用。
Python中還有幾種數據結構,在存檔時可能會有問題。 詳細資訊請閱讀 存檔、讀檔和回滾 頁面,尤其是 不保存什麼 部分。 (對待文件、socket埠、疊代器、task、future執行緒和generator生成器需要特別小心。)
最後,雖然很多語句都有等效的Python代碼,但等效Python往往不如直接用Ren’Py語句。
例如,使用Ren’Py中的 show
語句時,圖像可以提前預載入,但如果用 renpy.show`()
函數則不能預載入。
Python link
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語句 link
最常見的情況就是只有一行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語句 link
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語句 link
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語句的一個優點是,在聲明時它會記錄下檔案名和該行腳本編號,供啟動器(launcher)的導航(navigation)特性使用。 另一個優點是,Lint 可以檢查define後面的值,例如是否重複定義為不同的值。
通過define語句定義的變數會被當作一個常數,不會保存或讀取,也不該被修改。 (Ren’Py不做強制要求,但修改那些參數會導致不可預見的情況出現。)
default語句 link
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)中定義變數,又出現讀檔後某些變數不存在的問題。
儲存區變數名 link
Ren’Py儲存Python變數的地方稱作儲存區(store)。請務必保證你使用的儲存區名沒有衝突。
define語句將一個值聲明為一個變數,也可用作定義一個角色對象。這也意味著角色和flag不能同名。
下面這段有問題的腳本:
define e = Character("艾琳")
label start:
$ e = 0
e "你好,世界。"
$ e += 1
e "你得了一分!"
無法運行,因為變數 e 同時用作角色和flag。
其他也常常需要放入儲存區的對象是轉場(transition)和變換(transform)。
以下劃線 (_) 開頭的變數名是預留給Ren’Py內部使用。詳情可以查看 預留變數名目錄 。
其他的命名儲存區 link
命名儲存區提供了一種將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 in
語句塊中,預設的“outer”儲存區可以使用 renpy.store
或 import store
讀寫數據。
命名儲存區與默認儲存區在存檔、讀檔和回滾方面的情況一樣。
特殊命名空間,比如 persistent
、config
、renpy
等,不支持在其內部再創建子儲存空間。
常量儲存區 link
定義儲存區時,可以將內建變數 _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 link
- class JSONDB(filename, default=None) link
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) link
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
Warning
在.rpy文件裡定義的Python語句會格式轉換,使其允許回滾。從.py文件導入(import)的文件則不會發生這種格式轉換。因此,在Python中創建的對象無法使用回滾(rollback)操作,且在創建之後就最好不要更改。
並非所有的Python包都與Ren’Py相容。創作者需要自己先安裝並確保對應的包可以正常工作。
注入型Python link
在根目錄中創建一個名為 exec.py
的文件後,就可以在遊戲運行狀態注入(inject)Python。
推薦先在其他地方使用其他檔案名,編輯完成後再改名並移動到根目錄。
Ren’Py發現 exec.py
文件後,會載入文件內容,刪除文件,並在遊戲儲存區使用Python的可執行文件 exec
運行文件內容。
這通常發生在某個交互行為中。
注入型Python還可以用於添加debug工具。啟用開發者模式後,默認也啟用了注入型Python功能。 此外,也可以只將環境變數 RENPY_EXEC_PY 的值修改為True來啟用注入型Python功能。