This commit is contained in:
2026-03-04 11:30:40 +08:00
parent 9ff5e028b0
commit 189dad2809
3767 changed files with 1056662 additions and 0 deletions

View File

@@ -0,0 +1,626 @@
[
{
"__type__": "cc.Prefab",
"_name": "MainManager",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"data": {
"__id__": 1
},
"optimizationPolicy": 0,
"persistent": false
},
{
"__type__": "cc.Node",
"_name": "MainManager",
"_objFlags": 512,
"__editorExtras__": {},
"_parent": null,
"_children": [
{
"__id__": 2
},
{
"__id__": 6
},
{
"__id__": 10
},
{
"__id__": 14
},
{
"__id__": 18
}
],
"_active": true,
"_components": [
{
"__id__": 26
},
{
"__id__": 28
}
],
"_prefab": {
"__id__": 30
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 33554432,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.Node",
"_name": "EventManager",
"_objFlags": 512,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 3
}
],
"_prefab": {
"__id__": 5
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 33554432,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "b4ea6NEN3hCPZiqp3hRVbvU",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 4
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "38T7Dsd1BPcZKW0ht+pktn"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "d6NoKoYv9Gk4UlM7XwQPMZ",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "TimerManager",
"_objFlags": 512,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 7
}
],
"_prefab": {
"__id__": 9
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 33554432,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "b5636+NNRZFEKq6dPkgK4qf",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 6
},
"_enabled": true,
"__prefab": {
"__id__": 8
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "1dkZGh7HlMao6WFTkih0ky"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "48d9+lep1ML4jLQU06pY4T",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "LoaderManager",
"_objFlags": 512,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 11
}
],
"_prefab": {
"__id__": 13
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 33554432,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "b3bf5M3DHNHcYe1nnNZYr6B",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 10
},
"_enabled": true,
"__prefab": {
"__id__": 12
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "8eHWii+rhPFp6edhCSGQYK"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "f2JbolDiVMXbduKISlIjVg",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "SoundManager",
"_objFlags": 512,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 15
}
],
"_prefab": {
"__id__": 17
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 33554432,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "58002Ha2adOWbt2LDr8rmBT",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 14
},
"_enabled": true,
"__prefab": {
"__id__": 16
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "8bj+DC16xOWa/0aihp7pcS"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "54fbQjsl1HwJD2+SejsBvm",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "UIManager",
"_objFlags": 512,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 19
},
{
"__id__": 21
},
{
"__id__": 23
}
],
"_prefab": {
"__id__": 25
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 33554432,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "234f6Lx69NNFJ9vC2nHCWRJ",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 18
},
"_enabled": true,
"__prefab": {
"__id__": 20
},
"loadingPre": {
"__uuid__": "fe542035-b018-493e-bea8-084fe4e01905",
"__expectedType__": "cc.Prefab"
},
"shadePre": {
"__uuid__": "000cee21-922c-4fcd-bd39-6f80ac2436a4",
"__expectedType__": "cc.Prefab"
},
"toastPre": {
"__uuid__": "b2a00c44-d199-4031-8fa7-ea681618b9d4",
"__expectedType__": "cc.Prefab"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "67aJm34PdM/ItCntR8+zcy"
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 18
},
"_enabled": true,
"__prefab": {
"__id__": 22
},
"_contentSize": {
"__type__": "cc.Size",
"width": 750,
"height": 1334
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "c3fJug795H8Zpvt/9PivC1"
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 18
},
"_enabled": true,
"__prefab": {
"__id__": 24
},
"_alignFlags": 45,
"_target": null,
"_left": 0,
"_right": 0,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 750,
"_originalHeight": 1334,
"_alignMode": 2,
"_lockFlags": 0,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "152XoOPG1D3KEcrikGio7Z"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "4d13DruoVK/5VWhsBYQS/E",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 27
},
"_contentSize": {
"__type__": "cc.Size",
"width": 750,
"height": 1334
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "bab8wNsgZHRICm9NXgLsKC"
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 29
},
"_alignFlags": 45,
"_target": null,
"_left": 0,
"_right": 0,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 100,
"_originalHeight": 100,
"_alignMode": 2,
"_lockFlags": 0,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "12csQGnuVJRr1L+07HdNSc"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "09f6AV8NhG8amoujtBeGc3",
"instance": null,
"targetOverrides": null
}
]

View File

@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "5e43bb09-848f-434a-b3a5-a6b6602e00af",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "MainManager"
}
}

View File

@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "4f12d0c8-895c-48a5-8805-79653aadb7e4",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@@ -0,0 +1,89 @@
import { EventTarget, _decorator } from 'cc';
import BaseManager from '../../base/BaseManager';
const { ccclass } = _decorator;
class Event {
static destroy(event: Event) {
if (!event) return;
event._event = null;
}
// 事件管理器
private _event: EventTarget = new EventTarget();
/**
* 事件分发
*/
public emit(event: string | number, ...data: any[]) {
if (!this._event) {
throw Error('当前event已销毁无法继续调用');
}
this._event.emit(event as any, ...data);
}
/**
* 事件监听
*/
public on(event: string | number, cb: (...any: any[]) => void, target?: any) {
if (!this._event) {
throw Error('当前event已销毁无法继续调用');
}
this._event.on(event as any, cb, target);
}
/**
* 事件监听
*/
public once(event: string | number, cb: (...any: any[]) => void, target?: any) {
if (!this._event) {
throw Error('当前event已销毁无法继续调用');
}
this._event.once(event as any, cb, target);
}
/**
* 事件移除监听
*/
public off(event: string | number, cb?: (...any: any[]) => void, target?: any) {
if (!this._event) {
throw Error('当前event已销毁无法继续调用');
}
this._event.off(event as any, cb, target);
}
/**
* 事件移除监听
*/
public targetOff(target: any) {
if (!this._event) {
throw Error('当前event已销毁无法继续调用');
}
this._event.targetOff(target);
}
}
@ccclass('EventManager')
export default class EventManager extends BaseManager {
private events: Map<string | number | Symbol, Event> = new Map();
clear() {
this.events.forEach(event => Event.destroy(event));
return this.events.clear();
}
delete(key: string | number | Symbol) {
Event.destroy(this.events.get(key));
return this.events.delete(key);
}
get(key: string | number | Symbol): Event {
if (this.events.has(key)) {
return this.events.get(key);
}
const event = new Event();
this.events.set(key, event);
return event;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "b4ea6344-3778-423d-98aa-a7785155bbd4",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "d8954580-884f-4927-b59a-dfb9553d4ce6",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@@ -0,0 +1,869 @@
import { Asset, AssetManager, Font, ImageAsset, JsonAsset, Label, SceneAsset, Sprite, SpriteFrame, Texture2D, TextureCube, _decorator, assetManager, isValid, path, sp } from 'cc';
import { MINIGAME } from 'cc/env';
import BaseManager from '../../base/BaseManager';
import Core from '../../Core';
const { ccclass } = _decorator;
const REGEX = /^https?:\/\/.*/;
class Command {
private static cache: Command[] = [];
static create(onComplete: (items: unknown) => void, onProgress: (finish: number, total: number, item: AssetManager.RequestItem) => void = null) {
const command = Command.cache.pop() || new Command();
onProgress && command.onProgress.push(onProgress);
onComplete && command.onComplete.push(onComplete);
return command;
}
static put(command: Command) {
command.onProgress.length = 0;
command.onComplete.length = 0;
Command.cache.push(command);
}
onProgress: Array<(finish: number, total: number, item: AssetManager.RequestItem) => void> = [];
onComplete: Array<(items: unknown) => void> = [];
private constructor() { }
}
class Loader {
private assetMap = new Map<string, Asset>();
private loadingMap = new Map<string, Command>();
/**
* 预加载
* @param params.bundle 默认为resources, 可以是项目中的bundle名也可以是远程bundle的url(url末位作为bundle名)参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#%E5%8A%A0%E8%BD%BD-asset-bundle
* @param params.version 远程bundle的版本参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#asset-bundle-%E7%9A%84%E7%89%88%E6%9C%AC
*/
public preload(params: { path: string, bundle?: string, version?: string, type?: typeof Asset, onProgress?: (finish: number, total: number, item: AssetManager.RequestItem) => void, onComplete?: (item: AssetManager.RequestItem[] | null) => void }) {
return Core.inst.manager.loader.preload(params);
}
/**
* 预加载
* @param params.bundle 默认为resources, 可以是项目中的bundle名也可以是远程bundle的url(url末位作为bundle名)参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#%E5%8A%A0%E8%BD%BD-asset-bundle
* @param params.version 远程bundle的版本参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#asset-bundle-%E7%9A%84%E7%89%88%E6%9C%AC
*/
public preloadDir(params: { path: string, bundle?: string, version?: string, type?: typeof Asset, onProgress?: (finish: number, total: number, item: AssetManager.RequestItem) => void, onComplete?: (items: AssetManager.RequestItem[] | null) => void }) {
return Core.inst.manager.loader.preloadDir(params);
}
/**
* 加载bundle下的资源
* @param params.bundle 默认为resources, 可以是项目中的bundle名也可以是远程bundle的url(url末位作为bundel名)参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#%E5%8A%A0%E8%BD%BD-asset-bundle
* @param params.version 远程bundle的版本参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#asset-bundle-%E7%9A%84%E7%89%88%E6%9C%AC
* @param params.path bundle下的相对路径
* @param params.type 资源类型
*/
public load<T extends typeof Asset>(params: { path: string, bundle?: string, version?: string, type?: T, onProgress?: (finish: number, total: number, item: AssetManager.RequestItem) => void, onComplete?: (item: InstanceType<T> | null) => void }) {
const key = `${params.bundle || 'resources'}-${params.type.name}-${params.path}-${params.version || ''}`;
if (this.loadingMap.has(key)) {
const command = this.loadingMap.get(key);
params.onProgress && command.onProgress.push(params.onProgress);
params.onComplete && command.onComplete.push(params.onComplete);
return;
}
// 加载中
const command = Command.create(params.onComplete, params.onProgress);
this.loadingMap.set(key, command);
// 有缓存
if (this.assetMap.has(key)) {
const asset = this.assetMap.get(key);
// 有缓存的情况下不触发onProgress回调
setTimeout(() => {
// 加载无效
if (!this.loadingMap.has(key)) return;
this.loadingMap.delete(key);
command.onComplete.forEach(cb => cb(asset));
Command.put(command);
}, 0);
return;
}
Core.inst.manager.loader.load({
...params,
onProgress: (finish, total, item) => {
if (!this.loadingMap.has(key)) return;
command.onProgress.forEach(cb => cb(finish, total, item));
},
onComplete: (asset) => {
// 加载无效
if (!this.loadingMap.has(key)) {
asset.addRef();
asset.decRef();
return;
}
this.loadingMap.delete(key);
if (asset) {
asset.addRef();
this.assetMap.set(key, asset);
}
command.onComplete.forEach(cb => cb(asset));
Command.put(command);
}
});
}
/**
* 加载bundle下的资源
* @param params.bundle 默认为resources, 可以是项目中的bundle名也可以是远程bundle的url(url末位作为bundle名)参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#%E5%8A%A0%E8%BD%BD-asset-bundle
* @param params.version 远程bundle的版本参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#asset-bundle-%E7%9A%84%E7%89%88%E6%9C%AC
* @param params.path bundle下的相对路径
* @param params.type 资源类型
*/
public loadAsync<T extends typeof Asset>(params: { path: string, bundle?: string, version?: string, type?: T, onProgress?: (finish: number, total: number, item: AssetManager.RequestItem) => void }): Promise<InstanceType<T> | null> {
return new Promise((resolve) => {
this.load({
...params,
onComplete: resolve
});
});
}
/**
* 加载bundle下的资源
* @param params.bundle 默认为resources, 可以是项目中的bundle名也可以是远程bundle的url(url末位作为bundel名)参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#%E5%8A%A0%E8%BD%BD-asset-bundle
* @param params.version 远程bundle的版本参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#asset-bundle-%E7%9A%84%E7%89%88%E6%9C%AC
* @param params.path bundle下的相对路径
* @param params.type 资源类型
*/
public loadDir<T extends typeof Asset>(params: { path: string, bundle?: string, version?: string, type?: T, onProgress?: (finish: number, total: number, item: AssetManager.RequestItem) => void, onComplete?: (items: InstanceType<T>[] | null) => void }) {
const key = `${params.bundle || 'resources'}-${params.type.name}-${params.path}-${params.version || ''}:`;
if (this.loadingMap.has(key)) {
const command = this.loadingMap.get(key);
params.onProgress && command.onProgress.push(params.onProgress);
params.onComplete && command.onComplete.push(params.onComplete);
return;
}
// 加载中
const command = Command.create(params.onComplete, params.onProgress);
this.loadingMap.set(key, command);
const results = [] as InstanceType<T>[];
this.assetMap.forEach((asset, path) => {
if (path.indexOf(key) === 0) {
results.push(asset as InstanceType<T>);
}
});
// 有缓存
if (results.length) {
// 有缓存的情况下不触发onProgress回调
setTimeout(() => {
// 加载无效
if (!this.loadingMap.has(key)) return;
this.loadingMap.delete(key);
command.onComplete.forEach(cb => cb(results));
Command.put(command);
}, 0);
return;
}
Core.inst.manager.loader.loadDir({
...params,
onProgress: (finish, total, item) => {
if (!this.loadingMap.has(key)) return;
command.onProgress.forEach(cb => cb(finish, total, item));
},
onComplete: (assets) => {
// 加载无效
if (!this.loadingMap.has(key)) {
assets?.forEach((asset) => {
asset.addRef();
asset.decRef();
});
return;
}
this.loadingMap.delete(key);
assets?.forEach((asset) => {
asset.addRef();
this.assetMap.set(key + asset.uuid, asset);
});
command.onComplete.forEach(cb => cb(assets));
Command.put(command);
}
});
}
/**
* 加载bundle下的资源
* @param params.bundle 默认为resources, 可以是项目中的bundle名也可以是远程bundle的url(url末位作为bundle名)参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#%E5%8A%A0%E8%BD%BD-asset-bundle
* @param params.version 远程bundle的版本参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#asset-bundle-%E7%9A%84%E7%89%88%E6%9C%AC
* @param params.path bundle下的相对路径
* @param params.type 资源类型
*/
public loadDirAsync<T extends typeof Asset>(params: { path: string, bundle?: string, version?: string, type?: T, onProgress?: (finish: number, total: number, item: AssetManager.RequestItem) => void }): Promise<InstanceType<T>[] | null> {
return new Promise((resolve) => {
this.loadDir({
...params,
onComplete: resolve
});
});
}
/**
* 加载远程资源
* @example
* loadRemote({url:'', ext:'.png', onComplete:(result){ }})
*/
public loadRemote({ url, ext, onComplete }: { url: string, ext?: string, onComplete?: (result: Asset | null) => void }) {
if (this.loadingMap.has(url)) {
const command = this.loadingMap.get(url);
onComplete && command.onComplete.push(onComplete);
return;
}
// 加载中
const command = Command.create(onComplete);
this.loadingMap.set(url, command);
// 有缓存
if (this.assetMap.has(url)) {
const asset = this.assetMap.get(url);
// 有缓存的情况下不触发onProgress回调
setTimeout(() => {
// 加载无效
if (!this.loadingMap.has(url)) return;
this.loadingMap.delete(url);
command.onComplete.forEach(cb => cb(asset));
Command.put(command);
}, 0);
return;
}
Core.inst.manager.loader.loadRemote({
url, ext,
onComplete: (asset) => {
// 加载无效
if (!this.loadingMap.has(url)) {
asset.addRef();
asset.decRef();
return;
}
this.loadingMap.delete(url);
if (asset) {
asset.addRef();
this.assetMap.set(url, asset);
}
command.onComplete.forEach(cb => cb(asset));
Command.put(command);
}
});
}
/**
* 加载远程资源
* @example
* await loadRemoteAsync({url:'', ext:'.png'})
*/
public loadRemoteAsync(params: { url: string, ext?: string }): Promise<Asset | null> {
return new Promise((resolve) => {
this.loadRemote({
...params,
onComplete: resolve
});
});
}
/**
* 设置字体资源
* @param params.bundle 默认为resources
* @param params.path bundle下的相对路径
*
* @example
* setFont({target:label, path:'font/num', bundle:'resources', onComplete:(succ)=>{}})
* setFont({target:label, url:'http://img/a/font',ext:'.ttf', onComplete:(succ)=>{}})
*/
public setFont(params: { target: Label, url: string, ext?: string, onComplete?: (success: boolean) => any, onSuccess?: () => void, onFail?: () => void }): void;
public setFont(params: { target: Label, path: string, bundle?: string, onComplete?: (success: boolean) => any, onSuccess?: () => void, onFail?: () => void }): void;
public setFont(params: { target: Label, path?: string, bundle?: string, url?: string, ext?: string, onComplete?: (success: boolean) => any, onSuccess?: () => void, onFail?: () => void }) {
if (params.url) {
this.loadRemote({
url: params.url,
ext: params.ext,
onComplete: (font: Font) => {
if (!font || !isValid(params.target)) {
params.onFail && params.onFail();
params.onComplete && params.onComplete(false);
return;
}
params.target.font = font;
params.onSuccess && params.onSuccess();
params.onComplete && params.onComplete(true);
}
});
} else {
this.load({
path: params.path,
bundle: params.bundle,
type: Font,
onComplete: (font) => {
if (!font || !isValid(params.target)) {
params.onFail && params.onFail();
params.onComplete && params.onComplete(false);
return;
}
params.target.font = font;
params.onSuccess && params.onSuccess();
params.onComplete && params.onComplete(true);
}
});
}
}
/**
* 设置Spine资源
* @param params.bundle 默认为resources
* @param params.path bundle下的相对路径
*
* @example
* setSpine({target:spine, path:'spine/role', bundle:'resources', onComplete:(succ)=>{}})
*/
public setSpine(params: { target: sp.Skeleton, path: string, bundle?: string, onComplete?: (success: boolean) => any, onSuccess?: () => void, onFail?: () => void }) {
this.load({
path: params.path,
bundle: params.bundle,
type: sp.SkeletonData,
onComplete: (skeletonData) => {
if (!skeletonData || !isValid(params.target)) {
params.onFail && params.onFail();
params.onComplete && params.onComplete(false);
return;
}
params.target.skeletonData = skeletonData;
params.onSuccess && params.onSuccess();
params.onComplete && params.onComplete(true);
}
});
}
/**
* 设置图片资源
* @param params.bundle 默认为resources
* @param params.path bundle下的相对路径
*
* @example
* setSprite({target:sprite, path:'img/a/spriteFrame', bundle:'resources', onComplete:(succ)=>{}})
* setSprite({target:sprite, url:'http://img/a/avatar',ext:'.png', onComplete:(succ)=>{}})
*/
public setSprite(params: { target: Sprite, url: string, ext?: string, onComplete?: (success: boolean) => any, onSuccess?: () => void, onFail?: () => void }): void;
public setSprite(params: { target: Sprite, path: string, bundle?: string, onComplete?: (success: boolean) => any, onSuccess?: () => void, onFail?: () => void }): void;
public setSprite(params: { target: Sprite, path?: string, bundle?: string, url?: string, ext?: string, onComplete?: (success: boolean) => any, onSuccess?: () => void, onFail?: () => void }) {
if (params.url) {
this.loadRemote({
url: params.url,
ext: params.ext,
onComplete: (imageAsset: ImageAsset) => {
if (!imageAsset || !isValid(params.target)) {
params.onFail && params.onFail();
params.onComplete && params.onComplete(false);
return;
}
const spriteFrame = SpriteFrame.createWithImage(imageAsset);
params.target.spriteFrame = spriteFrame;
params.onSuccess && params.onSuccess();
params.onComplete && params.onComplete(true);
}
});
} else {
this.load({
path: params.path,
bundle: params.bundle,
type: SpriteFrame,
onComplete: (spriteFrame) => {
if (!spriteFrame || !isValid(params.target)) {
params.onFail && params.onFail();
params.onComplete && params.onComplete(false);
return;
}
params.target.spriteFrame = spriteFrame;
params.onSuccess && params.onSuccess();
params.onComplete && params.onComplete(true);
}
});
}
}
/**
* 释放所有资源
*/
public releaseAll() {
const assetList: Asset[] = [];
this.assetMap.forEach(asset => assetList.push(asset));
this.assetMap.clear();
this.loadingMap.clear();
// 延迟一秒释放资源
setTimeout(() => {
assetList.forEach(asset => asset.decRef());
}, 1000);
}
}
@ccclass('LoaderManager')
export default class LoaderManager extends BaseManager {
/**
* `Loader`的目的是对资源加载进行分组引用计数管理。比如两个`Loader`实例都加载了同一个资源当某个实例执行releaseAll后并不会让引擎资源释放资源只有两个实例都执行了释放资源后才会让引擎资源释放资源。
* @example
* // 创建Loader实例
* const loader = new LoaderManager.Loader();
* // 加载资源
* loader.load({path:'img/a/spriteFrame', bundle:'resources', type:SpriteFrame, onComplete:(spriteFrame)=>{}})
* // 加载远程图片资源
* loader.loadRemote({url:'http://img/a/avatar',ext:'.png', onComplete:(imageAsset)=>{}})
* // 释放所有资源
* loader.releaseAll();
*/
static Loader = Loader;
private handle(handle: string, { bundle, version, path, type, onProgress, onComplete }: { bundle?: string, version?: string, path: string, type?: typeof Asset, onProgress?: (finish: number, total: number, item: AssetManager.RequestItem) => void, onComplete?: (result: unknown | null) => void }) {
if (!handle) {
this.error('handle is empty');
return onComplete && onComplete(null);
}
if (!path) {
this.error(`${handle} fail. path is empty`);
return onComplete && onComplete(null);
}
if (!bundle) bundle = 'resources';
const args: any[] = [path];
if (type) args.push(type);
if (onProgress) args.push(onProgress);
args.push((err: string, res: any) => {
if (err) {
this.error(`${handle} "${path}" fail`, err);
if (type === SpriteFrame && path.slice(-12) !== '/spriteFrame') {
this.warn(`加载SpriteFrame类型的资源, 路径可能需要以/spriteFrame结尾, 如: 「${path}」 -> 「${path}/spriteFrame」`);
} else if (type === Texture2D && path.slice(-8) !== '/texture') {
this.warn(`加载Texture2D类型的资源, 路径可能需要以/texture结尾, 如: 「${path}」 -> 「${path}/texture」`);
} else if (type === TextureCube && path.slice(-12) !== '/textureCube') {
this.warn(`加载TextureCube类型的资源, 路径可能需要以/textureCube结尾, 如: 「${path}」 -> 「${path}/textureCube」`);
}
onComplete && onComplete(null);
} else {
onComplete && onComplete(res);
}
});
this.loadBundle({
bundle, version,
onComplete(bundle) {
if (!bundle) return onComplete && onComplete(null);
bundle[handle](args[0], args[1], args[2], args[3]);
},
});
}
/**
* 预加载
* @param params.bundle 默认为resources, 可以是项目中的bundle名也可以是远程bundle的url(url末位作为bundle名)参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#%E5%8A%A0%E8%BD%BD-asset-bundle
* @param params.version 远程bundle的版本参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#asset-bundle-%E7%9A%84%E7%89%88%E6%9C%AC
*/
public preload(params: { path: string, bundle?: string, version?: string, type?: typeof Asset, onProgress?: (finish: number, total: number, item: AssetManager.RequestItem) => void, onComplete?: (item: AssetManager.RequestItem[] | null) => void }) {
if (SceneAsset === params.type as typeof Asset) {
this.handle('preloadScene', { path: params.path, bundle: params.bundle, version: params.version, onProgress: params.onProgress, onComplete: params.onComplete });
} else {
this.handle('preload', params);
}
}
/**
* 预加载
* @param params.bundle 默认为resources, 可以是项目中的bundle名也可以是远程bundle的url(url末位作为bundle名)参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#%E5%8A%A0%E8%BD%BD-asset-bundle
* @param params.version 远程bundle的版本参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#asset-bundle-%E7%9A%84%E7%89%88%E6%9C%AC
*/
public preloadDir(params: { path: string, bundle?: string, version?: string, type?: typeof Asset, onProgress?: (finish: number, total: number, item: AssetManager.RequestItem) => void, onComplete?: (items: AssetManager.RequestItem[] | null) => void }) {
this.handle('preloadDir', params);
}
/**
* 加载bundle下的资源
* @param params.bundle 默认为resources, 可以是项目中的bundle名也可以是远程bundle的url(url末位作为bundle名)参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#%E5%8A%A0%E8%BD%BD-asset-bundle
* @param params.version 远程bundle的版本参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#asset-bundle-%E7%9A%84%E7%89%88%E6%9C%AC
* @param params.path bundle下的相对路径
* @param params.type 资源类型
*/
public load<T extends typeof Asset>(params: { path: string, bundle?: string, version?: string, type?: T, onProgress?: (finish: number, total: number, item: AssetManager.RequestItem) => void, onComplete?: (item: InstanceType<T> | null) => void }) {
if (SceneAsset === params.type as typeof Asset) {
this.handle('loadScene', { path: params.path, bundle: params.bundle, version: params.version, onProgress: params.onProgress, onComplete: params.onComplete });
} else {
this.handle('load', params);
}
}
/**
* 加载bundle下的资源
* @param params.bundle 默认为resources, 可以是项目中的bundle名也可以是远程bundle的url(url末位作为bundle名)参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#%E5%8A%A0%E8%BD%BD-asset-bundle
* @param params.version 远程bundle的版本参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#asset-bundle-%E7%9A%84%E7%89%88%E6%9C%AC
* @param params.path bundle下的相对路径
* @param params.type 资源类型
*/
public loadAsync<T extends typeof Asset>(params: { path: string, bundle?: string, version?: string, type?: T, onProgress?: (finish: number, total: number, item: AssetManager.RequestItem) => void }): Promise<InstanceType<T> | null> {
return new Promise((resolve) => {
this.load({
...params,
onComplete: resolve
});
});
}
/**
* 加载bundle下的资源
* @param params.bundle 默认为resources, 可以是项目中的bundle名也可以是远程bundle的url(url末位作为bundle名)参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#%E5%8A%A0%E8%BD%BD-asset-bundle
* @param params.version 远程bundle的版本参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#asset-bundle-%E7%9A%84%E7%89%88%E6%9C%AC
* @param params.path bundle下的相对路径
* @param params.type 资源类型
*/
public loadDir<T extends typeof Asset>(params: { path: string, bundle?: string, version?: string, type?: T, onProgress?: (finish: number, total: number, item: AssetManager.RequestItem) => void, onComplete?: (items: InstanceType<T>[] | null) => void }) {
this.handle('loadDir', params);
}
/**
* 加载bundle下的资源
* @param params.bundle 默认为resources, 可以是项目中的bundle名也可以是远程bundle的url(url末位作为bundle名)参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#%E5%8A%A0%E8%BD%BD-asset-bundle
* @param params.version 远程bundle的版本参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#asset-bundle-%E7%9A%84%E7%89%88%E6%9C%AC
* @param params.path bundle下的相对路径
* @param params.type 资源类型
*/
public loadDirAsync<T extends typeof Asset>(params: { path: string, bundle?: string, version?: string, type?: T, onProgress?: (finish: number, total: number, item: AssetManager.RequestItem) => void }): Promise<InstanceType<T>[] | null> {
return new Promise((resolve) => {
this.loadDir({
...params,
onComplete: resolve
});
});
}
/**
* 销毁一个bundle中对应path和type的资源
* @param params.bundle 默认为resources如果是远程bundle则使用url末位作为bundle名
* @param params.path bundle下的相对路径
* @param params.type 资源类型
*/
public release({ path, bundle, type }: { path: string, bundle?: string, type?: typeof Asset }) {
if (!bundle) bundle = 'resources';
assetManager.getBundle(bundle)?.release(path, type);
}
/**
* 销毁一个bundle中所有的资源
* @param bundle 默认为resources如果是远程bundle则使用url末位作为bundle名
*/
public releaseAll(bundle?: string) {
if (!bundle) bundle = 'resources';
const _bundle = assetManager.getBundle(bundle);
if (!_bundle) return;
// 只释放自己内部的资源,依赖的资源只减少引用计数
_bundle.getDirWithPath('/', Asset).forEach((asset) => {
_bundle.release(asset.path, asset.ctor);
});
// cocos提供的方法会将依赖的资源也卸载(这个设计很奇怪)
// _bundle?.releaseAll();
}
/**
* 销毁一个bundle中未使用的资源
* @param bundle 默认为resources如果是远程bundle则使用url末位作为bundle名
*/
public releaseUnused(bundle?: string) {
if (!bundle) bundle = 'resources';
//@ts-ignore
assetManager.getBundle(bundle)?.releaseUnusedAssets();
}
/**
* 加载一个bundle
* @param params.bundle 默认为resources, 可以是项目中的bundle名也可以是远程bundle的url(url末位作为bundle名)参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#%E5%8A%A0%E8%BD%BD-asset-bundle
* @param params.version 远程bundle的版本参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#asset-bundle-%E7%9A%84%E7%89%88%E6%9C%AC
*/
public loadBundle({ bundle, version, onComplete }: { bundle?: string, version?: string, onComplete?: (bundle: AssetManager.Bundle | null) => any }) {
if (!bundle) bundle = 'resources';
if (MINIGAME) {
if (REGEX.test(bundle)) {
this.warn('小游戏环境下只支持加载远程Bundle的资源数据, 不会加载脚本');
this.reloadBundle({ bundle, version, onComplete });
return;
}
if (version && assetManager.downloader.bundleVers[bundle] !== version) {
this.warn('小游戏环境下只支持更新Bundle的远程资源数据, 不会更新脚本');
// 先加载本地bundle运行脚本
assetManager.loadBundle(bundle, (err: Error, b: AssetManager.Bundle) => {
if (err || !b) return onComplete?.(null);
// 然后再走重载逻辑更新资源
this.reloadBundle({ bundle, version, onComplete });
});
} else {
assetManager.loadBundle(bundle, (err: Error, bundle: AssetManager.Bundle) => {
onComplete && onComplete(err ? null : bundle);
});
}
return;
}
if (version) {
assetManager.loadBundle(bundle, { version }, (err: Error, bundle: AssetManager.Bundle) => {
onComplete && onComplete(err ? null : bundle);
});
} else {
assetManager.loadBundle(bundle, (err: Error, bundle: AssetManager.Bundle) => {
onComplete && onComplete(err ? null : bundle);
});
}
}
/**
* 加载一个bundle
* @param params.bundle 默认为resources, 可以是项目中的bundle名也可以是远程bundle的url(url末位作为bundle名)参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#%E5%8A%A0%E8%BD%BD-asset-bundle
* @param params.version 远程bundle的版本参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#asset-bundle-%E7%9A%84%E7%89%88%E6%9C%AC
*/
public loadBundleAsync(params: { bundle?: string, version?: string }): Promise<AssetManager.Bundle | null> {
return new Promise((resolve) => {
this.loadBundle({
...params,
onComplete: resolve
});
});
}
/**
* 获取一个已经加载的bundle
* @param bundle 默认为resources如果是远程bundle则使用url末位作为bundle名
*/
public getBundle(bundle?: string) {
if (!bundle) bundle = 'resources';
return assetManager.getBundle(bundle);
}
/**
* 移除一个已经加载的bundle
* @param bundle 默认为resources如果是远程bundle则使用url末位作为bundle名
*/
public removeBundle(bundle?: string) {
if (!bundle) bundle = 'resources';
const b = assetManager.getBundle(bundle);
if (b) assetManager.removeBundle(b);
}
/**
* 重载一个bundle(只重载资源列表)
* - 只有远程bundle支持重载
* @param params.bundle 默认为resources, 可以是项目中的bundle名也可以是远程bundle的url(url末位作为bundle名)参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#%E5%8A%A0%E8%BD%BD-asset-bundle
* @param params.version 远程bundle的版本参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#asset-bundle-%E7%9A%84%E7%89%88%E6%9C%AC
*/
public reloadBundle({ bundle, version, onComplete }: { bundle?: string, version?: string, onComplete?: (bundle: AssetManager.Bundle | null) => any }) {
if (!bundle) bundle = 'resources';
let baseUrl = '';
let configUrl = '';
if (REGEX.test(bundle)) {
baseUrl = bundle;
const suffix = version ? `${version}.` : '';
configUrl = `${baseUrl}config.${suffix}json`;
}
else {
baseUrl = `${assetManager.downloader.remoteServerAddress}remote/${bundle}/`;
const suffix = version ? `${version}.` : '';
configUrl = `${baseUrl}config.${suffix}json`;
}
// 清除可能存在的config缓存
assetManager.cacheManager?.removeCache(configUrl);
assetManager.loadRemote(configUrl, (err: Error, data: JsonAsset) => {
if (err) {
this.error(`下载Bundle配置失败: ${configUrl}`);
onComplete?.(null);
return;
}
this.releaseAll(path.basename(bundle));
this.removeBundle(path.basename(bundle));
const ab = new AssetManager.Bundle();
const config = data.json as any;
config.base = baseUrl;
ab.init(config);
onComplete?.(ab);
});
}
/**
* 重载一个bundle(只重载资源列表)
* - 只有远程bundle支持重载
* @param params.bundle 默认为resources, 可以是项目中的bundle名也可以是远程bundle的url(url末位作为bundle名)参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#%E5%8A%A0%E8%BD%BD-asset-bundle
* @param params.version 远程bundle的版本参考https://docs.cocos.com/creator/manual/zh/asset/bundle.html#asset-bundle-%E7%9A%84%E7%89%88%E6%9C%AC
*/
public reloadBundleAsync(params: { bundle?: string, version?: string }): Promise<AssetManager.Bundle | null> {
return new Promise((resolve) => {
this.reloadBundle({
...params,
onComplete: resolve
});
});
}
/**
* 加载远程资源
* @example
* loadRemote({url:'', ext:'.png', onComplete:(result){ }})
*/
public loadRemote({ url, ext, onComplete }: { url: string, ext?: string, onComplete?: (result: Asset | null) => void }) {
if (ext) {
assetManager.loadRemote(url, { ext }, (error, res) => {
if (error) {
this.error(`loadRemote ${url} fail`);
return onComplete && onComplete(null);
}
onComplete && onComplete(res);
});
} else {
assetManager.loadRemote(url, (error, res) => {
if (error) {
this.error(`loadRemote ${url} fail`);
return onComplete && onComplete(null);
}
onComplete && onComplete(res);
});
}
}
/**
* 加载远程资源
* @example
* await loadRemoteAsync({url:'', ext:'.png'})
*/
public loadRemoteAsync(params: { url: string, ext?: string }): Promise<Asset | null> {
return new Promise((resolve) => {
this.loadRemote({
...params,
onComplete: resolve
});
});
}
/**
* 设置字体资源
* @param params.bundle 默认为resources
* @param params.path bundle下的相对路径
*
* @example
* setFont({target:label, path:'font/num', bundle:'resources', onComplete:(succ)=>{}})
* setFont({target:label, url:'http://img/a/font',ext:'.ttf', onComplete:(succ)=>{}})
*/
public setFont(params: { target: Label, url: string, ext?: string, onComplete?: (success: boolean) => any, onSuccess?: () => void, onFail?: () => void }): void;
public setFont(params: { target: Label, path: string, bundle?: string, onComplete?: (success: boolean) => any, onSuccess?: () => void, onFail?: () => void }): void;
public setFont(params: { target: Label, path?: string, bundle?: string, url?: string, ext?: string, onComplete?: (success: boolean) => any, onSuccess?: () => void, onFail?: () => void }) {
if (params.url) {
this.loadRemote({
url: params.url,
ext: params.ext,
onComplete: (font: Font) => {
if (!font || !isValid(params.target)) {
params.onFail && params.onFail();
params.onComplete && params.onComplete(false);
return;
}
params.target.font = font;
params.onSuccess && params.onSuccess();
params.onComplete && params.onComplete(true);
}
});
} else {
this.load({
path: params.path,
bundle: params.bundle,
type: Font,
onComplete: (font) => {
if (!font || !isValid(params.target)) {
params.onFail && params.onFail();
params.onComplete && params.onComplete(false);
return;
}
params.target.font = font;
params.onSuccess && params.onSuccess();
params.onComplete && params.onComplete(true);
}
});
}
}
/**
* 设置Spine资源
* @param params.bundle 默认为resources
* @param params.path bundle下的相对路径
*
* @example
* setSpine({target:spine, path:'spine/role', bundle:'resources', onComplete:(succ)=>{}})
*/
public setSpine(params: { target: sp.Skeleton, path: string, bundle?: string, onComplete?: (success: boolean) => any, onSuccess?: () => void, onFail?: () => void }) {
this.load({
path: params.path,
bundle: params.bundle,
type: sp.SkeletonData,
onComplete: (skeletonData) => {
if (!skeletonData || !isValid(params.target)) {
params.onFail && params.onFail();
params.onComplete && params.onComplete(false);
return;
}
params.target.skeletonData = skeletonData;
params.onSuccess && params.onSuccess();
params.onComplete && params.onComplete(true);
}
});
}
/**
* 设置图片资源
* @param params.bundle 默认为resources
* @param params.path bundle下的相对路径
*
* @example
* setSprite({target:sprite, path:'img/a/spriteFrame', bundle:'resources', onComplete:(succ)=>{}})
* setSprite({target:sprite, url:'http://img/a/avatar',ext:'.png', onComplete:(succ)=>{}})
*/
public setSprite(params: { target: Sprite, url: string, ext?: string, onComplete?: (success: boolean) => any, onSuccess?: () => void, onFail?: () => void }): void;
public setSprite(params: { target: Sprite, path: string, bundle?: string, onComplete?: (success: boolean) => any, onSuccess?: () => void, onFail?: () => void }): void;
public setSprite(params: { target: Sprite, path?: string, bundle?: string, url?: string, ext?: string, onComplete?: (success: boolean) => any, onSuccess?: () => void, onFail?: () => void }) {
if (params.url) {
this.loadRemote({
url: params.url,
ext: params.ext,
onComplete: (imageAsset: ImageAsset) => {
if (!imageAsset || !isValid(params.target)) {
params.onFail && params.onFail();
params.onComplete && params.onComplete(false);
return;
}
const spriteFrame = SpriteFrame.createWithImage(imageAsset);
params.target.spriteFrame = spriteFrame;
params.onSuccess && params.onSuccess();
params.onComplete && params.onComplete(true);
}
});
} else {
this.load({
path: params.path,
bundle: params.bundle,
type: SpriteFrame,
onComplete: (spriteFrame) => {
if (!spriteFrame || !isValid(params.target)) {
params.onFail && params.onFail();
params.onComplete && params.onComplete(false);
return;
}
params.target.spriteFrame = spriteFrame;
params.onSuccess && params.onSuccess();
params.onComplete && params.onComplete(true);
}
});
}
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "b3bf5337-0c73-4771-87b5-9e735962be81",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "a99e9a5e-e037-428c-b8f4-e5ad266db8ee",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@@ -0,0 +1,152 @@
import { AudioClip, AudioSource, Node } from 'cc';
export default class Audio {
private volume = 1;
private volumeScale = 1;
private mute = false;
private endedCallback: Function = null;
private startedCallback: Function = null;
private _playing = false;
public get playing() {
return this._playing;
}
private set playing(value) {
this._playing = value;
}
private _paused = false;
public get paused() {
return this._paused;
}
private set paused(value) {
this._paused = value;
}
private audioSource: AudioSource = null;
constructor() {
const node = new Node('audio');
this.audioSource = node.addComponent(AudioSource);
node.on(AudioSource.EventType.ENDED, this.onAudioEnded, this);
node.on(AudioSource.EventType.STARTED, this.onAudioStarted, this);
}
private onAudioEnded() {
if (this.endedCallback) {
const endedCallback = this.endedCallback;
this.endedCallback = null;
endedCallback();
}
}
private onAudioStarted() {
if (this.startedCallback) {
const startedCallback = this.startedCallback;
this.startedCallback = null;
startedCallback();
}
}
play(clip: AudioClip, onEnded: Function = null, onStarted: Function = null) {
this.audioSource.clip = clip;
this.endedCallback = onEnded;
this.startedCallback = onStarted;
this.audioSource.play();
this.playing = true;
this.paused = false;
return this;
}
stop() {
this.playing = false;
this.paused = false;
this.audioSource.stop();
this.audioSource.node.emit(AudioSource.EventType.ENDED);
return this;
}
pause() {
if (!this.playing) return this;
this.paused = true;
this.audioSource.pause();
return this;
}
resume() {
if (!this.playing) return this;
if (!this.paused) return this;
this.paused = false;
this.audioSource.play();
return this;
}
setVolume(volume = 1, scale?: number) {
this.volume = volume;
if (typeof scale === 'number') this.volumeScale = scale;
this.audioSource.volume = volume * this.volumeScale * (this.mute ? 0 : 1);
return this;
}
getVolume() {
return this.volume;
}
setVolumeScale(scale = 1) {
this.volumeScale = scale;
this.audioSource.volume = this.volume * scale * (this.mute ? 0 : 1);
return this;
}
getVolumeScale() {
return this.volumeScale;
}
setLoop(loop: boolean) {
this.audioSource.loop = loop;
return this;
}
getLoop() {
return this.audioSource.loop;
}
setMute(mute = true) {
this.mute = mute;
this.setVolume(this.volume);
return this;
}
getMute() {
return this.mute;
}
onEnded(endedCallback: Function) {
this.endedCallback = endedCallback;
return this;
}
clear() {
this.volume = 1;
this.volumeScale = 1;
this.mute = false;
this.paused = false;
this.endedCallback = null;
this.startedCallback = null;
if (this.audioSource) {
this.audioSource.stop();
this.audioSource.volume = 1;
this.audioSource.clip = null;
this.audioSource.loop = false;
}
return this;
}
destroy() {
this.clear();
this.audioSource.destroy();
this.audioSource.node.destroy();
this.audioSource = null;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "a40e77a6-cdac-445c-a50c-a8ced2411dbc",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,287 @@
import { AudioClip } from 'cc';
import Audio from './Audio';
import AudioManager from './AudioManager';
export default class AudioEngine {
private static _inst: AudioEngine = null;
static get inst() {
if (!this._inst) this._inst = new AudioEngine();
return this._inst;
}
private constructor() { }
/**effect的id从1开始music的id始终为0 */
private audioID = 1;
private endedCallbackMap: Map<number, Function> = new Map();
private effectMap: Map<number, Audio> = new Map();
private music: Audio = null;
private musicMute = false;
private musicVolumeScale = 1;
private effectMute = false;
private effectVolumeScale = 1;
////////////////////////////////
// 音效 //
////////////////////////////////
playEffect(audioClip: AudioClip, volume = 1, loop = false, onStarted: (audioID: number) => any = null, onEnded: Function = null) {
if (this.audioID > 100000) this.audioID = 1;
const audioID = this.audioID++;
const audio = AudioManager.inst.getAudio();
this.effectMap.set(audioID, audio);
if (onEnded) this.endedCallbackMap.set(audioID, onEnded);
audio.setLoop(loop)
.setMute(this.effectMute)
.setVolume(volume, this.effectVolumeScale)
.play(audioClip, () => {
AudioManager.inst.putAudio(audio);
this.effectMap.delete(audioID);
const callback = this.endedCallbackMap.get(audioID);
if (callback) {
this.endedCallbackMap.delete(audioID);
callback();
}
}, () => {
onStarted && onStarted(audioID);
});
return audioID;
}
stopEffect(id: number) {
return !!this.effectMap.get(id)?.stop();
}
stopAllEffects() {
this.effectMap.forEach((audio) => {
audio.stop();
});
}
pauseEffect(id: number) {
return !!this.effectMap.get(id)?.pause();
}
pauseAllEffects() {
this.effectMap.forEach((audio) => {
audio.pause();
});
}
resumeEffect(id: number) {
return !!this.effectMap.get(id)?.resume();
}
resumeAllEffects() {
this.effectMap.forEach((audio) => {
audio.resume();
});
}
setEffectMute(id: number, mute: boolean) {
return !!this.effectMap.get(id)?.setMute(mute);
}
getEffectMute(id: number) {
return !!this.effectMap.get(id)?.getMute();
}
setEffectVolume(id: number, volume: number) {
return !!this.effectMap.get(id)?.setVolume(volume);
}
getEffectVolume(id: number) {
return this.effectMap.get(id)?.getVolume() || 0;
}
setAllEffectsVolume(volume: number) {
this.effectMap.forEach((audio) => {
audio.setVolume(volume);
});
}
setEffectVolumeScale(id: number, volume: number) {
return !!this.effectMap.get(id)?.setVolumeScale(volume);
}
getEffectVolumeScale(id: number) {
return this.effectMap.get(id)?.getVolumeScale() || 0;
}
setGlobalEffectsVolumeScale(scale: number) {
this.effectVolumeScale = scale;
this.effectMap.forEach((audio) => {
audio.setVolumeScale(scale);
});
}
getGlobalEffectsVolumeScale() {
return this.effectVolumeScale;
}
setGlobalEffectsMute(mute: boolean) {
this.effectMute = mute;
this.effectMap.forEach((audio) => {
audio.setMute(mute);
});
}
getGlobalEffectsMute() {
return this.effectMute;
}
////////////////////////////////
// 音乐 //
////////////////////////////////
playMusic(audioClip: AudioClip, volume = 1, onStarted: Function = null) {
this.stopMusic();
this.music = AudioManager.inst.getAudio();
this.music
.setLoop(true)
.setMute(this.musicMute)
.setVolume(volume, this.musicVolumeScale)
.play(audioClip, null, onStarted);
return 0;
}
stopMusic() {
if (!this.music) return false;
this.music.destroy();
this.music = null;
return true;
}
pauseMusic() {
if (!this.music) return false;
this.music.pause();
return true;
}
resumeMusic() {
if (!this.music) return false;
this.music.resume();
return true;
}
setMusicVolume(volume: number) {
if (!this.music) return false;
this.music.setVolume(volume);
return true;
}
getMusicVolume() {
if (!this.music) return -1;
return this.music.getVolume();
}
setMusicVolumeScale(scale: number) {
this.musicVolumeScale = scale;
this.music?.setVolumeScale(scale);
return true;
}
getMusicVolumeScale() {
return this.musicVolumeScale;
}
setMusicMute(mute: boolean) {
this.musicMute = mute;
this.music?.setMute(mute);
return true;
}
getMusicMute() {
return this.musicMute;
}
////////////////////////////////
// 通用 //
////////////////////////////////
setEndedCallback(audioID: number, callback: Function) {
if (audioID === 0) {
return !!this.music?.onEnded(callback);
} else {
if (this.effectMap.has(audioID)) {
this.endedCallbackMap.set(audioID, callback);
return true;
}
return false;
}
}
stop(audioID: number) {
if (audioID === 0) {
return this.stopMusic();
} else {
return this.stopEffect(audioID);
}
}
pause(audioID: number) {
if (audioID === 0) {
return this.pauseMusic();
} else {
return this.pauseEffect(audioID);
}
}
resume(audioID: number) {
if (audioID === 0) {
return this.resumeMusic();
} else {
return this.resumeEffect(audioID);
}
}
pauseAll() {
this.pauseMusic();
this.pauseAllEffects();
}
resumeAll() {
this.resumeMusic();
this.resumeAllEffects();
}
stopAll() {
this.stopMusic();
this.stopAllEffects();
}
setVolume(audioID: number, volume: number) {
if (audioID === 0) {
return this.setMusicVolume(volume);
} else {
return this.setEffectVolume(audioID, volume);
}
}
getVolume(audioID: number) {
if (audioID === 0) {
return this.getMusicVolume();
} else {
return this.getEffectVolume(audioID);
}
}
setVolumeScale(audioID: number, scale: number) {
if (audioID === 0) {
return this.setMusicVolumeScale(scale);
} else {
return this.setEffectVolumeScale(audioID, scale);
}
}
getVolumeScale(audioID: number) {
if (audioID === 0) {
return this.getMusicVolumeScale();
} else {
return this.getEffectVolumeScale(audioID);
}
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "7a638f5c-801f-4e69-88cc-3fc72e1be985",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,24 @@
import Audio from './Audio';
export default class AudioManager {
private static _inst: AudioManager = null;
static get inst() {
if (!this._inst) this._inst = new AudioManager();
return this._inst;
}
private constructor() { }
private audioArray: Audio[] = [];
getAudio() {
if (this.audioArray.length) {
return this.audioArray.pop();
}
return new Audio();
}
putAudio(audio: Audio) {
audio.clear();
this.audioArray.push(audio);
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "5902cecf-4966-4675-95a9-ab0f0f271857",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,717 @@
import { AssetManager, AudioClip, Button, Game, _decorator, game, isValid, sys } from 'cc';
import { IEffectName, IMusicName } from '../../../../../assets/app-builtin/app-admin/executor';
import Core from '../../Core';
import BaseManager from '../../base/BaseManager';
import AudioEngine from './AudioEngine';
const { ccclass } = _decorator;
interface playMusic<T> { name: T, volume?: number, force?: boolean, onPlay?: Function, onError?: Function }
interface playEffect<T> { name: T, volume?: number, loop?: boolean, interval?: number, onPlay?: (audioID: number) => any, onError?: Function, onEnded?: Function }
interface playMusicAsync<T> { name: T, volume?: number, force?: boolean }
interface playEffectAsync<T> { name: T, volume?: number, loop?: boolean, interval?: number, onEnded?: Function }
interface playMusicWidthBundle { name: string, bundle: string, volume?: number, force?: boolean, onPlay?: Function, onError?: Function }
interface playEffectWidthBundle { name: string, bundle: string, volume?: number, loop?: boolean, interval?: number, onPlay?: (audioID: number) => any, onError?: Function, onEnded?: Function }
interface playMusicWidthBundleAsync { name: string, bundle: string, volume?: number, force?: boolean }
interface playEffectWidthBundleAsync { name: string, bundle: string, volume?: number, loop?: boolean, interval?: number, onEnded?: Function }
const storage = {
set(key: string, value: any) {
sys.localStorage.setItem(key, JSON.stringify(value));
},
get(key: string) {
const data = sys.localStorage.getItem(key);
if (data && typeof data === 'string') {
return JSON.parse(data);
}
return undefined;
}
};
/**
* 音乐名字枚举
*/
const MusicName: { [key in IMusicName]: key } = new Proxy({} as any, {
get: function (target, key) {
if (target[key]) return target[key];
target[key] = key;
return key;
}
});
/**
* 音效名字枚举
*/
const EffectName: { [key in IEffectName]: key } = new Proxy({} as any, {
get: function (target, key) {
if (target[key]) return target[key];
target[key] = key;
return key;
}
});
const BundleName = 'app-sound';
@ccclass('SoundManager')
export default class SoundManager<E extends string, M extends string> extends BaseManager {
/**静态设置 */
static setting: {
/**预加载 */
preload?: (IMusicName | IEffectName)[],
/**默认播放的音乐名 */
defaultMusicName?: IMusicName | '',
/**默认音乐的音量 */
defaultMusicVolume?: number,
/**默认按钮的音效名 */
defaultEffectName?: IEffectName | '',
/**默认按钮音效的音量 */
defaultEffectVolume?: number
} = {};
/**音乐名字枚举 */
static MusicName = MusicName;
/**音效名字枚举 */
static EffectName = EffectName;
private musicMuteCacheKey = 'SoundManager:MusicMute';
private effectMuteCacheKey = 'SoundManager:EffectMute';
private musicVolumeScaleCacheKey = 'SoundManager:MusicVolumeScale';
private effectVolumeScaleCacheKey = 'SoundManager:EffectVolumeScale';
private defaultMusicName = '';
private defaultMusicVolume = 0.2;
private defaultEffectName = '';
private defaultEffectVolume = 1;
private audioCache = {};
private effectInterval: { [key in string]: number } = {};
private playingMusic = { uuid: '', id: -1, name: '', volume: 1, playing: false, paused: false };
protected init(finish: Function) {
const setting = SoundManager.setting;
// 默认音乐
if (setting.defaultMusicName) this.defaultMusicName = setting.defaultMusicName;
if (typeof setting.defaultMusicVolume === 'number') this.defaultMusicVolume = setting.defaultMusicVolume;
// 默认按钮音效
if (setting.defaultEffectName) this.defaultEffectName = setting.defaultEffectName;
if (typeof setting.defaultEffectVolume === 'number') this.defaultEffectVolume = setting.defaultEffectVolume;
if (this.musicMuteCacheKey) {
const musicMute = storage.get(this.musicMuteCacheKey) === true;
AudioEngine.inst.setMusicMute(musicMute);
} else {
this.warn('musicMuteCacheKey不能为空');
}
if (this.effectMuteCacheKey) {
const effectMute = storage.get(this.effectMuteCacheKey) === true;
AudioEngine.inst.setGlobalEffectsMute(effectMute);
} else {
this.warn('effectMuteCacheKey不能为空');
}
if (this.musicVolumeScaleCacheKey) {
const musicVolumeScale = storage.get(this.musicVolumeScaleCacheKey);
if (typeof musicVolumeScale === 'number') AudioEngine.inst.setMusicVolumeScale(musicVolumeScale);
} else {
this.warn('musicVolumeScaleCacheKey不能为空');
}
if (this.effectVolumeScaleCacheKey) {
const effectVolumeScale = storage.get(this.effectVolumeScaleCacheKey);
if (typeof effectVolumeScale === 'number') AudioEngine.inst.setGlobalEffectsVolumeScale(effectVolumeScale);
} else {
this.warn('effectVolumeScaleCacheKey不能为空');
}
super.init(finish);
// 预加载
setting.preload?.forEach((path: string) => {
Core.inst.manager.loader.preload({
bundle: BundleName,
type: AudioClip,
path: path
});
});
}
protected onLoad() {
game.on(Game.EVENT_HIDE, function () {
AudioEngine.inst.pauseAll();
});
game.on(Game.EVENT_SHOW, function () {
AudioEngine.inst.resumeAll();
});
}
/**
* 预加载声音资源
* @param name sound路径
* @param bundle Bundle名默认为app-sound
*/
public preload(name: string, bundle: string, complete?: (item: AssetManager.RequestItem[] | null) => any): void;
public preload(name: (E | M), complete?: (item: AssetManager.RequestItem[] | null) => any): void;
public preload(name: string, ...args: any[]) {
const bundleName = (args.length >= 1
&& (typeof args[0] === 'string')
? (args[0] || BundleName)
: BundleName
) as string;
const complete = (args.length >= 1
&& (args[args.length - 1] instanceof Function)
? args[args.length - 1]
: null
) as (item: AssetManager.RequestItem[] | null) => any;
if (!name) {
this.error('preload', 'fail');
complete && setTimeout(() => {
if (!isValid(this)) return;
complete(null);
});
return;
}
if (name.indexOf('effect') !== 0 && name.indexOf('music') !== 0) {
this.error('preload', 'fail', name);
complete && setTimeout(() => {
if (!isValid(this)) return;
complete(null);
});
return;
}
// 远程加载
Core.inst.manager.loader.preload({
bundle: bundleName,
path: name,
type: AudioClip,
onComplete: complete
});
}
/**
* 加载声音资源
* @param name sound路径
* @param bundle Bundle名默认为app-sound
* @param progress 加载进度回调
* @param complete 加载完成回调
*/
public load(name: string, bundle: string): void;
public load(name: string, bundle: string, complete: (result: AudioClip | null) => any): void;
public load(name: string, bundle: string, progress: (finish: number, total: number, item: AssetManager.RequestItem) => void, complete: (result: AudioClip | null) => any): void;
public load(name: (E | M)): void;
public load(name: (E | M), complete: (result: AudioClip | null) => any): void;
public load(name: (E | M), progress: (finish: number, total: number, item: AssetManager.RequestItem) => void, complete: (result: AudioClip | null) => any): void;
public load(name: string, ...args: any[]): void {
const bundleName = (args.length >= 1
&& (typeof args[0] === 'string')
? (args[0] || BundleName)
: BundleName
) as string;
const progress = (args.length >= 2
&& (args[args.length - 1] instanceof Function)
&& (args[args.length - 2] instanceof Function)
? args[args.length - 2]
: null
) as (finish: number, total: number, item: AssetManager.RequestItem) => void;
const complete = (args.length >= 1
&& (args[args.length - 1] instanceof Function)
? args[args.length - 1]
: null
) as (result: AudioClip | null) => any;
if (!name) {
this.error('load', 'fail');
complete && setTimeout(() => {
if (!isValid(this)) return;
complete(null);
});
return;
}
const soundName = `${bundleName}://${name}`;
// 判断有无缓存
const audio = this.audioCache[soundName];
if (audio) {
complete && setTimeout(() => {
if (!isValid(this)) return;
complete(audio);
});
return;
}
// 远程加载
Core.inst.manager.loader.load({
path: name,
bundle: bundleName,
type: AudioClip,
onProgress: progress,
onComplete: (audioClip) => {
if (!isValid(this)) return;
if (audioClip) {
this.audioCache[soundName] = audioClip;
complete && complete(audioClip);
} else {
complete && complete(null);
}
}
});
}
/**
* 释放声音资源
* @param name 声音路径
* @param bundle Bundle名默认为app-sound
*/
public release(name: string, bundle: string): void;
public release(name: E | M): void;
public release(name: string, bundle?: string) {
const bundleName = bundle || BundleName;
const soundName = `${bundleName}://${name}`;
delete this.audioCache[soundName];
Core.inst.manager.loader.release({ bundle: bundleName, path: name, type: AudioClip });
}
/**
* 播放默认音乐
*/
public playDefaultMusic(onPlay?: Function) {
if (this.defaultMusicName) {
this.playMusic({ name: <M>this.defaultMusicName, volume: this.defaultMusicVolume, onPlay });
} else {
this.warn('defaultMusicName 不存在');
}
}
/**
* 播放默认音效
*/
public playDefaultEffect(onPlay?: (audioID: number) => void) {
if (this.defaultEffectName) {
this.playEffect({ name: <E>this.defaultEffectName, volume: this.defaultEffectVolume, onPlay });
} else {
this.warn('defaultEffectName 不存在');
}
}
/**
* 设置按钮点击播放的音效,优先级高于默认音效
* @param name 音效(如果为空,则使用默认音效)
* @param opts.volume 音量
* @param opts.interval 多少秒内不会重复播放
*/
public setButtonEffect(target: Button, name?: E, opts?: {
volume: number,
interval: number
}) {
if (name) {
const { volume = 1, interval = 0 } = opts || {};
//@ts-ignore
target.node['useDefaultEffect'] = false;
target.node.targetOff(this);
target.node.on(Button.EventType.CLICK, function (this: SoundManager<E, M>) {
this.playEffect({ name, volume, interval });
}, this);
} else {
//@ts-ignore
target.node['useDefaultEffect'] = true;
target.node.targetOff(this);
}
}
/**
* 播放音效
* @param name 音效
* @param bundle Bundle名默认为app-sound
* @param loop 循环播放
* @param volume 音量
* @param interval 多少秒内不会重复播放
*/
public playEffect({ name, volume, loop, interval, onEnded, onPlay, onError }: playEffect<E>): void;
public playEffect({ name, bundle, volume, loop, interval, onEnded, onPlay, onError }: playEffectWidthBundle): void;
public playEffect({ name, bundle, volume = 1, loop = false, interval = 0, onEnded, onPlay, onError }) {
if (!name) {
onError && onError();
return;
}
const bundleName = bundle || BundleName;
const soundName = `${bundleName}://${name}`;
// 静音不允许播放
if (this.isEffectMute) {
onError && onError();
return;
}
// 正在播放中,不允许重复播放
if (this.effectInterval[soundName] && Date.now() < this.effectInterval[soundName]) {
onError && onError();
return;
}
// 加载音乐
this.load(name, bundleName, (audioClip) => {
if (!isValid(this)) {
onError && onError();
return;
}
// 静音不允许播放
if (this.isEffectMute) {
onError && onError();
return;
}
// 正在播放中,不允许重复播放
if (this.effectInterval[soundName] && Date.now() < this.effectInterval[soundName]) {
onError && onError();
return;
}
if (!audioClip) {
this.error(`playEffect ${name} 不存在或加载失败`);
onError && onError();
return;
}
if (interval > 0) {
this.effectInterval[soundName] = Date.now() + interval * 1000;
}
AudioEngine.inst.playEffect(audioClip, volume, loop, onPlay, onEnded);
});
}
/**
* 播放音效
* @param name 音效
* @param bundle Bundle名默认为app-sound
* @param loop 循环播放
* @param volume 音量
* @param interval 多少秒内不会重复播放
* @returns 如果Promise返回值是null(非真),则播放失败
*/
public async playEffectAsync(params: playEffectAsync<E>): Promise<number>;
public async playEffectAsync(params: playEffectWidthBundleAsync): Promise<number>;
public async playEffectAsync(params: any): Promise<number> {
return new Promise((resolve) => {
this.playEffect({
...params,
onPlay: (audioID) => {
resolve(audioID);
},
onError: () => {
resolve(null);
}
});
});
}
/**
* 暂停音效
* @param id
* @returns
*/
public pauseEffect(id: number) {
return AudioEngine.inst.pauseEffect(id);
}
/**
* 暂停所有音效
* @returns
*/
public pauseAllEffects() {
return AudioEngine.inst.pauseAllEffects();
}
/**
* 恢复音效
* @param id
* @returns
*/
public resumeEffect(id: number) {
return AudioEngine.inst.resumeEffect(id);
}
/**
* 恢复所有音效
* @returns
*/
public resumeAllEffects() {
return AudioEngine.inst.resumeAllEffects();
}
/**
* 停止音效
* @param id
* @returns
*/
public stopEffect(id: number) {
return AudioEngine.inst.stopEffect(id);
}
/**
* 停止所有音效
* @returns
*/
public stopAllEffects() {
return AudioEngine.inst.stopAllEffects();
}
/**
* 播放音乐
* @param volume 音量
* @param bundle Bundle名默认为app-sound
* @param force 是否强制重新播放
*/
public playMusic(params: playMusic<M>): void;
public playMusic(params: playMusicWidthBundle): void;
public playMusic({ name, bundle, volume = 1, force = false, onPlay, onError }): void {
if (!name) {
onError && onError();
return;
}
const bundleName = bundle || BundleName;
const soundName = `${bundleName}://${name}`;
// 该音乐正在播放中
if (!force && this.playingMusic.id !== -1 && this.playingMusic.name === soundName) {
AudioEngine.inst.setMusicVolume(volume);
onPlay && onPlay();
return;
}
// 先停止当前音乐
this.stopMusic();
// 播放操作uuid
const uuid = this.createUUID();
this.playingMusic.uuid = uuid;
// 记录要播放音乐的名字
this.playingMusic.name = soundName;
// 记录要播放音乐的音量
this.playingMusic.volume = volume;
// 记录音乐状态
this.playingMusic.playing = true;
this.playingMusic.paused = false;
// 静音
if (this.isMusicMute) {
onPlay && onPlay();
return;
}
// 加载音乐
this.load(name, bundleName, (audioClip) => {
if (!isValid(this)) {
onError && onError();
return;
}
// 不合法
if (this.playingMusic.id !== -1) {
onError && onError();
return;
}
if (this.playingMusic.name !== soundName) {
onError && onError();
return;
}
if (this.playingMusic.uuid !== this.playingMusic.uuid) {
onError && onError();
return;
}
// 不存在
if (!audioClip) {
this.error(`playMusic ${name} 不存在或加载失败`);
onError && onError();
return;
}
// 静音
if (this.isMusicMute) {
onPlay && onPlay();
return;
}
this.playingMusic.id = AudioEngine.inst.playMusic(audioClip, volume, onPlay);
});
}
/**
* 播放音乐
* @param volume 音量
* @param bundle Bundle名默认为app-sound
* @param force 是否强制重新播放
* @returns 如果Promise返回值是false则播放失败
*/
public playMusicAsync(params: playMusicAsync<M>): Promise<boolean>;
public playMusicAsync(params: playMusicWidthBundleAsync): Promise<boolean>;
public playMusicAsync(params: any): Promise<boolean> {
return new Promise((resolve) => {
this.playMusic({
...params,
onPlay: () => {
resolve(true);
},
onError: () => {
resolve(false);
}
});
});
}
/**
* 重新播放音乐
*/
public replayMusic(onPlay?: Function) {
if (!this.playingMusic.playing) return;
if (!this.playingMusic.name) return;
if (this.playingMusic.name.indexOf('://') > 0) {
const [bundle, name] = this.playingMusic.name.split('://');
this.playMusic({
name,
bundle,
volume: this.playingMusic.volume,
force: true,
onPlay
});
} else {
this.playMusic({
name: this.playingMusic.name as any,
volume: this.playingMusic.volume,
force: true,
onPlay
});
}
}
/**
* 暂停音乐
*/
public pauseMusic() {
if (!this.playingMusic.playing) return false;
this.playingMusic.paused = true;
return AudioEngine.inst.pauseMusic();
}
/**
* 恢复音乐
*/
public resumeMusic() {
if (!this.playingMusic.playing) return false;
this.playingMusic.paused = false;
return AudioEngine.inst.resumeMusic();
}
/**
* 停止音乐
*/
public stopMusic() {
this.playingMusic.playing = false;
this.playingMusic.paused = false;
this.playingMusic.volume = 1;
this.playingMusic.name = '';
this.playingMusic.uuid = '';
this.playingMusic.id = -1;
return AudioEngine.inst.stopMusic();
}
/**
* 设置音乐静音
* @param mute 是否静音
* @param isCache 静音状态是否写入缓存(通过localstorage)
*/
public setMusicMute(mute: boolean, isCache = false) {
isCache && storage.set(this.musicMuteCacheKey, mute);
AudioEngine.inst.setMusicMute(mute);
if (!mute && this.playingMusic.name) {
if (this.playingMusic.name.indexOf('://') > 0) {
const [bundle, name] = this.playingMusic.name.split('://');
this.playMusic({
name,
bundle,
volume: this.playingMusic.volume,
});
} else {
this.playMusic({
name: this.playingMusic.name as any,
volume: this.playingMusic.volume
});
}
}
}
/**
* 音乐是否正在播放
*/
get isMusicPlaying() {
return this.playingMusic.playing;
}
/**
* 音乐是否暂停
*/
get isMusicPaused() {
return this.playingMusic.paused;
}
/**
* 音乐是否静音
*/
public get isMusicMute() {
return AudioEngine.inst.getMusicMute();
}
/**
* 设置音效静音
* @param mute 是否静音
* @param isCache 静音状态是否写入缓存(通过localstorage)
*/
public setEffectMute(mute: boolean, isCache = false) {
AudioEngine.inst.setGlobalEffectsMute(mute);
isCache && storage.set(this.effectMuteCacheKey, mute);
}
/**
* 音效是否静音
*/
public get isEffectMute() {
return AudioEngine.inst.getGlobalEffectsMute();
}
/**
* 设置音乐音量倍率
* @param scale
* @param isCache 音量倍率是否写入缓存(通过localstorage)
*/
public setMusicVolumeScale(scale: number, isCache = false) {
AudioEngine.inst.setMusicVolumeScale(scale);
isCache && storage.set(this.musicVolumeScaleCacheKey, scale);
}
/**
* 音乐音量倍率
*/
public get musicVolumeScale() {
return AudioEngine.inst.getMusicVolumeScale();
}
/**
* 设置音效音量倍率
* @param scale
* @param isCache 音量倍率是否写入缓存(通过localstorage)
*/
public setEffectVolumeScale(scale: number, isCache = false) {
AudioEngine.inst.setGlobalEffectsVolumeScale(scale);
isCache && storage.set(this.effectVolumeScaleCacheKey, scale);
}
/**
* 音效音量倍率
*/
public get effectVolumeScale() {
return AudioEngine.inst.getGlobalEffectsVolumeScale();
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "580021da-d9a7-4e59-bb76-2c3afcae6053",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "ea45b442-c58f-49d1-9b11-edc7bc719a73",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@@ -0,0 +1,276 @@
import { _decorator } from 'cc';
import BaseManager from '../../base/BaseManager';
const { ccclass } = _decorator;
type ICallback = (...args: any[]) => any;
class DailyTimer {
// 获取当前时间相对于当日零点的毫秒数
private static getDayTimeMs(date: Date): number {
return (
date.getHours() * 3600 +
date.getMinutes() * 60 +
date.getSeconds()
) * 1000 + date.getMilliseconds();
}
// 静态方法解析时间为毫秒数
private static parseTimeToMs(h: number, m: number, s: number): number {
return ((h * 3600 + m * 60 + s) * 1000) % 86400000;
}
private readonly startMs: number; // 起始时间毫秒数(相对于当日零点)
private readonly endMs: number; // 结束时间毫秒数
// 用于检查当前时间是否可触发回调
private checkDay = 0;
constructor(
time: string,
public readonly callback: ICallback,
public readonly target?: unknown,
public readonly once: boolean = false
) {
// 使用解构赋值提高可读性
const [startSegment, endSegment = startSegment] = time.split('-');
// 开始时间
const [startH = 0, startM = 0, startS = 0] = startSegment.split(':').map(
part => Math.max(0, parseInt(part, 10) || 0)
);
this.startMs = DailyTimer.parseTimeToMs(startH, startM, startS);
// 结束时间
const [endH = 0, endM = 0, endS = 0] = endSegment.split(':').map(
part => Math.max(0, parseInt(part, 10) || 0)
);
this.endMs = DailyTimer.parseTimeToMs(endH, endM, endS);
// 结束时间与开始时间不能相同
if (this.endMs === this.startMs) {
if (startM === 0 && startS === 0) {
this.endMs = DailyTimer.parseTimeToMs(startH + 1, startM, startS);
} else if (startS === 0) {
this.endMs = DailyTimer.parseTimeToMs(startH, startM + 1, startS);
} else {
this.endMs = DailyTimer.parseTimeToMs(startH, startM, startS + 1);
}
}
}
// 获取当前时间是否在时间范围内
private isInRange(now: Date): boolean {
const currentMs = DailyTimer.getDayTimeMs(now);
// 处理跨天时间段(如 23:00-01:00
return this.startMs <= this.endMs
? currentMs >= this.startMs && currentMs < this.endMs
: currentMs >= this.startMs || currentMs < this.endMs;
}
update(now: Date): boolean {
const dateDay = now.getDay();
if (this.checkDay === dateDay) return false;
if (!this.isInRange(now)) return false;
this.checkDay = dateDay;
this.callback.call(this.target);
return this.once;
}
}
class IntervalTimer {
private elapsed: number = 0;
constructor(
public readonly interval: number,
public readonly callback: ICallback,
public readonly target?: unknown,
public readonly once: boolean = false
) { }
update(dt: number): boolean {
this.elapsed += dt;
let completed = false;
// 处理可能多次触发的情况当dt > interval时
while (this.elapsed >= this.interval) {
this.callback.call(this.target);
this.elapsed -= this.interval;
if (this.once) {
completed = true;
break;
}
}
return completed;
}
}
class Timer {
static update(timer: Timer, dt: number) {
return timer.update(dt);
}
private intervalTimer: IntervalTimer[] = [];
/**
* 注册定时器
* @param interval
* @param callback
* @param target
* @param once
*/
register(
interval: number,
callback: ICallback,
target?: unknown,
once?: boolean
): void {
const timer = new IntervalTimer(interval, callback, target, once || false);
this.intervalTimer.push(timer);
}
/**
* 取消定时器
* @param callback
* @param target
*/
unregister(callback: ICallback, target?: unknown): void {
if (typeof target === 'undefined') {
this.intervalTimer = this.intervalTimer.filter(
timer => timer.callback !== callback
);
} else {
this.intervalTimer = this.intervalTimer.filter(
timer => !(timer.callback === callback && timer.target === target)
);
}
}
/**
* 取消所有定时器
*/
unregisterAll() {
this.intervalTimer = [];
}
private dailyTimers: DailyTimer[] = [];
/**
* 注册每日触发器
* @param time 24小时制,精确到秒
*
* @example
* registerDailyTrigger('16', ...) 等同于 registerDailyTrigger('16-17', ...)
* registerDailyTrigger('8-9:00', ...) 等同于 registerDailyTrigger('8', ...)
* registerDailyTrigger('8:00:01-24', ...)
*/
registerDailyTrigger(
time: string,
callback: ICallback,
target?: unknown,
once?: boolean
) {
const timer = new DailyTimer(time, callback, target, once || false);
this.dailyTimers.push(timer);
}
/**
* 取消每日触发器
*/
unregisterDailyTrigger(callback: ICallback, target?: unknown) {
if (typeof target === 'undefined') {
this.dailyTimers = this.dailyTimers.filter(
timer => timer.callback !== callback
);
} else {
this.dailyTimers = this.dailyTimers.filter(
timer => !(timer.callback === callback && timer.target === target)
);
}
}
/**
* 取消所有每日触发器
*/
unregisterAllDailyTrigger() {
this.dailyTimers = [];
}
/**
* 清除所有定时器和触发器
*/
clear() {
this.intervalTimer = [];
this.dailyTimers = [];
}
protected update(dt: number): void {
for (let index = 0; index < this.intervalTimer.length; index++) {
const timer = this.intervalTimer[index];
if (timer.update(dt)) {
this.intervalTimer.splice(index, 1);
index--;
}
}
const date = new Date();
for (let index = 0; index < this.dailyTimers.length; index++) {
const timer = this.dailyTimers[index];
if (timer.update(date)) {
this.dailyTimers.splice(index, 1);
index--;
}
}
}
}
@ccclass('TimerManager')
export default class TimerManager extends BaseManager {
private timers: Map<string | number | Symbol, Timer> = new Map();
/**
* 清除所有定时器
*/
clear() {
this.timers.forEach((timer) => {
timer.clear();
});
this.timers.clear();
}
/**
* 删除定时器
* @param key 定时器key
*/
delete(key: string | number | Symbol) {
const timer = this.timers.get(key);
if (!timer) return;
this.timers.delete(key);
timer.clear();
}
/**
* 获取定时器
* @param key 定时器key
*/
get(key: string | number | Symbol): Timer {
if (this.timers.has(key)) {
return this.timers.get(key);
}
const timer = new Timer();
this.timers.set(key, timer);
return timer;
}
protected update(dt: number): void {
this.timers.forEach((timer) => {
Timer.update(timer, dt);
});
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "b5636f8d-3516-4510-aaba-74f9202b8a9f",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "714739c4-f4c0-4313-bc98-2d350df6b208",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

Binary file not shown.

View File

@@ -0,0 +1,1617 @@
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<T, IShow = any, IShowReturn = any, IHideReturn = any> {
/**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<IShowReturn>,
/**UI触发onHide后 */
onHide?: IShowParamOnHide<IHideReturn>,
/**当code的值为ErrorCode.LogicError时如果返回true则自动重试 */
onError?: (result: string, code: ErrorCode) => true | void,
/**
* @private
* @deprecated
*/
attr?: IShowParamAttr,
}
interface IShowAsyncParams<T, IShow = any, IShowReturn = any> {
/**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<IShowReturn>,
/**当code的值为ErrorCode.LogicError时如果返回true则自动重试 */
onError?: (result: string, code: ErrorCode) => true | void,
/**
* @private
* @deprecated
*/
attr?: IShowParamAttr,
}
interface IHideParams<T, IHide = any, IHideReturn = any> {
name: T,
data?: IHide,
onHide?: IHideParamOnHide<IHideReturn>
}
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<UIName extends string, MiniName extends string> extends BaseManager {
/**静态设置 */
static setting: {
/**预加载列表会在UI初始化阶段进行 */
preload?: (IViewName | IMiniViewName | Array<IViewName | IMiniViewName>)[],
/**默认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<string, boolean>();
// 记录展示加载
private showLoadingMap = new Map<string, boolean>();
// 记录正在加载中的有效的ui
private uiLoadingMap: Map<UIName, string[]> = new Map();
// 记录正在展示中的有效的ui
private uiShowingMap: Map<BaseView, UIName> = new Map();
private showQueue: IShowParams<UIName>[] = [];
/**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<T extends typeof Asset>(target: Component, path: string, type: T, callback?: (item: InstanceType<T> | 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<T extends typeof Asset>(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<T extends typeof Asset>(target: Component, path: string, type: T, callback?: (items: InstanceType<T>[] | 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<T extends typeof Asset>(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<UIName>) {
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<UIName>) {
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<UI extends BaseView>(params
// @ts-ignore
: IShowParams<UIName, Parameters<UI['onShow']>[0], ReturnType<UI['onShow']>, ReturnType<UI['onHide']>>) {
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<UI extends BaseView>(params
// @ts-ignore
: IShowAsyncParams<UIName, Parameters<UI['onShow']>[0], ReturnType<UI['onShow']>>): Promise<ReturnType<UI['onHide']>> {
return new Promise((resolve) => {
this.show({
...params,
onHide(result) {
resolve(result);
}
});
});
}
/**
* 关闭View
* - 此流程一定是同步的
*/
public hide<UI extends BaseView>({ name, data, onHide }
// @ts-ignore
: IHideParams<UIName, Parameters<UI['onHide']>[0], ReturnType<UI['onHide']>>) {
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<UI extends BaseView>({ name, data, onHide }
// @ts-ignore
: IHideParams<UIName, Parameters<UI['onHide']>[0], ReturnType<UI['onHide']>>) {
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<UI extends BaseView>({ name, data, onHide }
// @ts-ignore
: IHideParams<UIName, Parameters<UI['onHide']>[0], ReturnType<UI['onHide']>>) {
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['on']>) {
Node.prototype.on.apply(this.UserInterface, args);
}
/**
* 在2DUI根节点上处理事件
*/
public onceUserInterface(...args: Parameters<Node['once']>) {
Node.prototype.once.apply(this.UserInterface, args);
}
/**
* 在2DUI根节点上处理事件
*/
public offUserInterface(...args: Parameters<Node['off']>) {
Node.prototype.off.apply(this.UserInterface, args);
}
/**
* 在2DUI根节点上处理事件
*/
public targetOffUserInterface(...args: Parameters<Node['targetOff']>) {
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;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "234f62f1-ebd3-4d14-9f6f-0b69c7096449",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "cc9c8895-27f9-49df-aca5-a0d1b9ba6480",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@@ -0,0 +1,114 @@
import { Component, Graphics, Node, Size, UITransform, _decorator } from 'cc';
const { ccclass, property, requireComponent } = _decorator;
@ccclass('UIMgrLoading')
@requireComponent(UITransform)
export default class UIMgrLoading extends Component {
@property(Node)
private loading: Node;
@property({ tooltip: '动画的尺寸' })
private size: Size = new Size(60, 60);
@property({ tooltip: '等待几秒后开始动画' })
private delay = 0;
private progress = 0;
private ringScale = 1;
private reverse = false;
private angleSpeed = 120;
private ringSpeed = 0.02;
private inited = false;
private drawing = false;
private timedown = 0;
init() {
if (this.inited) return;
this.inited = true;
this.progress = 0;
this.ringScale = 1;
this.loading.angle = 0;
this.reverse = false;
this.drawing = false;
this.timedown = this.delay;
this.loading.getComponent(Graphics).clear();
}
clear() {
this.inited = false;
this.drawing = false;
}
/**
* 需要重写
*/
private onDraw() {
const graphics = this.loading.getComponent(Graphics);
const uiTransform = this.loading.getComponent(UITransform);
const centerX = this.size.width * (0.5 - uiTransform.anchorX);
const centerY = this.size.height * (0.5 - uiTransform.anchorY);
const r = Math.min(this.size.width / 2, this.size.height / 2);
const allPI = Math.PI;
const offst = 0;
graphics.clear();
if (this.reverse) {
const start = 0.5 * Math.PI + offst;
const end = 0.5 * Math.PI + this.progress * 2 * allPI + offst;
graphics.arc(centerX, centerY, r, start, end, true);
} else {
const start = 0.5 * Math.PI - offst;
const end = 0.5 * Math.PI - this.progress * 2 * allPI - offst;
graphics.arc(centerX, centerY, r, start, end, false);
}
graphics.stroke();
}
protected update(dt: number): void {
if (!this.inited) return;
// 倒计时
if (!this.drawing) {
if (this.timedown > 0) {
this.timedown -= dt;
}
if (this.timedown <= 0) {
this.drawing = true;
} else {
return;
}
}
// 旋转
this.loading.angle -= this.angleSpeed * dt;
if (this.loading.angle >= 360 || this.loading.angle <= -360) {
this.loading.angle = this.loading.angle % 360;
}
// 进度
if (this.ringScale > 0) {
this.progress = Math.min(1, this.progress + this.ringSpeed * this.ringScale);
if (this.progress == 1) {
this.ringScale = -1;
this.reverse = !this.reverse;
}
} else {
this.progress = Math.max(0, this.progress + this.ringSpeed * this.ringScale);
if (this.progress == 0) {
this.ringScale = 1;
this.reverse = !this.reverse;
}
}
this.onDraw();
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "4a8e5697-ae78-4a4a-8d08-17acc6823a27",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,167 @@
import { Camera, Color, Component, Director, Material, RenderTexture, Sprite, SpriteFrame, UIOpacity, UITransform, _decorator, director } from 'cc';
import Core from '../../../Core';
const { ccclass, property, requireComponent } = _decorator;
@ccclass('UIMgrShade')
@requireComponent(Sprite)
@requireComponent(UIOpacity)
export default class UIMgrShade extends Component {
@property(Material)
private blurMaterial: Material = null;
@property(SpriteFrame)
private shadeFrame: SpriteFrame = null;
@property
private _delay = 0;
@property
get delay() { return this._delay; }
set delay(v) { this._delay = Math.max(v, 0); }
@property
private _begin = 0;
@property
get begin() { return this._begin; }
set begin(v) { if (v >= 0 && v <= 255) this._begin = v; }
@property
private _end = 255;
@property
get end() { return this._end; }
set end(v) { if (v >= 0 && v <= 255) this._end = v; }
@property
private _speed = 10;
@property
get speed() {
if (this.begin == this.end) {
return 0;
} else if (this.begin > this.end) {
return this._speed > 0 ? -this._speed : this._speed;
} else {
return this._speed >= 0 ? this._speed : -this._speed;
}
}
set speed(v) { this._speed = v; }
private get sprite() {
return this.node.getComponent(Sprite);
}
private get opacity() {
return this.node.getComponent(UIOpacity);
}
private inited = false;
private drawing = false;
private timedown = 0;
private blurFrame: SpriteFrame = null;
init(delay: number, begin: number, end: number, speed: number, blur: boolean) {
if (blur) {
director.targetOff(this);
this.inited = false;
this.drawing = false;
this.sprite.color = Color.WHITE;
this.sprite.customMaterial = null;
this.sprite.spriteFrame = this.blurFrame;
if (this.blurFrame) this.blurFrame.flipUVY = false;
let count = 0;
const cameras = director.getScene().getComponentsInChildren(Camera);
director.on(Director.EVENT_BEFORE_RENDER, () => {
count++;
const renderTexture = new RenderTexture();
const size = this.node.getComponent(UITransform);
renderTexture.reset({ width: size.width / 2, height: size.height / 2 });
renderTexture.addRef();
Core.inst.manager.ui.screenshot(renderTexture, {
cameraList: cameras
});
if (count === 1) {
this.blurFrame = new SpriteFrame();
this.blurFrame?.texture?.decRef();
this.blurFrame.texture = renderTexture;
this.blurFrame.flipUVY = true;
this.sprite.spriteFrame = this.blurFrame;
this.sprite.customMaterial = this.blurMaterial;
this.blurMaterial.setProperty('blurLevel', 2);
return;
}
if (count === 5) {
director.targetOff(this);
this.sprite.spriteFrame.flipUVY = false;
this.sprite.customMaterial = null;
return;
}
this.blurFrame?.texture?.decRef();
this.blurFrame.texture = renderTexture;
this.blurFrame.flipUVY = true;
this.sprite.spriteFrame = this.blurFrame;
this.sprite.customMaterial = this.blurMaterial;
this.blurMaterial.setProperty('blurLevel', count === 2 ? 3 : 1);
}, this);
} else {
director.targetOff(this);
this.sprite.spriteFrame = this.shadeFrame;
this.sprite.color = Color.BLACK;
this.sprite.customMaterial = null;
}
this.delay = delay;
this.begin = begin;
this.end = end;
this.speed = speed;
this.drawing = true;
if (this.inited) return;
this.inited = true;
this.timedown = this.delay;
// 初始透明度
this.opacity.opacity = this.timedown > 0 ? 0 : this.begin;
}
clear() {
this.inited = false;
this.drawing = false;
director.targetOff(this);
this.blurFrame?.texture?.decRef();
this.blurFrame?.destroy();
this.blurFrame = null;
this.sprite.spriteFrame = null;
}
protected update(dt: number) {
if (!this.inited) return;
if (!this.drawing) return;
if (this.timedown > 0) {
this.timedown -= dt;
if (this.timedown > 0) return;
// 初始透明度
this.opacity.opacity = this.begin;
}
const uiOpacity = this.opacity;
if (this.speed > 0) {
uiOpacity.opacity += this.speed * dt;
if (uiOpacity.opacity > this.end) {
uiOpacity.opacity = this.end;
}
} else if (this.speed < 0) {
uiOpacity.opacity += this.speed * dt;
if (uiOpacity.opacity < this.end) {
uiOpacity.opacity = this.end;
}
}
if (uiOpacity.opacity == this.end) {
this.drawing = false;
}
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "d0295d9b-b01d-493c-9e31-5ed78e6c33ab",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,111 @@
import { Component, NodePool, Prefab, Tween, UIOpacity, UITransform, _decorator, instantiate, tween, view } from 'cc';
import UIMgrToastCell from './UIMgrToastCell';
const { property, ccclass, requireComponent } = _decorator;
@ccclass('UIMgrToast')
@requireComponent(UITransform)
export default class UIMgrToast extends Component {
@property(Prefab)
private cell: Prefab = null;
/**每条信息显示几秒 */
private lifeTime = 2;
/**消失时花费几秒渐隐 */
private outTime = 0.2;
/**挤压基础速度 */
private squeezeSpeed = 200;
/**节点缓存池子 */
private pool = new NodePool();
add(data: {
message: string,
timeout?: number
}) {
const cell = this.pool.get() || instantiate(this.cell);
cell.setPosition(0, 0, 0);
cell.parent = this.node;
cell.active = true;
cell.getComponent(UIMgrToastCell).init(data.message);
cell.getComponent(UIOpacity).opacity = 255;
tween(cell.getComponent(UIOpacity))
.delay(data.timeout || this.lifeTime)
.to(this.outTime, { opacity: 0 })
.call(() => {
this.pool.put(cell);
})
.start();
}
clear() {
const children = this.node.children;
for (let index = children.length - 1; index >= 0; index--) {
Tween.stopAllByTarget(children[index].getComponent(UIOpacity));
children[index].destroy();
}
}
get size() {
return this.node.children.length;
}
protected onDestroy() {
this.pool.clear();
}
protected update(dt: number) {
const children = this.node.children;
for (let index = children.length - 1, recovery = false; index >= 0; index--) {
const zero = index === children.length - 1;
const curr = children[index];
// 直接触发回收逻辑
if (recovery) {
Tween.stopAllByTarget(curr.getComponent(UIOpacity));
this.pool.put(curr);
continue;
}
if (zero) {
const currUT = curr.getComponent(UITransform);
const lastMaxY = 0 - currUT.height / 2;
const currMinY = curr.position.y + lastMaxY;
if (currMinY > lastMaxY) {
// 存在空隙
const addLen = Math.max(-this.squeezeSpeed * dt * (children.length - index), lastMaxY - currMinY);
curr.setPosition(curr.position.x, curr.position.y + addLen, curr.position.z);
}
} else {
const last = children[index + 1];
const currUT = curr.getComponent(UITransform);
const lastUT = last.getComponent(UITransform);
const currMinY = curr.position.y - currUT.height / 2 - 6;//6像素的间隔
const lastMaxY = last.position.y + lastUT.height / 2;
if (currMinY < lastMaxY) {
// 存在重叠
const addLen = Math.min(this.squeezeSpeed * dt * (children.length - index - 1), lastMaxY - currMinY);
curr.setPosition(curr.position.x, curr.position.y + addLen, curr.position.z);
const winSize = view.getVisibleSize();
if (currMinY > winSize.height / 2) {
// 触发回收逻辑
recovery = true;
Tween.stopAllByTarget(curr.getComponent(UIOpacity));
this.pool.put(curr);
}
} else if (currMinY > lastMaxY) {
// 存在空隙
const addLen = Math.max(-this.squeezeSpeed * dt * (children.length - index), lastMaxY - currMinY);
curr.setPosition(curr.position.x, curr.position.y + addLen, curr.position.z);
}
}
}
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "cde1528c-f66c-40f4-bdd2-27bf138ce1df",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,26 @@
import { Component, Label, UIOpacity, UITransform, _decorator } from 'cc';
const { ccclass, property, requireComponent } = _decorator;
@ccclass('UIMgrToastCell')
@requireComponent(UIOpacity)
@requireComponent(UITransform)
export default class UIMgrToastCell extends Component {
@property(Label)
private title: Label = null;
init(title: string) {
if (title.split('\n').find((v) => v.length > 30)) {
this.title.overflow = Label.Overflow.RESIZE_HEIGHT;
this.title.getComponent(UITransform).width = 600;
} else {
this.title.overflow = Label.Overflow.NONE;
}
this.title.string = title;
this.title.updateRenderData(true);
}
unuse() {
this.title.string = '';
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "6cc631d6-b08e-4ee3-8bde-e307c4288734",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,68 @@
import { _decorator, Component, Director, director, Node } from 'cc';
const { ccclass } = _decorator;
@ccclass('UIMgrZOrder')
export default class UIMgrZOrder extends Component {
private zOrder = false;
private tempArr: Node[] = [];
protected onLoad() {
this.checkUpdateZOrder();
this.node.on(Node.EventType.CHILD_ADDED, this.onChildAdded, this);
this.node.on(Node.EventType.CHILD_REMOVED, this.onChildRemoveed, this);
if (Node.EventType.CHILDREN_ORDER_CHANGED) {
this.node.on(Node.EventType.CHILDREN_ORDER_CHANGED, this.checkUpdateZOrder, this);
} else {
this.node.on(Node.EventType.SIBLING_ORDER_CHANGED, this.checkUpdateZOrder, this);
}
}
protected onDestroy() {
director.off(Director.EVENT_AFTER_UPDATE, this.updateZOrder, this);
this.node.off(Node.EventType.CHILD_ADDED, this.onChildAdded, this);
this.node.off(Node.EventType.CHILD_REMOVED, this.onChildRemoveed, this);
if (Node.EventType.CHILDREN_ORDER_CHANGED) {
this.node.off(Node.EventType.CHILDREN_ORDER_CHANGED, this.checkUpdateZOrder, this);
} else {
this.node.off(Node.EventType.SIBLING_ORDER_CHANGED, this.checkUpdateZOrder, this);
}
}
private onChildAdded(child: Node) {
this.checkUpdateZOrder();
child.on(Node.EventType.TRANSFORM_CHANGED, this.checkUpdateZOrder, this);
}
private onChildRemoveed(child: Node) {
child.off(Node.EventType.TRANSFORM_CHANGED, this.checkUpdateZOrder, this);
}
private checkUpdateZOrder() {
if (this.zOrder) return;
this.zOrder = true;
director.once(Director.EVENT_AFTER_UPDATE, this.updateZOrder, this);
}
/**
* 更新节点树排序
*/
public updateZOrder() {
if (!this.zOrder) return;
Array.prototype.push.apply(this.tempArr, this.node.children);
this.tempArr
.sort((a, b) => {
return (a.position.z - b.position.z)
|| (a.getSiblingIndex() - b.getSiblingIndex());
})
.forEach((child, index) => {
child.setSiblingIndex(index);
});
// 一定要放到最后再设置false
// 避免更新过程中设置siblingIndex
// 导致无限重复调用
this.zOrder = false;
this.tempArr.length = 0;
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "ad5cb510-639e-40c2-acdd-399ad00629b9",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "e3ac29f9-11a4-461c-a404-9e478271b953",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,119 @@
CCEffect %{
techniques:
- name: opaque
passes:
- vert: vs:vert
frag: fs:frag
depthStencilState:
depthTest: false
depthWrite: false
blendState:
targets:
- blend: true
blendSrc: src_alpha
blendSrcAlpha: src_alpha
blendDst: one_minus_src_alpha
blendDstAlpha: one_minus_src_alpha
rasterizerState:
cullMode: none
properties:
blurSize: { value: [750, 1334] }
blurriness: { value: 1, editor: {range:[0, 1, 0.01], slide: true} }
blurLevel: { value: 1, editor: {range:[1, 3, 1], slide: true} }
}%
CCProgram vs %{
precision highp float;
#include <cc-global>
in vec4 a_position;
in vec2 a_texCoord;
out vec2 v_texCoord;
vec4 vert() {
vec4 pos = cc_matViewProj * a_position;
v_texCoord = a_texCoord;
return pos;
}
}%
CCProgram fs %{
precision highp float;
in vec2 v_texCoord;
#pragma builtin(local)
layout(set = 2, binding = 10) uniform sampler2D cc_spriteTexture;
uniform Constant {
vec2 blurSize;
float blurriness;
float blurLevel;
};
// 模糊处理函数
vec4 blur (vec2 pos) {
float sum = 0.0;
vec4 color = vec4(0);
if (blurLevel == 1.0) {
const float blurRadius = 10.0;
const float blurStep = 1.0;
// 采样周边像素并求出加权平均值,得到最终的像素值
for (float rx = -blurRadius; rx <= blurRadius; rx += blurStep) {
for (float ry = -blurRadius; ry <= blurRadius; ry += blurStep) {
vec2 target = pos + vec2(rx / blurSize[0], ry / blurSize[1]);
float weight = (blurRadius - abs(rx)) * (blurRadius - abs(ry));
target.x = clamp(target.x, 0.0, 1.0);
target.y = clamp(target.y, 0.0, 1.0);
color += texture(cc_spriteTexture, target) * weight;
sum += weight;
}
}
} else if(blurLevel == 2.0) {
const float blurRadius = 20.0;
const float blurStep = 2.0;
// 采样周边像素并求出加权平均值,得到最终的像素值
for (float rx = -blurRadius; rx <= blurRadius; rx += blurStep) {
for (float ry = -blurRadius; ry <= blurRadius; ry += blurStep) {
vec2 target = pos + vec2(rx / blurSize[0], ry / blurSize[1]);
float weight = (blurRadius - abs(rx)) * (blurRadius - abs(ry));
target.x = clamp(target.x, 0.0, 1.0);
target.y = clamp(target.y, 0.0, 1.0);
color += texture(cc_spriteTexture, target) * weight;
sum += weight;
}
}
} else {
const float blurRadius = 30.0;
const float blurStep = 3.0;
// 采样周边像素并求出加权平均值,得到最终的像素值
for (float rx = -blurRadius; rx <= blurRadius; rx += blurStep) {
for (float ry = -blurRadius; ry <= blurRadius; ry += blurStep) {
vec2 target = pos + vec2(rx / blurSize[0], ry / blurSize[1]);
float weight = (blurRadius - abs(rx)) * (blurRadius - abs(ry));
target.x = clamp(target.x, 0.0, 1.0);
target.y = clamp(target.y, 0.0, 1.0);
color += texture(cc_spriteTexture, target) * weight;
sum += weight;
}
}
}
color /= sum;
return color;
}
vec4 frag () {
// 获取纹理像素颜色
vec4 o = vec4(1, 1, 1, 1);
o *= texture(cc_spriteTexture, v_texCoord);
// 执行模糊逻辑
vec4 color = blur(v_texCoord);
color.a = o.a;
o = o + (color-o) * blurriness;
return o;
}
}%

View File

@@ -0,0 +1,11 @@
{
"ver": "1.7.1",
"importer": "effect",
"imported": true,
"uuid": "b5376e0b-9e5d-4c91-b0a4-19448fd39179",
"files": [
".json"
],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,36 @@
{
"__type__": "cc.Material",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"_effectAsset": {
"__uuid__": "b5376e0b-9e5d-4c91-b0a4-19448fd39179",
"__expectedType__": "cc.EffectAsset"
},
"_techIdx": 0,
"_defines": [
{}
],
"_states": [
{
"rasterizerState": {},
"depthStencilState": {},
"blendState": {
"targets": [
{}
]
}
}
],
"_props": [
{
"textureSize": {
"__type__": "cc.Vec2",
"x": 750,
"y": 1334
},
"blurDegree": 1
}
]
}

View File

@@ -0,0 +1,11 @@
{
"ver": "1.0.21",
"importer": "material",
"imported": true,
"uuid": "a313b5eb-b939-4c44-affc-32c713440cea",
"files": [
".json"
],
"subMetas": {},
"userData": {}
}

View File

@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "a32ee073-dc9a-4d81-b3c1-54283a151627",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@@ -0,0 +1,508 @@
[
{
"__type__": "cc.Prefab",
"_name": "UIMgrLoading",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"data": {
"__id__": 1
},
"optimizationPolicy": 0,
"persistent": false
},
{
"__type__": "cc.Node",
"_name": "UIMgrLoading",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": null,
"_children": [
{
"__id__": 2
},
{
"__id__": 10
}
],
"_active": true,
"_components": [
{
"__id__": 16
},
{
"__id__": 18
},
{
"__id__": 20
},
{
"__id__": 22
}
],
"_prefab": {
"__id__": 24
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 33554432,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.Node",
"_name": "com_loading",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 3
},
{
"__id__": 5
},
{
"__id__": 7
}
],
"_prefab": {
"__id__": 9
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 33554432,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 4
},
"_contentSize": {
"__type__": "cc.Size",
"width": 1600.0000000000002,
"height": 2300
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "c73aXf03tEr6Ib/PoiFOWn"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 6
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_spriteFrame": {
"__uuid__": "e9d91e97-574a-4fc4-925f-14bf8dd2d82c@f9941",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 1,
"_fillType": 0,
"_sizeMode": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": null,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "cagPaWVyVAfZ9zrBMCGK2P"
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 8
},
"_alignFlags": 45,
"_target": null,
"_left": 0,
"_right": 0,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 68,
"_originalHeight": 2160,
"_alignMode": 2,
"_lockFlags": 0,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "9eIX1qF4BFhZE6ShzpnuQF"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "57nQao2ydNi6bfaoHEW7aP",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.Node",
"_name": "loading",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 11
},
{
"__id__": 13
}
],
"_prefab": {
"__id__": 15
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 33554432,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 10
},
"_enabled": true,
"__prefab": {
"__id__": 12
},
"_contentSize": {
"__type__": "cc.Size",
"width": 0,
"height": 0
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "93a3D7kcNLmKSzPl3DUiom"
},
{
"__type__": "cc.Graphics",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 10
},
"_enabled": true,
"__prefab": {
"__id__": 14
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_lineWidth": 8,
"_strokeColor": {
"__type__": "cc.Color",
"r": 247,
"g": 155,
"b": 17,
"a": 255
},
"_lineJoin": 2,
"_lineCap": 1,
"_fillColor": {
"__type__": "cc.Color",
"r": 247,
"g": 155,
"b": 17,
"a": 255
},
"_miterLimit": 10,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "8cV7EpeTlFy50kiDgQAuVw"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "f1BL1+S9ZHKLm/dYTzeaLU",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 17
},
"_contentSize": {
"__type__": "cc.Size",
"width": 1600.0000000000002,
"height": 2300
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "3brXLn6dBA1Zn+gXeG0T+V"
},
{
"__type__": "4a8e5aXrnhKSo0IF6zGgjon",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 19
},
"loading": {
"__id__": 10
},
"size": {
"__type__": "cc.Size",
"width": 60,
"height": 60
},
"delay": 1,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "07sEXIwyZEKI/ruuFHnBcu"
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 21
},
"_alignFlags": 45,
"_target": null,
"_left": 0,
"_right": 0,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 100,
"_originalHeight": 100,
"_alignMode": 2,
"_lockFlags": 0,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "1en2H6LWVAnbHn1Id9zrSK"
},
{
"__type__": "cc.BlockInputEvents",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 23
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "23+7We+IdLxIZlOVikvEPE"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "daRB56CWxIRa37BwtucqYI",
"instance": null,
"targetOverrides": null
}
]

View File

@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "fe542035-b018-493e-bea8-084fe4e01905",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "UIMgrLoading"
}
}

View File

@@ -0,0 +1,262 @@
[
{
"__type__": "cc.Prefab",
"_name": "UIMgrShade",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"data": {
"__id__": 1
},
"optimizationPolicy": 0,
"persistent": false
},
{
"__type__": "cc.Node",
"_name": "UIMgrShade",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": null,
"_children": [],
"_active": true,
"_components": [
{
"__id__": 2
},
{
"__id__": 4
},
{
"__id__": 6
},
{
"__id__": 8
},
{
"__id__": 10
},
{
"__id__": 12
}
],
"_prefab": {
"__id__": 14
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 33554432,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 3
},
"_contentSize": {
"__type__": "cc.Size",
"width": 750,
"height": 1334
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "7dX6ImdfBJcqlTYmg63cOq"
},
{
"__type__": "cc.UIOpacity",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 5
},
"_opacity": 255,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "79YFX8QOxIqrfZHGmtwiec"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 7
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_spriteFrame": {
"__uuid__": "5512993f-89ea-46fe-b788-0ecc0c2cd51c@f9941",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 0,
"_fillType": 0,
"_sizeMode": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": null,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "22F/XyC25NNbnQRqf69Fet"
},
{
"__type__": "d02952bsB1JPJ4xXteObDOr",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 9
},
"blurMaterial": {
"__uuid__": "a313b5eb-b939-4c44-affc-32c713440cea",
"__expectedType__": "cc.Material"
},
"shadeFrame": {
"__uuid__": "5512993f-89ea-46fe-b788-0ecc0c2cd51c@f9941",
"__expectedType__": "cc.SpriteFrame"
},
"_delay": 0,
"_begin": 60,
"_end": 180,
"_speed": 100,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "cfGNW3HSFJZ5C0B0FM1PtW"
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 11
},
"_alignFlags": 45,
"_target": null,
"_left": 0,
"_right": 0,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 750,
"_originalHeight": 1334,
"_alignMode": 2,
"_lockFlags": 0,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "229/N15SBGH4HGma+cYqM5"
},
{
"__type__": "cc.BlockInputEvents",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 13
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "82DtXS4ChDkLThlpVA53IU"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "61DZmvnnRICYRzOyDnyCeu",
"instance": null,
"targetOverrides": null
}
]

View File

@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "000cee21-922c-4fcd-bd39-6f80ac2436a4",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "UIMgrShade"
}
}

View File

@@ -0,0 +1,93 @@
[
{
"__type__": "cc.Prefab",
"_name": "UIMgrToast",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"data": {
"__id__": 1
},
"optimizationPolicy": 0,
"persistent": false
},
{
"__type__": "cc.Node",
"_name": "UIMgrToast",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": null,
"_children": [],
"_active": true,
"_components": [
{
"__id__": 2
}
],
"_prefab": {
"__id__": 4
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 33554432,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cde15KM9mxA9L3SJ78TjOHf",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 3
},
"cell": {
"__uuid__": "5aa9450d-6710-4fac-a82c-ee2939cf9411",
"__expectedType__": "cc.Prefab"
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "3aBOvgZTJM7o51sRqaqstL"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "7cYDN4HdROmJQ7qqaDYftE",
"instance": null,
"targetOverrides": null
}
]

View File

@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "b2a00c44-d199-4031-8fa7-ea681618b9d4",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "UIMgrToast"
}
}

View File

@@ -0,0 +1,438 @@
[
{
"__type__": "cc.Prefab",
"_name": "UIMgrToastCell",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"data": {
"__id__": 1
},
"optimizationPolicy": 0,
"persistent": false
},
{
"__type__": "cc.Node",
"_name": "UIMgrToastCell",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": null,
"_children": [
{
"__id__": 2
}
],
"_active": true,
"_components": [
{
"__id__": 8
},
{
"__id__": 10
},
{
"__id__": 12
},
{
"__id__": 14
},
{
"__id__": 16
},
{
"__id__": 18
}
],
"_prefab": {
"__id__": 20
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 33554432,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.Node",
"_name": "title",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 3
},
{
"__id__": 5
}
],
"_prefab": {
"__id__": 7
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 3.552713678800501e-15,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 33554432,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 4
},
"_contentSize": {
"__type__": "cc.Size",
"width": 0,
"height": 50.4
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "18U2eEmv1MSrJ3Kp8ChYCB"
},
{
"__type__": "cc.Label",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": {
"__id__": 6
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_string": "",
"_horizontalAlign": 1,
"_verticalAlign": 1,
"_actualFontSize": 30,
"_fontSize": 30,
"_fontFamily": "Arial",
"_lineHeight": 40,
"_overflow": 0,
"_enableWrapText": true,
"_font": null,
"_isSystemFontUsed": true,
"_spacingX": 0,
"_isItalic": false,
"_isBold": false,
"_isUnderline": false,
"_underlineHeight": 2,
"_cacheMode": 0,
"_enableOutline": false,
"_outlineColor": {
"__type__": "cc.Color",
"r": 0,
"g": 0,
"b": 0,
"a": 255
},
"_outlineWidth": 2,
"_enableShadow": false,
"_shadowColor": {
"__type__": "cc.Color",
"r": 0,
"g": 0,
"b": 0,
"a": 255
},
"_shadowOffset": {
"__type__": "cc.Vec2",
"x": 2,
"y": 2
},
"_shadowBlur": 2,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "fcBV+kyJJKoLa0miLOwB0D"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "17FiiUdlpA7INUb+CSPtXV",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 9
},
"_contentSize": {
"__type__": "cc.Size",
"width": 80,
"height": 70.4
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "7bhW+VfdhFIZBU1YlNH87w"
},
{
"__type__": "cc.UIOpacity",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 11
},
"_opacity": 255,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "85IfdKUvlDVobXX10lMYgh"
},
{
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 13
},
"_customMaterial": null,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": {
"__type__": "cc.Color",
"r": 255,
"g": 255,
"b": 255,
"a": 255
},
"_spriteFrame": {
"__uuid__": "7922bee9-7eb5-449b-884e-14ac57ae515c@f9941",
"__expectedType__": "cc.SpriteFrame"
},
"_type": 1,
"_fillType": 0,
"_sizeMode": 0,
"_fillCenter": {
"__type__": "cc.Vec2",
"x": 0,
"y": 0
},
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": true,
"_useGrayscale": false,
"_atlas": null,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "d5KZg62HRMhqP9FpXXBvrN"
},
{
"__type__": "cc.Layout",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 15
},
"_resizeMode": 1,
"_layoutType": 1,
"_cellSize": {
"__type__": "cc.Size",
"width": 40,
"height": 40
},
"_startAxis": 0,
"_paddingLeft": 40,
"_paddingRight": 40,
"_paddingTop": 10,
"_paddingBottom": 10,
"_spacingX": 0,
"_spacingY": 0,
"_verticalDirection": 1,
"_horizontalDirection": 0,
"_constraint": 0,
"_constraintNum": 1,
"_affectedByScale": false,
"_isAlign": false,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "e0RO5Tg+lAWq8B14qLuRXc"
},
{
"__type__": "cc.Layout",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 17
},
"_resizeMode": 1,
"_layoutType": 2,
"_cellSize": {
"__type__": "cc.Size",
"width": 40,
"height": 40
},
"_startAxis": 0,
"_paddingLeft": 0,
"_paddingRight": 0,
"_paddingTop": 10,
"_paddingBottom": 10,
"_spacingX": 0,
"_spacingY": 0,
"_verticalDirection": 1,
"_horizontalDirection": 0,
"_constraint": 0,
"_constraintNum": 2,
"_affectedByScale": false,
"_isAlign": false,
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "b4w/hORDRDYLZbEx1j4ybh"
},
{
"__type__": "6cc63HWsI5O44ve4wfEKIc0",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 19
},
"title": {
"__id__": 5
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "a82bayEVpB1YYG3zm38T3l"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "c46/YsCPVOJYA4mWEpNYRx",
"instance": null,
"targetOverrides": null
}
]

View File

@@ -0,0 +1,13 @@
{
"ver": "1.1.50",
"importer": "prefab",
"imported": true,
"uuid": "5aa9450d-6710-4fac-a82c-ee2939cf9411",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "UIMgrToastCell"
}
}

View File

@@ -0,0 +1,12 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "f5f10a35-ca33-4eb2-81f1-52459832001b",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 B

View File

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "5512993f-89ea-46fe-b788-0ecc0c2cd51c",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "5512993f-89ea-46fe-b788-0ecc0c2cd51c@6c48a",
"displayName": "singleColor",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "5512993f-89ea-46fe-b788-0ecc0c2cd51c",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "5512993f-89ea-46fe-b788-0ecc0c2cd51c@f9941",
"displayName": "singleColor",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimType": "auto",
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": 0,
"trimX": 0,
"trimY": 0,
"width": 2,
"height": 2,
"rawWidth": 2,
"rawHeight": 2,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-1,
-1,
0,
1,
-1,
0,
-1,
1,
0,
1,
1,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
0,
2,
2,
2,
0,
0,
2,
0
],
"nuv": [
0,
0,
1,
0,
0,
1,
1,
1
],
"minPos": [
-1,
-1,
0
],
"maxPos": [
1,
1,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "5512993f-89ea-46fe-b788-0ecc0c2cd51c@6c48a",
"atlasUuid": ""
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": true,
"hasAlpha": false,
"redirect": "5512993f-89ea-46fe-b788-0ecc0c2cd51c@6c48a"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "b250e49c-5d79-4d07-a85f-a980fca36170",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "b250e49c-5d79-4d07-a85f-a980fca36170@6c48a",
"displayName": "toastBox",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "b250e49c-5d79-4d07-a85f-a980fca36170",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "b250e49c-5d79-4d07-a85f-a980fca36170@f9941",
"displayName": "toastBox",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimType": "auto",
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": 0,
"trimX": 0,
"trimY": 0,
"width": 288,
"height": 78,
"rawWidth": 288,
"rawHeight": 78,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 40,
"borderRight": 40,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-144,
-39,
0,
144,
-39,
0,
-144,
39,
0,
144,
39,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
0,
78,
288,
78,
0,
0,
288,
0
],
"nuv": [
0,
0,
1,
0,
0,
1,
1,
1
],
"minPos": [
-144,
-39,
0
],
"maxPos": [
144,
39,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "b250e49c-5d79-4d07-a85f-a980fca36170@6c48a",
"atlasUuid": ""
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": true,
"hasAlpha": true,
"redirect": "b250e49c-5d79-4d07-a85f-a980fca36170@6c48a"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 B

View File

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "7922bee9-7eb5-449b-884e-14ac57ae515c",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "7922bee9-7eb5-449b-884e-14ac57ae515c@6c48a",
"displayName": "toastCell",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "7922bee9-7eb5-449b-884e-14ac57ae515c",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "7922bee9-7eb5-449b-884e-14ac57ae515c@f9941",
"displayName": "toastCell",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimType": "auto",
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": 0,
"trimX": 0,
"trimY": 0,
"width": 100,
"height": 100,
"rawWidth": 100,
"rawHeight": 100,
"borderTop": 15,
"borderBottom": 15,
"borderLeft": 15,
"borderRight": 15,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-50,
-50,
0,
50,
-50,
0,
-50,
50,
0,
50,
50,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
0,
100,
100,
100,
0,
0,
100,
0
],
"nuv": [
0,
0,
1,
0,
0,
1,
1,
1
],
"minPos": [
-50,
-50,
0
],
"maxPos": [
50,
50,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "7922bee9-7eb5-449b-884e-14ac57ae515c@6c48a",
"atlasUuid": ""
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"hasAlpha": true,
"fixAlphaTransparencyArtifacts": false,
"redirect": "7922bee9-7eb5-449b-884e-14ac57ae515c@6c48a"
}
}