diff --git a/assets/core/common/event/EventDispatcher.ts b/assets/core/common/event/EventDispatcher.ts index 68d6063..5bf33da 100644 --- a/assets/core/common/event/EventDispatcher.ts +++ b/assets/core/common/event/EventDispatcher.ts @@ -45,7 +45,7 @@ export class EventDispatcher { * @param event 事件名(枚举) * @param data 事件数据(必须完全匹配类型定义) */ - emit(event: K, data: OopsFramework.TypedEventMap[K]): void { + emit(event: K, data?: OopsFramework.TypedEventMap[K]): void { message.emit(event, data); } diff --git a/assets/core/common/event/MessageManager.ts b/assets/core/common/event/MessageManager.ts index 742dbd6..591027d 100644 --- a/assets/core/common/event/MessageManager.ts +++ b/assets/core/common/event/MessageManager.ts @@ -63,7 +63,7 @@ export class MessageManager { * @param data 事件数据(必须完全匹配类型定义) * @note 使用此方法可获得编译时的强类型约束,参数不匹配会编译报错 */ - emit(event: K, data: OopsFramework.TypedEventMap[K]): void { + emit(event: K, data?: OopsFramework.TypedEventMap[K]): void { const list = this.events.get(event as string); if (list != null) { const eds: Array = list.concat(); diff --git a/assets/core/gui/layer/UIConfig.ts b/assets/core/gui/layer/UIConfig.ts index 4a7b239..c350ed9 100644 --- a/assets/core/gui/layer/UIConfig.ts +++ b/assets/core/gui/layer/UIConfig.ts @@ -1,4 +1,5 @@ import type { Node, Vec3 } from 'cc'; +import { LayerCustomType, LayerType } from './LayerEnum'; /** * 界面配置结构体 @@ -28,7 +29,7 @@ export interface UIConfig { /** 远程包名 */ bundle?: string; /** 窗口层级 */ - layer: string; + layer: LayerType | LayerCustomType; /** 预制资源相对路径 */ prefab: string; /** 是否自动施放(默认自动释放) */ diff --git a/assets/core/utils/ImageUtil.ts b/assets/core/utils/ImageUtil.ts index 2aa47fe..10474ca 100644 --- a/assets/core/utils/ImageUtil.ts +++ b/assets/core/utils/ImageUtil.ts @@ -4,7 +4,7 @@ * @LastEditors: dgflash * @LastEditTime: 2022-09-02 14:49:42 */ -import { Color, Texture2D } from 'cc'; +import { assetManager, Color, ImageAsset, sys, Texture2D } from 'cc'; /** * 图像工具 @@ -21,6 +21,15 @@ const color = ImageUtil.getPixelColor(texture, 1, 1); cc.color(50, 100, 123, 255); */ static getPixelColor(texture: Texture2D, x: number, y: number): Color { + if (sys.isBrowser) { + return ImageUtil.getPixelColorForWeb(texture, x, y); + } + else { + return ImageUtil.getPixelColorForNative(texture, x, y); + } + } + + private static getPixelColorForWeb(texture: Texture2D, x: number, y: number): Color { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d')!; canvas.width = texture.width; @@ -36,19 +45,43 @@ cc.color(50, 100, 123, 255); return color; } + private static getPixelColorForNative(texture: Texture2D, x: number, y: number): Color { + const height = texture.height; + const pixels = texture.getPixel(x - 1, height - y); + if (pixels) { + return new Color(pixels.r, pixels.g, pixels.b, pixels.a); + } + return new Color(0, 0, 0, 0); + } + /** - * 将图像转为 Base64 字符(仅 png、jpg 或 jpeg 格式资源)(有问题) + * 将图像转为 Base64 字符(仅 png、jpg 或 jpeg 格式资源) * @param url 图像地址 * @param callback 完成回调 */ static imageToBase64(url: string, callback?: (dataURL: string) => void): Promise { + return new Promise((res) => { + if (sys.isBrowser) { + ImageUtil.imageToBase64ForWeb(url, callback).then((dataURL) => { + res(dataURL); + }); + } + else { + ImageUtil.imageToBase64ForNative(url, callback).then((dataURL) => { + res(dataURL); + }); + } + }); + } + + private static imageToBase64ForWeb(url: string, callback?: (dataURL: string) => void): Promise { return new Promise((res) => { let extname = /\.png|\.jpg|\.jpeg/.exec(url)?.[0]; - //@ts-ignore - if (['.png', '.jpg', '.jpeg'].includes(extname)) { + if (extname && ['.png', '.jpg', '.jpeg'].includes(extname)) { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d')!; const image = new Image(); + image.crossOrigin = 'anonymous'; image.src = url; image.onload = () => { canvas.height = image.height; @@ -61,6 +94,11 @@ cc.color(50, 100, 123, 255); image.remove(); canvas.remove(); }; + image.onerror = () => { + console.warn('Failed to load image:', url); + callback && callback(''); + res(''); + }; } else { console.warn('Not a jpg/jpeg or png resource!'); @@ -70,34 +108,215 @@ cc.color(50, 100, 123, 255); }); } + private static imageToBase64ForNative(url: string, callback?: (dataURL: string) => void): Promise { + return new Promise((res) => { + const extname = /\.png|\.jpg|\.jpeg/.exec(url)?.[0]; + if (!extname || !['.png', '.jpg', '.jpeg'].includes(extname)) { + console.warn('Not a jpg/jpeg or png resource!'); + callback && callback(''); + res(''); + return; + } + assetManager.loadRemote(url, (err, imageAsset) => { + if (err || !imageAsset) { + console.warn('Failed to load image for base64:', url, err); + callback && callback(''); + res(''); + return; + } + const data = imageAsset.data as Uint8Array; + const base64 = ImageUtil.uint8ArrayToBase64(data); + const mimeType = extname === '.jpg' ? 'image/jpeg' : `image/${extname.replace('.', '')}`; + const dataURL = `data:${mimeType};base64,${base64}`; + callback && callback(dataURL); + res(dataURL); + }); + }); + } + /** - * 将 Base64 字符转为 cc.Texture2D 资源(有问题) + * 将 Base64 字符转为 cc.Texture2D 资源(异步) * @param base64 Base64 字符 */ - static base64ToTexture(base64: string): Texture2D { + static base64ToTextureAsync(base64: string): Promise { + return new Promise((resolve) => { + if (sys.isBrowser) { + ImageUtil.base64ToTextureForWebAsync(base64).then((texture) => { + resolve(texture); + }); + } + else { + const texture = ImageUtil.base64ToTextureForNative(base64); + resolve(texture); + } + }); + } + + /** + * 将 Base64 字符转为 cc.Texture2D 资源(同步,仅 Web 平台推荐使用) + * @param base64 Base64 字符 + * @deprecated 建议使用 base64ToTextureAsync 异步方法 + */ + static base64ToTexture(base64: string): Texture2D | null { + if (sys.isBrowser) { + return ImageUtil.base64ToTextureForWeb(base64); + } + else { + return ImageUtil.base64ToTextureForNative(base64); + } + } + + private static base64ToTextureForWeb(base64: string): Texture2D | null { const image = document.createElement('img'); image.src = base64; + if (image.width === 0 || image.height === 0) { + console.warn('Image not loaded yet, please use base64ToTextureAsync instead'); + return null; + } + const imageAsset = new ImageAsset(image); const texture = new Texture2D(); - //@ts-ignore - texture.initWithElement(image); + texture.image = imageAsset; image.remove(); return texture; } + private static base64ToTextureForWebAsync(base64: string): Promise { + return new Promise((resolve) => { + const image = new Image(); + image.onload = () => { + const imageAsset = new ImageAsset(image); + const texture = new Texture2D(); + texture.image = imageAsset; + resolve(texture); + }; + image.onerror = () => { + console.warn('Failed to load base64 image'); + resolve(null); + }; + image.src = base64; + }); + } + + private static base64ToTextureForNative(base64: string): Texture2D | null { + try { + const base64Data = base64.includes(',') ? base64.split(',')[1] : base64; + if (!base64Data) { + console.warn('Invalid base64 string'); + return null; + } + const bytes = ImageUtil.base64ToUint8Array(base64Data); + const imageAsset = new ImageAsset(); + imageAsset.reset({ + _data: bytes, + width: 0, + height: 0, + format: Texture2D.PixelFormat.RGBA8888, + _compressed: false + }); + const texture = new Texture2D(); + texture.image = imageAsset; + return texture; + } + catch (e) { + console.warn('Failed to convert base64 to texture:', e); + return null; + } + } + /** - * 将 Base64 字符转为二进制数据(有问题) + * 将 Base64 字符转为二进制数据 * @param base64 Base64 字符 */ - static base64ToBlob(base64: string): Blob { - const strings = base64.split(','); - //@ts-ignore - const type = /image\/\w+|;/.exec(strings[0])[0]; - const data = window.atob(strings[1]); - const arrayBuffer = new ArrayBuffer(data.length); - const uint8Array = new Uint8Array(arrayBuffer); - for (let i = 0; i < data.length; i++) { - uint8Array[i] = data.charCodeAt(i) & 0xff; + static base64ToBlob(base64: string): Blob | null { + try { + const strings = base64.split(','); + const type = /image\/\w+/.exec(strings[0])?.[0] || 'image/png'; + const base64Data = strings[1] || base64; + const bytes = ImageUtil.base64ToUint8Array(base64Data); + return new Blob([bytes], { type: type }); } - return new Blob([uint8Array], { type: type }); + catch (e) { + console.warn('Failed to convert base64 to blob:', e); + return null; + } + } + + private static base64ToUint8Array(base64: string): Uint8Array { + const binaryString = ImageUtil.atob(base64); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes; + } + + private static atob(base64: string): string { + if (typeof window !== 'undefined' && window.atob) { + return window.atob(base64); + } + return ImageUtil.atobPolyfill(base64); + } + + private static atobPolyfill(base64: string): string { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + let output = ''; + let i = 0; + base64 = base64.replace(/[^A-Za-z0-9\+\/\=]/g, ''); + while (i < base64.length) { + const enc1 = chars.indexOf(base64.charAt(i++)); + const enc2 = chars.indexOf(base64.charAt(i++)); + const enc3 = chars.indexOf(base64.charAt(i++)); + const enc4 = chars.indexOf(base64.charAt(i++)); + const chr1 = (enc1 << 2) | (enc2 >> 4); + const chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + const chr3 = ((enc3 & 3) << 6) | enc4; + output += String.fromCharCode(chr1); + if (enc3 !== 64) { + output += String.fromCharCode(chr2); + } + if (enc4 !== 64) { + output += String.fromCharCode(chr3); + } + } + return output; + } + + private static uint8ArrayToBase64(uint8Array: Uint8Array): string { + let binary = ''; + const len = uint8Array.byteLength; + for (let i = 0; i < len; i++) { + binary += String.fromCharCode(uint8Array[i]); + } + return ImageUtil.btoa(binary); + } + + private static btoa(binary: string): string { + if (typeof window !== 'undefined' && window.btoa) { + return window.btoa(binary); + } + return ImageUtil.btoaPolyfill(binary); + } + + private static btoaPolyfill(binary: string): string { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + let output = ''; + let i = 0; + while (i < binary.length) { + const byte1 = binary.charCodeAt(i++); + const byte2 = binary.charCodeAt(i++); + const byte3 = binary.charCodeAt(i++); + const enc1 = byte1 >> 2; + const enc2 = ((byte1 & 3) << 4) | (byte2 >> 4); + let enc3 = ((byte2 & 15) << 2) | (byte3 >> 6); + let enc4 = byte3 & 63; + if (isNaN(byte2)) { + enc3 = enc4 = 64; + } + else if (isNaN(byte3)) { + enc4 = 64; + } + output += chars.charAt(enc1) + chars.charAt(enc2) + chars.charAt(enc3) + chars.charAt(enc4); + } + return output; } } diff --git a/assets/module/common/CCBusiness.ts b/assets/module/common/CCBusiness.ts index 6cf1dfc..9652e11 100644 --- a/assets/module/common/CCBusiness.ts +++ b/assets/module/common/CCBusiness.ts @@ -113,7 +113,7 @@ export class CCBusiness { * @param event 事件名(枚举) * @param data 事件数据 */ - emit(event: K, data: OopsFramework.TypedEventMap[K]): void { + emit(event: K, data?: OopsFramework.TypedEventMap[K]): void { if (this._destroyed) { console.warn('[OopsFramework]', '尝试在已销毁的业务逻辑上触发事件'); return; diff --git a/assets/module/common/GameComponent.ts b/assets/module/common/GameComponent.ts index 448f251..ad67779 100644 --- a/assets/module/common/GameComponent.ts +++ b/assets/module/common/GameComponent.ts @@ -93,7 +93,7 @@ export class GameComponent extends Component { * @param event 事件名(枚举) * @param data 事件数据 */ - emit(event: K, data: OopsFramework.TypedEventMap[K]): void { + emit(event: K, data?: OopsFramework.TypedEventMap[K]): void { this.event.emit(event, data); }