import { Asset, AssetManager, Camera, Canvas, Component, Event, Layers, Node, Prefab, RenderTexture, ResolutionPolicy, Scene, SceneAsset, Settings, UITransform, Widget, _decorator, director, instantiate, isValid, js, screen, settings, size, view } from 'cc'; import { DEV } from 'cc/env'; import { IMiniViewName, IViewName } from '../../../../../assets/app-builtin/app-admin/executor'; import Core from '../../Core'; import BaseManager from '../../base/BaseManager'; import BaseView, { IHideParamOnHide, IShade, IShowParamAttr, IShowParamOnHide, IShowParamOnShow, IViewType, ViewType } from '../../base/BaseView'; import UIMgrLoading from './comp/UIMgrLoading'; import UIMgrShade from './comp/UIMgrShade'; import UIMgrToast from './comp/UIMgrToast'; import UIMgrZOrder from './comp/UIMgrZOrder'; const { ccclass, property } = _decorator; interface IShowParams { /**UI名 */ name: T, /** * 数据 * - 被onShow接收 */ data?: IShow, /** * 是否将UI显示在最上 * - 默认true */ top?: boolean, /** * 队列模式,一个UI关闭后,是否展示下一个UI * - join: 排队 * - jump: 插队(到首位) */ queue?: 'join' | 'jump', /**静默 默认false(不显示加载loading,也不屏蔽触摸) */ silent?: boolean, /**UI触发onShow后 */ onShow?: IShowParamOnShow, /**UI触发onHide后 */ onHide?: IShowParamOnHide, /**当code的值为ErrorCode.LogicError时,如果返回true,则自动重试 */ onError?: (result: string, code: ErrorCode) => true | void, /** * @private * @deprecated */ attr?: IShowParamAttr, } interface IShowAsyncParams { /**UI名 */ name: T, /** * 数据 * - 被onShow接收 */ data?: IShow, /** * 是否将UI显示在最上 * - 默认true */ top?: boolean, /** * 队列模式,一个UI关闭后,是否展示下一个UI * - join: 排队 * - jump: 插队(到首位) */ queue?: 'join' | 'jump', /**静默 默认false(不显示加载loading,也不屏蔽触摸) */ silent?: boolean, /**UI触发onShow后 */ onShow?: IShowParamOnShow, /**当code的值为ErrorCode.LogicError时,如果返回true,则自动重试 */ onError?: (result: string, code: ErrorCode) => true | void, /** * @private * @deprecated */ attr?: IShowParamAttr, } interface IHideParams { name: T, data?: IHide, onHide?: IHideParamOnHide } const UIScene = 'UIScene'; const UserInterfacePath = 'UserInterface'; const ViewTypes = [ViewType.Page, ViewType.Paper, ViewType.Pop, ViewType.Top]; const BlockEvents = [ Node.EventType.TOUCH_START, Node.EventType.TOUCH_MOVE, Node.EventType.TOUCH_END, Node.EventType.TOUCH_CANCEL, Node.EventType.MOUSE_DOWN, Node.EventType.MOUSE_MOVE, Node.EventType.MOUSE_UP, Node.EventType.MOUSE_ENTER, Node.EventType.MOUSE_LEAVE, Node.EventType.MOUSE_WHEEL ]; /** * 错误码 */ enum ErrorCode { /**加载失败 */ LoadError, /**beforeShow返回错误 */ LogicError, /**UI无效(UI的isViewValid返回false) */ InvalidError, } /** * 界面名字枚举 */ const ViewName: { [key in IViewName]: key } = new Proxy({} as any, { get: function (target, key) { if (target[key]) return target[key]; target[key] = key; return key; } }); /** * 子界面名字枚举 */ const MiniViewName: { [key in IMiniViewName]: key } = new Proxy({} as any, { get: function (target, key) { if (target[key]) return target[key]; target[key] = key; return key; } }); /** * 将驼峰命名转成串式命名 * @param str 驼峰字符串 * @returns */ function stringCaseNegate(str: string) { return str.replace(/[A-Z]/g, (searchStr, startIndex) => { if (startIndex === 0) { return searchStr.toLowerCase(); } else { return '-' + searchStr.toLowerCase(); } }); } @ccclass('UIManager') export default class UIManager extends BaseManager { /**静态设置 */ static setting: { /**预加载列表,会在UI初始化阶段进行 */ preload?: (IViewName | IMiniViewName | Array)[], /**默认UI,框架初始化完毕后会自动加载 */ defaultUI?: IViewName, /**给默认UI传递的数据 */ defaultData?: any, /**弹窗背景遮罩的参数 */ shade?: IShade, /** * 是否自动适配分辨率策略 * - 开启后会弃用当前的适配策略,并根据实际设备分辨率与设计分辨率的比值,计算出新的适配策略(宽适配或高适配),保证游戏区域不会被裁减只会扩边 * - 当实际设备分辨率「高/宽」>= 设计分辨率「高/宽」时,为宽适配 * - 当实际设备分辨率「高/宽」< 设计分辨率「高/宽」时,为高适配 */ autoFit?: boolean, } = {}; /**错误码 */ static ErrorCode = ErrorCode; /**界面名字枚举 */ static ViewName = ViewName; /**子界面名字枚举 */ static MiniViewName = MiniViewName; @property({ type: Prefab, tooltip: '位置: app://manager/ui/prefab/UIMgrLoading' }) private loadingPre: Prefab = null; @property({ type: Prefab, tooltip: '位置: app://manager/ui/prefab/UIMgrShade' }) private shadePre: Prefab = null; @property({ type: Prefab, tooltip: '位置: app://manager/ui/prefab/UIMgrToast' }) private toastPre: Prefab = null; // UI根节点 private UserInterface: Node = null; // 加载和遮罩节点 private loading: Node = null; private shade: Node = null; private toast: Node = null; private defaultUI: UIName = null; private defaultData: string = ''; private currScene: string = ''; private currPage: BaseView = null; private currFocus: BaseView = null; // 预制体缓存 private prefabCache: { [name in string]: Prefab } = {}; private sceneCache: { [name in string]: SceneAsset } = {}; // 全局触摸有效 private touchEnabled: boolean = true; // 记录触摸屏蔽 private touchMaskMap = new Map(); // 记录展示加载 private showLoadingMap = new Map(); // 记录正在加载中的有效的ui private uiLoadingMap: Map = new Map(); // 记录正在展示中的有效的ui private uiShowingMap: Map = new Map(); private showQueue: IShowParams[] = []; /**UI根节点 */ public get root() { return this.node.parent.parent; } /**相机 */ public get camera() { return this.canvas.cameraComponent; } /**画布*/ public get canvas() { return this.root.getComponent(Canvas); } protected init(finish: Function) { const setting = UIManager.setting; this.defaultUI = setting.defaultUI as UIName; this.defaultData = setting.defaultData; super.init(finish); // 预加载,符合AnyTask规则 if (setting.preload?.length) { const task = Core.inst.lib.task.createAny(); setting.preload.forEach((preload) => { if (preload instanceof Array) { task.add(preload.map(name => { return next => this.preload(name as any, next); })); } else { task.add(next => this.preload(preload as any, next)); } }); task.start(); } } protected onLoad() { this.UserInterface = this.root.getChildByName(UserInterfacePath); this.root.getComponentsInChildren(Camera).forEach(camera => { // 避免camera.priority<0的情况,否则会造成渲染异常 if (camera.priority < 0) camera.priority = 0; // 避免camera.far<=camera.near的情况,否则会造成渲染异常 if (camera.far <= camera.near) camera.far = camera.near + 1; }); director.addPersistRootNode(this.root); this.createTypeRoot(); this.shade = instantiate(this.shadePre); this.shade.parent = this.UserInterface; this.shade.active = false; this.shade.getComponent(Widget).target = this.root; this.loading = instantiate(this.loadingPre); this.loading.parent = this.node; this.loading.active = false; // toast是后面加的,需要做容错 if (this.toastPre) { this.toast = instantiate(this.toastPre); this.toast.parent = this.node; } // 自动适配分辨率策略 if (UIManager.setting.autoFit) { const designResolution = settings.querySettings(Settings.Category.SCREEN, 'designResolution') as { width: number, height: number, policy: number }; const windowSize = size(screen.windowSize); let resolutionPolicy = designResolution.policy; const autoFitResolutionPolicy = function () { if (windowSize.width / windowSize.height > designResolution.width / designResolution.height) { if (resolutionPolicy === ResolutionPolicy.FIXED_HEIGHT) return; view.setResolutionPolicy(ResolutionPolicy.FIXED_HEIGHT); resolutionPolicy = ResolutionPolicy.FIXED_HEIGHT; } else { if (resolutionPolicy === ResolutionPolicy.FIXED_WIDTH) return; view.setResolutionPolicy(ResolutionPolicy.FIXED_WIDTH); resolutionPolicy = ResolutionPolicy.FIXED_WIDTH; } }; autoFitResolutionPolicy(); this.schedule(() => { if (windowSize.equals(screen.windowSize)) return; windowSize.set(screen.windowSize); autoFitResolutionPolicy(); }, 0.5); } } private createTypeRoot() { ViewTypes.forEach((type) => { const d2 = new Node(type); d2.layer = Layers.Enum.UI_2D; d2.addComponent(UIMgrZOrder); d2.parent = this.UserInterface; d2.addComponent(UITransform); const widget = d2.addComponent(Widget); widget.isAlignBottom = true; widget.isAlignLeft = true; widget.isAlignRight = true; widget.isAlignTop = true; widget.top = 0; widget.left = 0; widget.right = 0; widget.bottom = 0; widget.alignMode = Widget.AlignMode.ON_WINDOW_RESIZE; if (DEV) { d2.on(Node.EventType.CHILD_ADDED, (child: Node) => { if (!child) return; if (child === this.shade) return; if (this.getBaseView(child)) return; this.warn(`${UserInterfacePath}/${type}下非必要请不要添加非UI节点:`, child?.name); }, this); } }); } private addTouchMaskListener() { if (!this.touchEnabled) return; if (this.touchMaskMap.size > 0) return; for (let i = 0; i < BlockEvents.length; i++) { this.root.on(BlockEvents[i], this.stopPropagation, this, true); } } private removeTouchMaskListener() { if (!this.touchEnabled) return; if (this.touchMaskMap.size > 0) return; for (let i = 0; i < BlockEvents.length; i++) { this.root.off(BlockEvents[i], this.stopPropagation, this, true); } } private stopPropagation(event: Event) { if (!this.touchEnabled || this.touchMaskMap.size > 0) { event.propagationStopped = true; if (event.type !== Node.EventType.MOUSE_MOVE) { this.log('屏蔽触摸'); } } } /** * 获取一个节点上的BaseView组件, 获取不到返回null */ private getBaseView(node: Node): BaseView { if (!node) return null; return node.components.find(component => component instanceof BaseView) as BaseView; } /** * 在所有父节点中找到一个最近的view组件 * @param target * @returns */ private getViewInParents(target: Node) { let node = target; let com: BaseView = null; while (node.parent && !(node.parent instanceof Scene)) { com = this.getBaseView(node.parent); if (!com) { node = node.parent; } else { break; } } return com; } /** * 在子节点中找到一个最近的view组件 * @param target * @returns */ private getViewInChildren(target: Node) { for (let index = 0; index < target.children.length; index++) { const node = target.children[index]; const com = this.getBaseView(node); if (com) return com; } return null; } /** * 根据UI名字获取它的脚本类 */ private getUIClass(name: string): typeof BaseView { return js.getClassByName(name) as (typeof BaseView); } /** * 根据UI名字获取UI路径 * @param name ui名字 * @returns */ private getUIPath(name: string) { return name; } /** * 获取前缀 * @param name ui名字 */ private getUIPrefix(name: string): ViewType { for (let index = 0; index < ViewTypes.length; index++) { const typeName = ViewTypes[index]; if (name.indexOf(typeName) === 0) { return typeName; } } this.error('getUIPrefix', `${name}`); } /** * 根据UI名字查询父节点 * @param name ui名字 */ private getUIParent(name: string): Node { if (this.currScene === name) { return director.getScene(); } const prefix = this.getUIPrefix(name); for (let index = 0; index < ViewTypes.length; index++) { const viewType = ViewTypes[index]; if (viewType === prefix) { return this.UserInterface.getChildByName(viewType); } } this.error('getUIParent', `找不到${name}对应的Parent`); return null; } /** * 根据UI名字获取场景内的节点 * @param name ui名字 */ private getUIInScene(name: string): Node; private getUIInScene(name: string, multiple: false): Node; private getUIInScene(name: string, multiple: true): Node[]; private getUIInScene(name: string, multiple = false) { const parent = this.getUIParent(name); if (multiple) { const result = parent.children.filter(node => node.name === name); if (result.length) return result.filter(node => isValid(node, true)); } else { const result = parent.children.find(node => node.name === name); if (result) return isValid(result, true) ? result : null; } return multiple ? [] : null; } /** * 根据UI名字获取展示中的节点 * @param name ui名字 */ private getUIInShowing(name: string): Node; private getUIInShowing(name: string, multiple: false): Node; private getUIInShowing(name: string, multiple: true): Node[]; private getUIInShowing(name: string, multiple = false) { if (multiple) { const result: Node[] = []; this.uiShowingMap.forEach((_name, com) => { if (_name === name) result.push(com.node); }); return result; } else { let result: Node = null; this.uiShowingMap.forEach((_name, com) => { if (!result && _name === name) result = com.node; }); return result; } } /** * 获取UI骨架Bundle名字 * @deprecated 将会移除,请改为其它方案 */ public getNativeBundleName(uiName: UIName | MiniName) { // 兼容旧版本 const oldBundleName = `app-view_${uiName}`; const projectBundles = settings.querySettings(Settings.Category.ASSETS, 'projectBundles') as string[]; if (projectBundles && projectBundles.indexOf(oldBundleName) >= 0) { return oldBundleName; } return stringCaseNegate(uiName); } /** * 获取UI资源Bundle名字 * @deprecated 将会移除,请改为其它方案 */ public getResBundleName(uiName: UIName | MiniName) { // 兼容旧版本 const oldBundleName = `app-view_${uiName}_Res`; const projectBundles = settings.querySettings(Settings.Category.ASSETS, 'projectBundles') as string[]; if (projectBundles && projectBundles.indexOf(oldBundleName) >= 0) { return oldBundleName; } return `${stringCaseNegate(uiName)}-res`; } /** * 初始化Bundle */ private initBundle(name: UIName | MiniName, onFinish: (result: [AssetManager.Bundle, AssetManager.Bundle]) => any) { Core.inst.lib.task.createASync<[AssetManager.Bundle, AssetManager.Bundle]>() .add((next) => { Core.inst.manager.loader.loadBundle({ bundle: this.getNativeBundleName(name), onComplete: next }); }) .add((next) => { Core.inst.manager.loader.loadBundle({ bundle: this.getResBundleName(name), onComplete: next }); }) .start(onFinish); } /** * 安装UI */ private installUI(name: UIName | MiniName, complete?: (result: Prefab | SceneAsset | null) => any, progress?: (finish: number, total: number, item: AssetManager.RequestItem) => void) { if (this.sceneCache[name]) { complete && setTimeout(() => { if (!isValid(this)) return; complete(this.sceneCache[name]); }); return; } else if (this.prefabCache[name]) { complete && setTimeout(() => { if (!isValid(this)) return; complete(this.prefabCache[name]); }); return; } const task = Core.inst.lib.task.createSync<[[AssetManager.Bundle, AssetManager.Bundle], Prefab | SceneAsset]>() .add(next => { this.initBundle(name, next); }) .add((next) => { // 失败 const uiBundles = task.results[0]; if (!uiBundles || !uiBundles[0] || !uiBundles[1]) return next(null); const isScene = uiBundles[0].getSceneInfo(name); Core.inst.manager.loader.load({ bundle: this.getNativeBundleName(name), path: this.getUIPath(name), type: isScene ? SceneAsset : Prefab, onProgress: progress, onComplete: next }); }) .start((results) => { if (!isValid(this)) return; // 验证缓存 const cache = this.sceneCache[name] || this.prefabCache[name]; if (cache) { return complete && complete(cache); } // 验证有效 const asset = results[1]; if (!asset) { return complete && complete(null); } // 添加引用计数 asset.addRef(); // 添加缓存 if (asset instanceof Prefab) { this.prefabCache[name] = asset; } else { this.sceneCache[name] = asset; } this.log(`加载: ${name}`); return complete && complete(asset); }); } /** * 卸载UI */ private uninstallUI(name: UIName | MiniName) { if (this.sceneCache[name]) { // 释放引用计数 this.sceneCache[name].decRef(); // 删除缓存 delete this.sceneCache[name]; } else if (this.prefabCache[name]) { // 释放引用计数 this.prefabCache[name].decRef(); // 删除缓存 delete this.prefabCache[name]; } const resBundle = this.getResBundleName(name); const naBundle = this.getNativeBundleName(name); Core.inst.manager.loader.releaseAll(resBundle); Core.inst.manager.loader.releaseAll(naBundle); Core.inst.manager.loader.removeBundle(resBundle); Core.inst.manager.loader.removeBundle(naBundle); this.log(`卸载: ${name}`); } /** * 加载ui内部资源 */ public loadRes(target: Component, path: string, type: T, callback?: (item: InstanceType | null) => any) { if (typeof target === 'string') { Core.inst.manager.loader.load({ bundle: this.getResBundleName(target), path: path, type: type, onComplete: callback }); } else { const view = this.getBaseView(target.node) || this.getViewInParents(target.node) || this.getViewInChildren(director.getScene()); if (view) { Core.inst.manager.loader.load({ bundle: this.getResBundleName(view.viewName as UIName | MiniName), path: path, type: type, onComplete: callback }); } else { this.error('loadRes', target.name, path); callback && callback(null); } } } /** * 预加载ui内部资源 */ public preloadRes(target: Component | UIName | MiniName, path: string, type: T, complete?: (item: AssetManager.RequestItem[] | null) => any) { if (typeof target === 'string') { Core.inst.manager.loader.preload({ bundle: this.getResBundleName(target), path: path, type: type, onComplete: complete }); } else { const view = this.getBaseView(target.node) || this.getViewInParents(target.node) || this.getViewInChildren(director.getScene()); if (view) { Core.inst.manager.loader.preload({ bundle: this.getResBundleName(view.viewName as UIName | MiniName), path: path, type: type, onComplete: complete }); } else { this.error('preloadRes', target.name, path); } } } /** * 加载ui内部资源 */ public loadResDir(target: Component, path: string, type: T, callback?: (items: InstanceType[] | null) => any) { if (typeof target === 'string') { Core.inst.manager.loader.loadDir({ bundle: this.getResBundleName(target), path: path, type: type, onComplete: callback }); } else { const view = this.getBaseView(target.node) || this.getViewInParents(target.node) || this.getViewInChildren(director.getScene()); if (view) { Core.inst.manager.loader.loadDir({ bundle: this.getResBundleName(view.viewName as UIName | MiniName), path: path, type: type, onComplete: callback }); } else { this.error('loadResDir', target.name, path); callback && callback([]); } } } /** * 预加载ui内部资源 */ public preloadResDir(target: Component | UIName | MiniName, path: string, type: T, complete?: (item: AssetManager.RequestItem[] | null) => any) { if (typeof target === 'string') { Core.inst.manager.loader.preloadDir({ bundle: this.getResBundleName(target), path: path, type: type, onComplete: complete }); } else { const view = this.getBaseView(target.node) || this.getViewInParents(target.node) || this.getViewInChildren(director.getScene()); if (view) { Core.inst.manager.loader.preloadDir({ bundle: this.getResBundleName(view.viewName as UIName | MiniName), path: path, type: type, onComplete: complete }); } else { this.error('preloadResDir', target.name, path); } } } /** * 预加载UI */ public preload(name: UIName | MiniName, complete?: (item: AssetManager.RequestItem[] | null) => any) { // 验证name是否为真 if (!name) { this.error('preload', 'fail'); complete && setTimeout(() => { if (!isValid(this)) return; complete(null); }); return; } this.initBundle(name, ([naBundle]) => { const isScene = naBundle.getSceneInfo(name); Core.inst.manager.loader.preload({ bundle: this.getNativeBundleName(name), path: this.getUIPath(name), type: isScene ? SceneAsset : Prefab, onComplete: complete }); }); } /** * 加载UI */ public load(name: UIName | MiniName): void; public load(name: UIName | MiniName, complete: (result: Prefab | SceneAsset | null) => any): void; public load(name: UIName | MiniName, progress: (finish: number, total: number, item: AssetManager.RequestItem) => void, complete: (result: Prefab | SceneAsset | null) => any): void; public load(name: UIName | MiniName, ...args: Function[]): void { const progress = (args[1] && args[0]) as (finish: number, total: number, item: AssetManager.RequestItem) => void; const complete = (args[1] || args[0]) as (result: any) => any; // 验证name是否为真 if (!name) { this.error('load', 'fail'); complete && setTimeout(() => { if (!isValid(this)) return; complete(null); }); return; } // 异步加载 this.installUI(name, (result) => { if (!result) return complete && complete(null); return complete && complete(result); }, progress); } /** * 销毁UI,释放资源 * - 直接销毁,不管是否是show状态 * - 此流程一定是同步的 */ public release(nameOrCom: UIName | MiniName | BaseView) { const uiName = typeof nameOrCom === 'string' ? nameOrCom : nameOrCom.viewName; if (!uiName) { this.error('release', `${nameOrCom} fail`); return; } // 传入字符串是释放所有 if (typeof nameOrCom === 'string') { this.getUIInScene(uiName, true).forEach((node) => { const com = this.getBaseView(node); if (!com) { this.error('release', `${uiName}不存在BaseView组件`); return; } if (com.isShow) { this.warn('release', `${uiName}正处于show状态, 此处将直接销毁`); } if (com === this.currPage) { this.currPage = null; } if (com === this.currFocus) { this.currFocus = null; } this.uiShowingMap.delete(com); if (node && isValid(node, true)) { node.parent = null; node.destroy(); } }); } // 传入组件是释放单个 else { if (nameOrCom.isShow) { this.warn('release', `${uiName}正处于show状态, 此处将直接销毁`); } if (nameOrCom === this.currPage) { this.currPage = null; } if (nameOrCom === this.currFocus) { this.currFocus = null; } this.uiShowingMap.delete(nameOrCom); const node = nameOrCom.node; if (node && isValid(node, true)) { node.parent = null; node.destroy(); } } // 当全部释放时才清除缓存 const nodes = this.getUIInScene(uiName, true); if (nodes.length === 0 || nodes.every(node => !isValid(node, true))) { this.uninstallUI(uiName as UIName | MiniName); this.log(`释放资源: ${uiName}`); } } /** * 销毁全部UI,释放资源 * - 直接销毁,不管是否是show状态 * - 此流程一定是同步的 */ public releaseAll(exclude?: UIName[]) { Object.keys(this.prefabCache).forEach((name: UIName) => { if (exclude && exclude.indexOf(name) !== -1) return; this.release(name); }); Object.keys(this.sceneCache).forEach((name: UIName) => { if (exclude && exclude.indexOf(name) !== -1) return; this.release(name); }); } /** * 检查UI是否有效 * - -1: 加载失败 * - 0: UI无效 * - 1: UI有效 */ private checkUIValid(name: UIName | MiniName, data: any, callback: (valid: -1 | 0 | 1) => any) { this.installUI(name, (result) => { if (!result) return callback(-1); const View = this.getUIClass(name); if (!View) return callback(0); if (!View.isViewValid) return callback(1); View.isViewValid((valid: boolean) => { callback(valid ? 1 : 0); }, data); }); } /** * 更新阴影的层级及显示 */ public refreshShade() { // 借助refreshShade实现onFocus、onLostFocus(onFocus不会被每次都触发,只有产生变化时才触发) let onFocus = false; // 倒序遍历uiRoots let uiRoots = this.UserInterface.children; for (let index = uiRoots.length - 1; index >= 0; index--) { const uiRoot = uiRoots[index]; if (uiRoot !== this.shade && uiRoot !== this.loading) { // 倒序遍历uiRoot let children = uiRoot.children; for (let i = children.length - 1; i >= 0; i--) { const node = children[i]; if (node === this.shade) continue; const com = this.getBaseView(node); if (!com) continue; // 触发onFocus if (!onFocus && com.isCaptureFocus && com.isShow) { onFocus = true; if (this.currFocus !== com) { isValid(this.currFocus, true) && this.currFocus.constructor.prototype.focus.call(this.currFocus, false); this.currFocus = com; this.currFocus.constructor.prototype.focus.call(this.currFocus, true); } } // 添加遮罩 if (com.isNeedShade && com.isShow) { const shadeSetting = Object.assign({}, UIManager.setting.shade, com.constructor.prototype.onShade.call(com)); if (shadeSetting.blur) { this.shade.getComponent(UIMgrShade).init(0, 255, 255, 0, true); } else { this.shade.getComponent(UIMgrShade).init( typeof shadeSetting.delay !== 'number' ? 0 : shadeSetting.delay, typeof shadeSetting.begin !== 'number' ? 60 : shadeSetting.begin, typeof shadeSetting.end !== 'number' ? 180 : shadeSetting.end, typeof shadeSetting.speed !== 'number' ? 100 : shadeSetting.speed, false, ); } this.shade.layer = node.layer; this.shade.parent = uiRoot; this.shade.active = true; // 以z坐标来代替2.x时代的zIndex this.shade.setPosition(this.shade.position.x, this.shade.position.y, node.position.z); let shadeIndex = this.shade.getSiblingIndex(); let nodeIndex = node.getSiblingIndex(); if (shadeIndex > nodeIndex) { this.shade.setSiblingIndex(nodeIndex); } else { this.shade.setSiblingIndex(nodeIndex - 1); } return; } } } } this.shade.active = false; this.shade.getComponent(UIMgrShade).clear(); if (!onFocus) { isValid(this.currFocus, true) && this.currFocus.constructor.prototype.focus.call(this.currFocus, false); this.currFocus = null; } } // 解析prefab private parsingPrefab(prefab: Prefab, name: string) { if (!prefab) return null; const node = instantiate(prefab); node.active = false; if (node.name !== name) { this.warn('parsingPrefab', `节点名与UI名不一致, 已重置为UI名: ${this.getUIPath(name)}`); node.name = name; } node.parent = this.getUIParent(name); node.getComponent(Widget)?.updateAlignment(); return node; } // 解析scene private parsingScene(asset: SceneAsset, name: string) { if (!asset || !asset.scene) return null; if (asset.scene.name !== name) { this.warn('parsingScene', `场景名与UI名不一致, 已重置为UI名: ${this.getUIPath(name)}`); asset.scene.name = name; } const view = this.getViewInChildren(asset.scene); if (!view) { this.error('parsingScene', `解析场景时未查询到根节点存在BaseView: ${this.getUIPath(name)}`); return null; } view.node.active = false; if (view.node.name !== name) { this.warn('parsingScene', `节点名与UI名不一致, 已重置为UI名: ${this.getUIPath(name)}`); view.node.name = name; } return view.node; } private addUILoadingUuid(name: UIName, loadingUuid?: string) { const uuid = loadingUuid || this.createUUID(); if (!this.uiLoadingMap.has(name)) { this.uiLoadingMap.set(name, [uuid]); } else { this.uiLoadingMap.get(name).push(uuid); } return uuid; } private removeUILoadingUuid(name: UIName, uuid: string) { if (!this.uiLoadingMap.has(name)) return false; const index = this.uiLoadingMap.get(name).indexOf(uuid); if (index === -1) return false; this.uiLoadingMap.get(name).splice(index, 1); return true; } /** * 创建UI */ private createUI(name: UIName, silent: boolean, callback: (node: Node, scene?: Scene) => any) { // 生成一个UI加载的UUID const loadingUuid = silent ? '' : this.showLoading(); const uiLoadingUuid = this.addUILoadingUuid(name, loadingUuid); // 验证name if (!name) { setTimeout(() => { if (!isValid(this)) return; // 验证本次加载是否有效 if (this.removeUILoadingUuid(name, uiLoadingUuid) === false) { return this.hideLoading(loadingUuid); } callback(null); this.hideLoading(loadingUuid); }); return; } // 判断是否已经存在节点并且是单例模式 const node = this.getUIInScene(name); if (isValid(node, true) && this.getBaseView(node).isSingleton === true) { setTimeout(() => { if (!isValid(this)) return; // 验证本次加载是否有效 if (this.removeUILoadingUuid(name, uiLoadingUuid) === false) { return this.hideLoading(loadingUuid); } // 验证节点是否有效 if (isValid(node, true)) { if (this.currScene === name) { callback(node, director.getScene()); } else { callback(node); } this.hideLoading(loadingUuid); } else { this.createUI(name, silent, callback); this.hideLoading(loadingUuid); } }); return; } // 加载UI this.load(name, (asset) => { if (!isValid(this)) return; // 验证本次加载是否有效 if (this.removeUILoadingUuid(name, uiLoadingUuid) === false) { return this.hideLoading(loadingUuid); } // 是场景 if (asset instanceof SceneAsset) { callback(this.parsingScene(asset, name), asset.scene); this.hideLoading(loadingUuid); return; } // 验证是否是单例(一个单例会有被同时load多次的情况,因为判断一个ui是否是单例,必须要至少实例化一个后才能获取) const node = this.getUIInScene(name); if (!isValid(node, true) || this.getBaseView(node).isSingleton === false) { callback(this.parsingPrefab(asset, name)); this.hideLoading(loadingUuid); } else { callback(node); this.hideLoading(loadingUuid); } }); } /** * 展示默认View */ public showDefault(onShow?: (result?: any) => any) { if (this.defaultUI) { this.show({ name: this.defaultUI, data: this.defaultData, onShow }); } else { Core.inst.manager.ui.showToast('请先设置首界面\n在setting.ts中修改defaultUI', 100); onShow && onShow(); this.warn('defaultUI不存在,请在setting.ts中修改'); } } /** * 是否展示了(包括加载中和队列中) */ public isShow(name: UIName) { return !!this.getUIInShowing(name) || this.isInQueue(name) || this.isLoading(name); } /** * 是否在队列中 */ public isInQueue(name: UIName) { return !!this.showQueue.find((v) => { return v.name == name; }); } /** * 是否在加载中 */ public isLoading(name: UIName) { return this.uiLoadingMap.has(name) && this.uiLoadingMap.get(name).length > 0; } /** * 放入队列 */ private putInShowQueue(data: IShowParams) { if (data.queue === 'join' || this.showQueue.length === 0) { this.showQueue.push(data); } else { this.showQueue.splice(1, 0, data); } if (this.showQueue.length === 1) { this.consumeShowQueue(); } } /** * 消耗队列 */ private consumeShowQueue() { if (this.showQueue.length === 0) return; const data = this.showQueue[0]; this.show({ name: data.name, data: data.data, onShow: data.onShow, onHide: (result: any) => { data.onHide && data.onHide(result); this.showQueue.shift(); this.consumeShowQueue(); }, onError: data.onError ? (error: string, code: 0 | 1) => { const ret = data.onError(error, code); this.showQueue.shift(); this.consumeShowQueue(); return ret; } : undefined, top: data.top, attr: data.attr, silent: data.silent }); } private showUI(params: IShowParams) { const { name, data, onShow, onHide, onError, top = true, attr = null, silent = false } = params; this.createUI(name, silent, (node, scene) => { if (!node) { this.error('show', `${name} 不存在或加载失败`); // 「没有指定onError」或「onError返回true」会自动发起重试 if (onError && onError(`${name} 不存在或加载失败`, UIManager.ErrorCode.LoadError) !== true) { return; } this.scheduleOnce(() => this.showUI(params), 1); if (!silent) this.showLoading(1); return; } !scene && top && node.setSiblingIndex(-1); const com = this.getBaseView(node); this.uiShowingMap.set(com, name); com.constructor.prototype.show.call(com, data, attr, // onShow (result: any) => { this.uiShowingMap.set(com, name); onShow && onShow(result); }, // onHide (result: any) => { this.uiShowingMap.delete(com); onHide && onHide(result); }, // beforeShow (error: string) => { if (error) { this.uiShowingMap.delete(com); onError && onError(error, UIManager.ErrorCode.LogicError); } else if (BaseView.isPage(name)) { this.uiShowingMap.set(com, name); const oldCom = this.currPage; this.currPage = com; if (isValid(oldCom, true) && oldCom !== com && oldCom.isShow && oldCom.isAlwaysExist === false) { oldCom.constructor.prototype.hide.call(oldCom, { name }); } if (scene) { if (oldCom !== com) { this.currScene = name; director.runSceneImmediate(scene, null, () => { this.log(`切换场景: ${name}`); }); } } else if (this.currScene !== UIScene) { this.currScene = UIScene; const scene = new Scene(UIScene); scene.autoReleaseAssets = true; director.runSceneImmediate(scene, null, () => { this.log(`切换场景: ${UIScene}`); }); } } } ); }); } /** * 展示一个UI * - 此流程一定是异步的 */ public show(params // @ts-ignore : IShowParams[0], ReturnType, ReturnType>) { const { name, data, queue, onError, silent = false } = params; // 加入队列中 if (queue) { this.putInShowQueue(params); return; } this.log(`show: ${name}`); // 生成一个UI加载的UUID const loadingUuid = silent ? '' : this.showLoading(); const uiLoadingUuid = this.addUILoadingUuid(name, loadingUuid); // 判断ui是否有效 Core.inst.lib.task.execute((retry) => { this.checkUIValid(name, data, (valid) => { // 验证本次加载是否有效 if (this.removeUILoadingUuid(name, uiLoadingUuid) === false) { this.hideLoading(loadingUuid); return; } // 加载失败 if (valid === -1) { this.error('show', `${name} 不存在或加载失败`); // 「没有指定onError」或「onError返回true」会自动发起重试 if (onError && onError(`${name} 不存在或加载失败`, UIManager.ErrorCode.LoadError) !== true) { return this.hideLoading(loadingUuid); } return retry(1); } // ui无效 if (valid === 0) { this.warn('show', `${name} 无效`); this.uninstallUI(name); onError && onError(`${name} 无效`, UIManager.ErrorCode.InvalidError); this.hideLoading(loadingUuid); return; } this.showUI(params); this.hideLoading(loadingUuid); }); }); } /** * 展示一个UI * - 此流程一定是异步的 */ public showAsync(params // @ts-ignore : IShowAsyncParams[0], ReturnType>): Promise> { return new Promise((resolve) => { this.show({ ...params, onHide(result) { resolve(result); } }); }); } /** * 关闭View * - 此流程一定是同步的 */ public hide({ name, data, onHide } // @ts-ignore : IHideParams[0], ReturnType>) { const nodes = this.getUIInShowing(name, true); this.log(`hide: ${name}`); if (nodes.length === 0) { if (!this.uiLoadingMap.has(name) || this.uiLoadingMap.get(name).length === 0) { return this.warn('hide', `${name} 不存在`); } } if (this.uiLoadingMap.has(name)) { this.uiLoadingMap.get(name).forEach((loadingUuid) => this.hideLoading(loadingUuid)); this.uiLoadingMap.get(name).length = 0; } for (let index = nodes.length - 1; index >= 0; index--) { const node = nodes[index]; const com = this.getBaseView(node); if (this.currPage === com) { this.currPage = null; } com.constructor.prototype.hide.call(com, data, onHide); } } /** * 从顶部关闭一个View(不会重复关闭节点) * - 此流程一定是同步的 */ public pop({ name, data, onHide } // @ts-ignore : IHideParams[0], ReturnType>) { const nodes = this.getUIInShowing(name, true); if (this.uiLoadingMap.has(name) && this.uiLoadingMap.get(name).length) { const loadingUuid = this.uiLoadingMap.get(name).pop(); this.hideLoading(loadingUuid); this.log(`pop: ${name}`); return; } if (nodes.length) { const node = nodes.pop(); const com = this.getBaseView(node); if (this.currPage === com) { this.currPage = null; } com.constructor.prototype.hide.call(com, data, onHide); this.log(`pop: ${name}`); return; } this.warn('pop', `${name} 不存在`); } /** * 从底部关闭一个View(不会重复关闭节点) * - 此流程一定是同步的 */ public shift({ name, data, onHide } // @ts-ignore : IHideParams[0], ReturnType>) { const nodes = this.getUIInShowing(name, true); if (nodes.length) { const node = nodes[0]; const com = this.getBaseView(node); if (this.currPage === com) { this.currPage = null; } com.constructor.prototype.hide.call(com, data, onHide); this.log(`shift: ${name}`); return; } if (this.uiLoadingMap.has(name) && this.uiLoadingMap.get(name).length) { const loadingUuid = this.uiLoadingMap.get(name).shift(); this.hideLoading(loadingUuid); this.log(`shift: ${name}`); return; } this.warn('shift', `${name} 不存在`); } /** * 关闭全部View * - 不关闭展示中的Page(加载中的会停止) * - 此流程一定是同步的 */ public hideAll({ data, exclude }: { data?: any, exclude?: UIName[] } = {}): void { this.log('hideAll'); // 展示中的 this.uiShowingMap.forEach((name, com) => { if (BaseView.isPaper(name)) return; if (exclude && exclude.indexOf(name) !== -1) return; if (com === this.currPage) return; com.constructor.prototype.hide.call(com, data); }); // 加载中的 this.uiLoadingMap.forEach((value, name) => { if (BaseView.isPaper(name)) return; if (exclude && exclude.indexOf(name) !== -1) return; value.forEach((loadingUuid) => this.hideLoading(loadingUuid)); value.length = 0; }); } public showLoading(timeout = 0) { this.loading.active = true; this.loading.setSiblingIndex(-1); if (this.loading.getComponent(UIMgrLoading)) { this.loading.getComponent(UIMgrLoading).init(); } else { // 兼容旧版本 this.loading.getComponentInChildren(UIMgrLoading)?.init(); } const uuid = this.createUUID(); this.showLoadingMap.set(uuid, true); if (timeout > 0) this.scheduleOnce(() => { this.hideLoading(uuid); }, timeout); return uuid; } public hideLoading(uuid: string) { if (!uuid) return; this.showLoadingMap.delete(uuid); if (this.showLoadingMap.size === 0) { if (this.loading.getComponent(UIMgrLoading)) { this.loading.getComponent(UIMgrLoading).clear(); } else { // 兼容旧版本 this.loading.getComponentInChildren(UIMgrLoading)?.clear(); } this.loading.active = false; } } /** * 添加触摸屏蔽 */ public addTouchMask(timeout = 0) { this.addTouchMaskListener(); const uuid = this.createUUID(); this.touchMaskMap.set(uuid, true); if (timeout > 0) this.scheduleOnce(() => { this.removeTouchMask(uuid); }, timeout); return uuid; } /** * 移除触摸屏蔽 * @param uuid addTouchMask的返回值 */ public removeTouchMask(uuid: string) { if (!uuid) return; this.touchMaskMap.delete(uuid); this.removeTouchMaskListener(); } /** * 显示Toast * @param message 文本 * @param timeout 持续时间(秒),默认2秒 */ public showToast(message: string, timeout?: number) { if (!this.toast) { return this.error('showToast', '请确认首场景中「Canvas/Manager/UIManager」的「Toast Pre」属性存在'); } this.toast.setSiblingIndex(-1); this.toast.getComponent(UIMgrToast).add({ message, timeout }); } /** * 清理Toast */ public clearToast() { if (!this.toast) return; this.toast.getComponent(UIMgrToast).clear(); } /** * 设置触摸是否启用 * @param enabled 是否启用 */ public setTouchEnabled(enabled: boolean) { if (enabled) { this.touchEnabled = true; this.removeTouchMaskListener(); } else { this.addTouchMaskListener(); this.touchEnabled = false; } this.warn('setTouchEnabled', this.touchEnabled); } /** * 在2DUI根节点上处理事件 */ public onUserInterface(...args: Parameters) { Node.prototype.on.apply(this.UserInterface, args); } /** * 在2DUI根节点上处理事件 */ public onceUserInterface(...args: Parameters) { Node.prototype.once.apply(this.UserInterface, args); } /** * 在2DUI根节点上处理事件 */ public offUserInterface(...args: Parameters) { Node.prototype.off.apply(this.UserInterface, args); } /** * 在2DUI根节点上处理事件 */ public targetOffUserInterface(...args: Parameters) { Node.prototype.targetOff.apply(this.UserInterface, args); } /** * 立即给2DUI的子节点排序 */ public sortUserInterface(name: IViewType) { this.UserInterface ?.getChildByName(name) ?.getComponent(UIMgrZOrder) ?.updateZOrder(); } /** * 屏幕截图 * - 需要在Director.EVENT_BEFORE_RENDER事件中调用 * @example * director.once(Director.EVENT_BEFORE_RENDER, () => { * const renderTexture = new RenderTexture(); * const size = view.getVisibleSize(); * renderTexture.reset({ width: size.width, height: size.height }); * app.manager.ui.screenshot(renderTexture); * }); */ public screenshot(renderTexture: RenderTexture, opts?: { /**摄像机筛选 */ cameraFilter?: (camera: Camera) => boolean; /**摄像机列表 */ cameraList?: Camera[]; }) { const cameras = opts?.cameraList || director.getScene().getComponentsInChildren(Camera); const cameraList = cameras.sort((a, b) => a.priority - b.priority) .filter(camera => { if (!camera.enabledInHierarchy) return false; if (camera.targetTexture) return false; return opts?.cameraFilter ? opts.cameraFilter(camera) : true; }); const cameraList2 = cameraList.map(camera => camera.camera); cameraList.forEach(camera => { camera.targetTexture = renderTexture; }); director.root.pipeline.render(cameraList2); cameraList.forEach(camera => { camera.targetTexture = null; }); return renderTexture; } }