# 原生界面修改

# 设计初衷

开发者在开发游戏玩法时,往往需要在游戏的原生界面上做一些自己的修改,可以通过获取原生界面的 ScreenNode 实例,并使用SDK接口去修改。

# 框架介绍

该框架涉及到两个类,NativeScreenManager,CustomUIScreenProxy,这两个类均可通过extraClientApi中对应API获取。NativeScreenManager提供开发者想要修改的原生界面的注册与反注册接口,通过注册接口注册后每当对应原生界面被push使都会生成对应的代理,CustomUIScreenProxy代理的是原生界面,它持有原生界面的ScreenNode实例以及其生命周期函数,调用反注册接口取消注册。

# 如何指定要修改的原生UI

如果只是代理界面,那就仅需要原生界面的全名即可,即界面的 namespace+"."+name,比如 "UIDemo.main" 这个界面,对应到UI Json文件中的namespace的值以及对应画布的名称。 如果代理的是原生界面中创建的控件,那么就不仅仅需要原生界面的全名,还需要指明代理控件生成的路径。下文对这两点展开说明。

# 如何获取原生界面的全名

原生界面的全名都是固定的(比如暂停界面就是"pause.pause_screen"),我们可以通过 PushScreenEvent 事件的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时返回失败
  • 示例

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

注意:

  • 对原生界面中的某个原生控件(原生控件本来就有的,非用户创建的),进行回调绑定(比如 ButtonUIControl 的 SetButtonTouchDownCallback 接口)或者数据绑定,是会导致该控件原有的逻辑丢失的,除非有特殊需求,否则建议是对自己在原生界面创建的控件进行回调或者数据绑定,而不要在原有控件上做这一事情。

  • 由于是在原生界面上进行SDK接口调用,而大部分的SDK接口是需要控件路径作为参数的(比如 ScreenNode 的 GetBaseUIControl 接口),鉴于随着版本变动,一些原生界面会有所更新,导致部分控件的路径发生变化,所以开发者如果遇到原本可以获取的路径在某个版本后变成无法获取控件的情况,可以使用 UI调试工具 查看是否路径发生了改变。

  • 示例

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

  • 描述

    每帧调用的生命周期函数

  • 参数

  • 返回值