Chameleon Tool中的Python代码执行顺序¶
在Chameleon Tool中,我们的Python代码会出现了多个不同的位置:
- JSON 文件中的
"InitPyCmd"
- JSON 文件中的
"OnClosePyCmd"
- Python工具类的构造函数
__init__()
- 控件上的回调函数中的代码,比如SEditableText等控件上的
OnTextChanged
实际执行顺序¶
通过菜单项或者 unreal.ChameleonData.launch_chameleon_tool(json_path)
打开工具后,以下事件会依次触发:
- TAPython插件通过C++代码创建用户在JSON中定义的界面
- 界面控件中的部分回调被触发
- 调用JSON文件中的
"InitPyCmd"
(其中会创建Python工具类的实例) - Python工具类的构造函数
__init__()
1 注意:Python工具类都继承自单例,所以它们的构造函数只会被调用一次,除非它们所在的模块被reload "InitPyCmd"
中,"创建"工具类实例之后的代码"OnClosePyCmd"
中的代码
注意:¶
- 我们通常会在控件的回调函数调用Python工具实例中的方法(顺序2)。但当第一次打开工具时,工具的实例是不存在的(顺序4执行之后才存在)。此时需要加上判断
"OnTextChanged": "if 'chameleon_inst' in globals()\n\tchameleon_inst.do_something()"
同理,SComboBox.OnSelectionChanged 在第一次运行时,也会因为chameleon_inst
还没被赋值而报错。
"OnSelectionChanged": "if 'chameleon_inst' in globals():\n\chameleon_inst.do_something()
- 用户可以在Python工具实例中持有某个UObject的引用,并且关闭界面后,工具实例并没有被删除。99%情况下这种情况是我们想要的,我们不会丢失工具中的数据。但在切换UE的关卡时,持有之前场景中的物体的引用就会带来问题。这个时候就可以在
"OnClosePyCmd"
添加清理对象的操作。
例如:ObjectDetailViewer中, 我在"OnClosePyCmd"中调用了on_close()
方法,并在其中清理持有的对象引用。
"OnClosePyCmd": "chemeleon_objectDetailViewer.on_close()",
注意 MenuConfig.json 中的Python代码¶
如果MenuConfig.json中的一个菜单配置项中,同时出现了"command"
和"ChameleonTools"
字段,那么只有"command"
会被执行,"ChameleonTools"
中的内容会被忽略。
优先级¶
"command"
高于 "ChameleonTools"
{
"name": "Chameleon Minimal Example",
"ChameleonTools": "../Python/Example/MinimalExample.json",
"command": "print('command called.')"
},
...
线程¶
Rule 1: 任何对界面部分的操作,必须在主线程中执行。如果在其他线程中执行,会导致UE崩溃。
当我们在处理一些比较慢的任务,比如网络相关的任务,为了不卡住UE 编辑器,通常我们会在python中启动一个线程,执行相关的任务。通常在任务结束之后,我们会需要更新界面中的内容。此时,如果之间在python中调用修改界面的API,就会导致UE崩溃。
def on_button_click(self):
self.thread = threading.Thread(target=self.some_slow_task)
self.thread.start()
解决的方法是把更新界面的操作放到主线程中执行。这个时候,我们有至少两个选择:
- 在Chameleon工具的Python代码的OnTick中检查是否有需要更新的任务,如果有,就更新界面
- 通过unreal.PythonBPLib.execute_python_command()来执行更新界面的代码,并传入参数
force_game_thread = True
下面的例子中,我们使用了第二种方法:
class ThreadExample(metaclass=Singleton):
def __init__(self, jsonPath:str):
self.jsonPath = jsonPath
self.data = unreal.PythonBPLib.get_chameleon_data(self.jsonPath)
self.ui_output = "InfoOutput"
self.clickCount = 0
self.thread = None
def show_result(self):
self.data.set_text(self.ui_output, "Clicked {} time(s)".format(self.clickCount))
def some_slow_task(self):
time.sleep(1) # fake slow task
unreal.PythonBPLib.exec_python_command("chameleon_thread_example.show_result()", force_game_thread=True)
def on_button_click(self):
if self.thread is None or not self.thread.is_alive():
self.clickCount += 1
self.data.set_text(self.ui_output, "Pending")
self.thread = threading.Thread(target=self.some_slow_task)
self.thread.start()
else:
... # skipped, avoid multiple execution
注意execute_python_command
中的第一参数是一个字符串,而不是一个函数。这个字符串会被当作Python代码执行。类似与在JSON文件中指定的"InitPyCmd"
,"ConClick"
等。
当force_game_thread=True
时,实际会启动一个AsyncTask,并把Python代码放到主线程(GameThread)中执行。
NOTEforce_game_thread
选项在TAPython 1.0.12中添加。如果你使用的是旧版本的TAPython,那么你需要手动更新TAPython插件。
SComboBox.OnSelectionChanged 在第一次运行时,会报错
"OnSelectionChanged": "if 'chameleon_stable_diffusion' in globals():\n\tchameleon_stable_diffusion.on_change_sampler(%)"