界面語言最佳化 link
Ren’Py有一些技巧能最佳化界面語言的速度。當使用Ren’Py創建複雜的UI時,例如模擬遊戲使用的介面,理解界面語言的工作機制有助於創作者實現最佳的畫面表現。
這篇指導適用於界面語言的第二種實現方式,其是在Ren’Py的6.18版本中加入的。如果你的項目使用Ren’Py6.17或者更早的版本創建,最好在啟動器(launcher)選擇“強制重新編譯”,確保界面工具升級到最新版本。
本指導並不能替代良好養成編程習慣的練習。如果某個界面使用循環嵌套實現一大堆無效的工作,就會比不需要這種循環的界面運行得慢。由於理解本指導中的技巧十分重要,完全避免這種辣雞行為總是比讓Ren’Py最佳化你的項目代碼更好。
參數列表 link
為了實現最好的畫面效果,所有界面都應該定義一個參數列表。如果某個界面不使用參數,應該使用一個空的參數列表。這個界面:
screen test():
vbox:
for i in range(10):
text "[i]"
比下面那個運行得更快:
screen test:
vbox:
for i in range(10):
text "[i]"
當不使用參數列表定義一個界面時,界面使用的任何名稱都可以在界面顯示時被重定義。這要求Ren’Py分析界面時更加保守,限制最佳化級別。
預載入 link
提前預載入界面可以提升表現效果。因為Ren’Py在預載入時間內依然會執行界面,並載入界面使用的圖像。
總共有兩種Ren’Py自動預載入界面的方法:
Ren’Py會預載入通過
show screen
和call screen
語句顯示的界面。Ren’Py會預載入通過
Show()
和ShowMenu()
行為顯示的界面。
如果界面直接通過Python語句顯示,最好在顯示之前就開始預載入界面。開始預載入某個界面可以使用
renpy.start_predict_screen()
函數。停止預載入某個界面可以使用 renpy.stop_predict_screen()
函數。
可視組件的復用 link
當編譯解釋界面語言中創建某個可視組件時,Ren’Py會檢查固定位置參數和特性(property)是否與最近編譯解釋過的語句中有一樣的。如果存在同樣的可視組件,Ren’Py就不會創建新的,而是復用原來的。
可視組件的復用具有一系列性能表現方面的意義。它節省了創建新可視組件的消耗,很明顯新創建可視組件包含大量的內部環節。更為重要的是,很多情況下的可視組件復用,Ren’Py顯示時就不需要重新渲染了,可以導致另一項明顯的速度提升。
要比較固定位置參數和特性(property)時,Ren’Py使用的相等概念即Python內嵌的 “==” 操作符。我們已經擴展了行為(action)的相等概念,可以判斷兩個行為是否相等,也就是行為間沒有差異的情況——當無論哪個行為被調用,行為的需求和最終作用於組件的可用性和可選擇性都相同。
所有Ren’Py提供的行為(action)都符合該定義。當定義你自己的行為(action)時,也需要讓行為符契約樣的相等概念。可以通過提供一個合適的 __eq__
函數實現。例如:
class TargetShip(Action):
def __init__(self, ship):
self.ship = ship
def __eq__(self, other):
if not isinstance(other, TargetShip):
return False
return self.ship is other.ship
def __call__(self):
global target
target = self.ship
認真細緻地定義 __eq__
函數很重要,確保這個函數可以比較行為的所有欄位(field),並可以合適地使用相等符號(==)和標識(is)比較。
常表達式和純函數 link
Ren’Py可以檢索常變數和純函數的特性(property),並提升界面賦值式的運行速度,且避免界面某些部分的賦值運算。
定義 link
如果一個表達式計算結果總是同樣的值,那麼這個表達式就是 const 的(constant常量的縮寫)。出於Ren’Py的需要,只有在表達式計算結果為常量或者表達式被定義為常量時,表達式才是常表達式。
將任何一元,二元或三元運算符應用於表達式,前提是其他操作數也是常量。
訪問表達式上的欄位。
使用數字或對象為表達式建立索引。
Python的數值和字串都是常量,由常量數值和字串組成的列表、元組、集和字典類型數據也都是常量。Ren’Py中使用 define
語句將變數定義為常量。
renpy.const()
和 renpy.not_const()
函數可以用於在Ren’Py判斷是否為常量的問題上進行更細緻的操作。在下面的 常量名稱
中會列出預設的常量名。
如果你使用的某個變數從來不會改變,使用 define
將其定義和聲明為常量是合理的。例如:
define GRID_WIDTH = 20
define GRID_HEIGHT = 10
當某個可調用的函數、類(class)或者行為(action)的入參都是常量,返回值也始終是同一個常量,那麼這個函數、類或者行為就是 pure (純粹的)。除此之外,一個使用常表達式調用純函數的表達式也總是一個常表達式。
大量的默認函數、類和行為都已被標記為“pure”。這些函數會在後面的 純函數名 章節中列出。
函數可以使用 renpy.pure()
定義為純函數,並在默認儲存區中用作函數修飾器(decorator)。
常表達式和純函數在下列事件消息中不需要可以維護同一個值:
初始化段落的結尾。
語言的改變。
重建樣式。
如何使用常量最佳化界面語言 link
確保界面語言入參和特性(property)是常量,會帶來三點優勢。
第一點優勢是,常量入參和特性(property)在界面準備階段就可以計算和簡化,比如在初始化階段結束時、語言改變時、重建樣式時。在那之後,就不需要再浪費時間計算常量入參和特性了。
第二點優勢是,常量與可視組件復用功能的相性很好。如果某個可視組件的入參和特性都是常量,那麼這個可視組件就總是可以被復用的,獲得了可視組件復用的最大收益。
最後一點優勢是,當Ren’Py遇到可視組件的樹(tree),整個樹的所有入參、特性和作用主控流程的表達式都是常量的話,Ren’Py就不在重新計算表達式或創建新的可視組件,而會重用整個樹。這種規格的復用會帶來明顯的性能飛升。
例如,下列的界面不執行任何Python語句,僅在首次預載入或者顯示時創建可視組件:
screen mood_picker():
hbox:
xalign 1.0
yalign 0.0
textbutton "Happy" action SetVariable("mood", "happy")
textbutton "Sad" action SetVariable("mood", "sad")
textbutton "Angry" action SetVariable("mood", "angry")
常量文本 link
定義文本時請注意,包含新樣式文本替代的字串都是常量:
$ t = "Hello, world."
text "[t]"
直接包含某個文本變數的字串,一般不是常量:
$ t = "Hello, world."
text t
使用百分號替代格式的字串,也不是常量:
$ t = "Hello, world."
text "%s" % t
最後需要注意的是,文本多語言支持函數下劃線(_)是純函數,所以如果這個函數包含一個字串,整個表達式是常表達式:
text _("Your score is: [score]")
如果變數中包含文本替換,就需要使用 !i
轉義符:
$ who = "Jane"
$ t = "Hello, [who]!"
text 'Then I told her, "[t!i]"'
常函數 link
- renpy.const(name) link
將某個儲存區的變數聲明為常量。
如果沒有什麼可以改變一個變數的值,或者無法建立索引抵達變數,或者不能存取變數的各種屬性(attribute),那這個變數就是常量。變數必須在定義、初始化和多語言支持Python語句塊(block)之外保持一個常值。
- name
一個字串,表示聲明為常量的變數名。
- renpy.not_const(name) link
將某個儲存區的變數聲明不是常量。
這個函數會取消
renpy.const()
和renpy.pure()
的效果。- name
聲明不為常量的變數名。
- renpy.pure(fn) link
聲明某個函數為純函數。純函數必須在定義、初始化和多語言支持Python語句塊(block)之外總是使用同樣的入參並返回同樣的值。
- fn
聲明為純函數的函數名。可以是包含函數名的字串,或者函數本身。
返回 fn ,允許函數用作修飾器(decorator)。
性能分析 link
Ren’Py支持使用 renpy.profile_screen
函數對界面執行進行性能分析。
- class renpy.profile_screen(name, predict=False, show=False, update=False, request=False, time=False, debug=False, const=False) link
請求對名為 name 的界面進行界面分析, name 必須是個字串。
除了 name ,所有入參都必須以關鍵字入參形式提供。該函數使用三組入參。
- predict
若為真(True),在界面預載入時進行性能分析。
- show
若為真(True),在界面第一次顯示時進行性能分析。
- update
若為真(True),在界面更新時進行性能分析。
- request
若為真(True),在按下F8時進行性能分析。
第二組入參控制性能分析的輸出結果。
- time
若為真(True),Ren’Py會記錄界面運算消耗的時間。
- debug
若為真(True),Ren’Py會記錄界面運算的相關資訊,包括:
哪些可視組件被Ren’Py處理為常量。
哪些入參需要被運算。
哪些可視組件被復用。
產生和記錄這些除錯資訊會消耗可觀的時間。所以當 debug 為真(True)時,輸出的 time 時間應該就不是完全可信的。
最後一組入參控制每次Ren’Py運行時的輸出結果。
- const
顯示在界面中標記為常量和非常量的變數。
所有性能分析輸出都會記錄在遊戲目錄的profile_screen.txt文件中。
常量名 link
以下是預設的常量名:
False
None
True
config
style
純函數名 link
以下是預設為純函數或常量名。
ADVCharacter
ADVSpeaker
AddToSet()
Alpha
AlphaBlend()
AlphaDissolve()
AlphaMask()
AnimatedValue()
Animation
At()
AudioPositionValue()
Call()
Character()
Color
ComposeTransition()
ConditionSwitch()
Confirm()
CropMove()
DictInputValue()
DictValue()
DisableAllInputValues()
Dissolve()
Drag
DynamicCharacter
DynamicDisplayable()
EndReplay()
FactorZoom
Fade()
FieldInputValue()
FieldValue()
FileDelete()
FilePage()
FilePageNameInputValue()
FileTakeScreenshot()
Fixed()
Flatten()
FontGroup()
Frame()
Grid()
HBox()
Help()
Hide()
HideInterface()
If()
Image()
ImageDissolve()
ImageReference
InputValue
InvertSelected()
Jump()
Language()
LiveComposite()
LiveCrop()
LiveTile()
MainMenu()
MixerValue()
Motion
MouseMove()
Move
MoveFactory
MoveIn
MoveOut
MoveTransition()
Movie()
MultipleTransition()
NVLCharacter
Notify()
Null()
NullAction()
OldMoveTransition
OpenURL()
Pan
ParameterizedText()
Particles
Pause()
PauseAudio()
Pixellate()
Play
PlayCharacterVoice()
Position
Preference()
PushMove()
Queue()
QueueEvent()
QuickLoad()
QuickSave()
Quit()
RemoveFromSet()
Replay()
RestartStatement()
Return()
Revolve
RevolveInOut
RollForward()
Rollback()
RotoZoom
ScreenVariableValue()
Screenshot()
SelectedIf()
SensitiveIf()
SetCharacterVolume()
SetDict()
SetField()
SetMixer()
SetMute()
SetScreenVariable()
SetVariable()
SetVoiceMute()
Show()
ShowMenu()
ShowTransient()
ShowingSwitch()
SizeZoom
Skip()
SnowBlossom()
Solid()
Speaker
Start()
StaticValue()
Stop
StylePreference()
SubTransition
Text()
ToggleDict()
ToggleField()
ToggleMute()
ToggleScreen()
ToggleScreenVariable()
ToggleSetMembership()
ToggleVariable()
ToggleVoiceMute()
Transform
Update
VBox()
VariableInputValue()
VariableValue()
Viewport
VoiceReplay()
Window
Zoom
ZoomInOut
_()
_DisplayReset
_InputValueAction
_m1_00gallery__GalleryAction
_m1_00gallery__GalleryToggleSlideshow
_m1_00musicroom__MusicRoomPlay
_m1_00musicroom__MusicRoomRandomPlay
_m1_00musicroom__MusicRoomStop
_m1_00musicroom__MusicRoomTogglePlay
_m1_00preferences__DisplayAction
_movebottom
_moveleft
_moveright
_movetop
_narrator
_notify_transform
_p()
abs
all
any
apply
bin
blinds
bool
bytes
callable
centered
chr
cmp
color
dict
dissolve
divmod
fade
filter
float
frozenset
getattr
globals
gui.SetPreference()
gui.TogglePreference()
gui.preference()
hasattr
hash
hex
hpunch
int
irisin
irisout
isinstance
len
list
long
map
max
min
name_only
narrator
oct
ord
pixellate
pow
pushdown
pushleft
pushright
pushup
range
reduce
renpy.Keymap
renpy.ParameterizedText()
renpy.check_text_tags()
renpy.curried_call_in_new_context
renpy.curried_invoke_in_new_context
renpy.curry
renpy.easy_displayable
renpy.exists()
renpy.filter_text_tags()
renpy.fsdecode()
renpy.fsencode()
renpy.get_all_labels()
renpy.has_label()
renpy.has_screen
renpy.image_exists
renpy.image_size()
renpy.known_languages()
renpy.license
renpy.list_files()
renpy.loadable()
renpy.munged_filename
renpy.partial
renpy.unelide_filename
renpy.variant()
renpy.version()
renpy.version_name
renpy.version_only
renpy.version_string
renpy.version_tuple
repr
round
set
slideawaydown
slideawayleft
slideawayright
slideawayup
slidedown
slideleft
slideright
slideup
sorted
squares
str
sum
tuple
ui.callsinnewcontext
ui.gamemenus
ui.invokesinnewcontext
ui.jumps
ui.jumpsoutofcontext
ui.returns
unichr
unicode
vars
vcentered
vpunch
wipedown
wipeleft
wiperight
wipeup
zip
zoomin
zoominout
zoomout