Files
netease-modsdk-wiki/docs/mcguide/18-界面与交互/61-原生界面修改文档.md
2025-03-18 14:46:12 +08:00

221 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
front:
hard: 入门
time: 分钟
---
# 原生界面修改
## 设计初衷
开发者在开发游戏玩法时,往往需要在游戏的原生界面上做一些自己的修改,可以通过[UI说明文档-修改其他界面](30-UI说明文档.md#修改其他界面)的方式修改,或者通过获取原生界面的 [ScreenNode](40-UIAPI文档.md#screennode) 实例并使用SDK接口去修改。
## 框架介绍
该框架涉及到两个类NativeScreenManagerCustomUIScreenProxy这两个类均可通过extraClientApi中对应API获取。NativeScreenManager提供开发者想要修改的原生界面的注册与反注册接口通过注册接口注册后每当对应原生界面被push使都会生成对应的代理CustomUIScreenProxy代理的是原生界面它持有原生界面的ScreenNode实例以及其生命周期函数调用反注册接口取消注册。
### 如何指定要修改的原生UI
如果只是代理界面,那就仅需要原生界面的全名即可,即界面的 namespace+"."+name比如 "UIDemo.main" 这个界面对应到UI Json文件中的namespace的值以及对应画布的名称。
如果代理的是原生界面中创建的控件,那么就不仅仅需要原生界面的全名,还需要指明代理控件生成的路径。下文对这两点展开说明。
#### 如何获取原生界面的全名
原生界面的全名都是固定的(比如暂停界面就是"pause.pause_screen"),我们可以通过 <a href="../../mcdocs/1-ModAPI/事件/UI.html#pushscreenevent" rel="noopenner"> PushScreenEvent </a> 事件的screenDef参数获取。
```
import client.extraClientApi as clientApi
class TutorialClientSystem(ClientSystem):
# 客户端System的初始化函数
def __init__(self, namespace, systemName):
super(TutorialClientSystem, self).__init__(namespace, systemName)
self.ListenForEvent(clientApi.GetEngineNamespace(),
clientApi.GetEngineSystemName(), "PushScreenEvent", self, self.onPushScreen)
def onPushScreen(self, args):
# 打印出当前弹出界面的全名,
# 比如在PC按 esc 键弹出暂停界面的时候就能知道暂停界面的全名是 "pause.pause_screen"
print args['screenDef']
```
### 注册与反注册接口
#### RegisterScreenProxy
- 描述
注册对应原生界面的委托类注册成功后每次原生界面被创建时委托就会被创建并获取到原生界面的ScreenNode实例关闭后委托销毁。
- 参数
| 参数名 | 数据类型 | 说明 |
| :--- | :--- | :--- |
| screenName | str | 界面的全名(包括原生以及自定义的界面),一般由命名空间+"."+画布名称,如 "UIDemo.main",另外的获取方式可以见上文说明 |
| proxyClassName | str | 继承自CustomUIScreenProxy的自定义代理类模块路径 |
- 返回值
| 数据类型 | 说明 |
| :--- | :--- |
| bool | 是否注册成功。重复注册同一个代理类找不到类或者非继承自CustomUIScreenProxy时返回失败 |
- 示例
```python
import client.extraClientApi as clientApi
ClientSystem = clientApi.GetClientSystemCls()
NativeScreenManager = clientApi.GetNativeScreenManagerCls()
class TutorialClientSystem(ClientSystem):
def __init__(self, namespace, systemName):
ClientSystem.__init__(self, namespace, systemName)
# 给暂停界面注册委托,类为 tutorialScripts.proxys.PauseScreenProxy.PauseScreenProxy
NativeScreenManager.instance().RegisterScreenProxy(
"pause.pause_screen", "tutorialScripts.proxys.PauseScreenProxy.PauseScreenProxy"
)
```
#### UnRegisterScreenProxy
- 描述
取消注册,取消后生成原生界面时将不再生成对应的委托。
- 参数
| 参数名 | 数据类型 | 说明 |
| :--- | :--- | :--- |
| screenName | str | 界面的全名参考RegisterScreenProxy接口 |
| proxyClassName | str | 继承自CustomUIScreenProxy的自定义代理类模块路径 |
- 返回值
### CustomUIScreenProxy
CustomUIScreenProxy是原生界面的代理类可通过extraClientApi.GetUIScreenProxyCls()获得。它持有生成在原生UI中自定义UI的BaseUIControl实例以及其生命周期函数但在其生命周期函数中不进行任何操作开发者可继承该类进行自定义逻辑编写并将自定义类的模块路径传入注册接口进行注册当原生界面创建完成后开发者在自定义类中重写的生命周期函数就会被调用。
既然是ScreenNode的代理同样它也会支持@ViewBinder.binding注解的支持允许对代理界面上的控件进行数据绑定关于这一点可以查看示例 [NativeUIDemoMod](../20-玩法开发/13-模组SDK编程/60-Demo示例.md#NativeUIDemoMod)。
注意:
- 对原生界面中的某个原生控件(原生控件本来就有的,非用户创建的),进行回调绑定(比如 ButtonUIControl 的 <a href="../../mcdocs/1-ModAPI/接口/自定义UI/UI控件.html#setbuttontouchdowncallback" rel="noopenner"> SetButtonTouchDownCallback </a> 接口)或者数据绑定,是会导致该控件原有的逻辑丢失的,除非有特殊需求,否则建议是对自己在原生界面创建的控件进行回调或者数据绑定,而不要在原有控件上做这一事情。
- 由于是在原生界面上进行SDK接口调用而大部分的SDK接口是需要控件路径作为参数的比如 ScreenNode 的 <a href="../../mcdocs/1-ModAPI/接口/自定义UI/UI界面.html?catalog=1#getbaseuicontrol" rel="noopenner"> GetBaseUIControl </a> 接口),鉴于随着版本变动,一些原生界面会有所更新,导致部分控件的路径发生变化,所以开发者如果遇到原本可以获取的路径在某个版本后变成无法获取控件的情况,可以使用<a href="../30-测试/4-UI调试工具.html" rel="noopenner"> UI调试工具 </a>查看是否路径发生了改变。
- 示例
```python
import client.extraClientApi as clientApi
CustomUIScreenProxy = clientApi.GetUIScreenProxyCls()
class PauseScreenProxy(CustomUIScreenProxy):
def __init__(self, screenName, screenNode):
CustomUIScreenProxy.__init__(self, screenName, screenNode)
def OnCreate(self):
print("PauseScreenProxy Create")
def OnDestroy(self):
print("PauseScreenProxy Destroy")
def OnTick(self):
print("PauseScreenProxy Tick")
@ViewBinder.binding(ViewBinder.BF_ToggleChanged, "#myMod.myToggle")
def onToggleChanged(self, args):
"""
proxy也支持binding这里展示的是绑定一类toggle这类toggle甚至可以不存在这类toggle满足"toggle_name"属性是"#myMod.myToggle"这一要求,当他们发生状态改变的时候就会调用该函数。
"""
print("myToggle onToggleChanged: {}".format(args))
```
#### GetScreenName
- 描述
获得所代理的界面的全名
- 参数
- 返回值
| 数据类型 | 说明 |
| :--- | :--- |
| str | 代理的界面的全名 |
#### GetScreenNode
- 描述
获得所代理的界面的ScreenNode实例
- 参数
- 返回值
| 数据类型 | 说明 |
| :--- | :--- |
| ScreenNode | 代理的界面的ScreenNode实例 |
#### OnCreate
- 描述
原生界面打开时,创建代理后调用的生命周期函数
- 参数
- 返回值
#### OnDestroy
- 描述
当原生界面关闭时代理销毁后调用的生命周期函数
- 参数
- 返回值
#### OnTick
- 描述
每帧调用的生命周期函数
- 参数
- 返回值