import { _decorator, Asset, Component, Constructor, director, Node, ParticleSystem, Prefab, SceneAsset, setPropertyEnumType, SkeletalAnimation, Sprite, SpriteFrame, tween, UIOpacity, UITransform, v2, v3, Vec3 } from 'cc'; import { Utils } from './Utils'; import { Constants } from '../data/Constants'; import { bundleMgr } from '../core/manager/BundleManager'; import { Logger } from '../extend/Logger'; import { PoolManager } from '../core/manager/PoolManager'; import { uiMgr } from '../core/manager/UIManager'; const { ccclass, property } = _decorator; /** * 游戏道具类型枚举 * @description 道具类型标识 */ export enum ITEM_TYPE { Diamond = "diamond",//钻石 Coin = "coin",//金币 Boomerang = "boomerang",//飞镖 } /** * 游戏特效类型枚举 * @description 所有特效资源分类标识 */ export enum EffectType { Bleeding = "effects/Prefabs/blood", //流血效果(子弹命中敌人) BarrelExplosion = "effects/Prefabs/OilBoom", //油桶爆炸(基础爆炸) RockDebris = "effects/Prefabs/StoneBoom",// 沙堆/石礅碎裂烟雾 VehicleSmoke = "effects/Prefabs/HeavySmoke", // 坦克/汽车受损烟雾(浓烟) VehicleExplosion = "effects/Prefabs/TankBoom" // 坦克/汽车爆炸(强烈爆炸) } @ccclass('ResUtil') export class ResUtil { /** * 设置精灵贴图 只能prefabs包 texture目录下 * @param path 资源路径 * 只能是在prefabs这个bundle目录下、然后如果使用 * @param target 精灵或者节点 * @param cb 回调函数 */ public static setSpriteFrame(name: string, target: Sprite | Node) { if(Utils.isNull(name))return; const path:string = `texture/${name}/spriteFrame`; bundleMgr.loadAsset(Constants.bundleName.prefabs,path,SpriteFrame, (err: Error, clip: SpriteFrame) => { if(!err) { let sprite: Sprite = target instanceof Node ? target.getComponent(Sprite) : target; if (!sprite) { console.error('Target node does not have a Sprite component!'); }else{ sprite.spriteFrame = clip; } } }); } /** * 设置精灵贴图 指定自定义的包 * @param bundle 包名如果传空 默认prefabs包名 * @param path 路径 如果在prefabs包下,就可以直接传递传图片名称或者path路径(hit_m or texture/hit_m/spriteFrame) * 如果不在prefabs包下就只能传递完成的路径(texture/hit_m/spriteFrame) * @param type 类型 * @param target 图片或者节点 * @param onComplete 设置完成的回调 */ public static setSpriteFrameAsset( bundle: string, path: string, type: Constructor, target: Sprite | Node, onComplete?: (err: Error | null, asset?: any) => void){ if(Utils.isNull(bundle)){ bundle = Constants.bundleName.prefabs; if(!path.startsWith("texture/")){ path = `texture/${path}/spriteFrame`; } } if(Utils.isNull(path))return; bundleMgr.loadAsset(bundle,path,type, (err: Error, clip: SpriteFrame) => { if(!err) { let sprite: Sprite = target instanceof Node ? target.getComponent(Sprite) : target; if (!sprite) { console.error('Target node does not have a Sprite component!'); }else{ sprite.spriteFrame = clip; } } onComplete?.(err,clip); }); } /** * 预加载场景资源(支持进度回调) * @param {string} sceneName - 场景名称(如 "main" 或者"level1") * @param {Function} [onComplete] - 完成回调 * @param {Function} [onProgress] - 加载进度回调 * @param {Error | null} onProgress.err - 错误信息(成功时为 null) * @param {number} onProgress.progress - 加载进度(0~1) */ public static preloadScene( sceneName: string, onComplete?: () => void, onProgress?: (err: Error | null, progress: number) => void ): void { if (Utils.isNull(sceneName)) { Logger.warn("[SceneLoader] Scene name cannot be null or empty!"); return; }//开始预加载场景 director.preloadScene( sceneName, (loadedCount: number, totalCount: number) => {//进度更新回调 const progress = totalCount > 0 ? loadedCount / totalCount : 0; onProgress?.(null, progress); // 通知进度(未完成) },//加载完成回调 (error: Error | null, sceneAsset?: SceneAsset) => { if (error) { Logger.error(`[SceneLoader] Failed to preload scene "${sceneName}":`, error); onProgress?.(error,0); // 通知失败 return; } onComplete?.(); //预加载成功 Logger.log(`[SceneLoader] Scene "${sceneName}" preloaded successfully`); onProgress?.(null, 1); } ); } /** * 加载敌人的预制体 * @param name 敌人的名字 * @param parent 父节点 * @returns 返回一个敌人 */ public static loadEnemyRes(name: string, parent?: Node): Promise { return new Promise(async (resolve, reject) => { try { if(Utils.isNull(name)) { resolve(null); return; } //加载资源 const path = `enemy/${name}`; const res = await bundleMgr.loadAsset(Constants.bundleName.prefabs, path, Prefab) as Prefab; //从对象池获取实例 const enemy = PoolManager.getNode(res) as Node; //设置父节点 if (parent) { enemy.parent = parent; } //返回结果 resolve(enemy); } catch (error) { Logger.error(`[loadEnemyRes] 加载敌人资源失败: ${name}`, error); reject(new Error(`加载敌人资源失败: ${name}`)); } }); } /** * 加载枪的预制体 * @param name 枪的名称 * @param parent 父节点 * @returns 返回一把枪 */ public static async loadGunRes(path: string,parent?: Node){ return new Promise(async (resolve, reject) => { try { if(Utils.isNull(path)) { resolve(null); return; } const res: Prefab = await bundleMgr.loadAsset(Constants.bundleName.prefabs, path, Prefab) as Prefab; const gun: Node = PoolManager.getNode(res) as Node; if (parent) { gun.parent = parent; } resolve(gun); } catch (error) { Logger.error(`[loadGunRes] 加载枪资源失败: ${name}`, error); reject(error); } }); } /** * 只加载prefabs资源ui和页面 预制体 * @param name 名字 只能是预知体 * @param parent 父节点 * @returns 返回一个节点 */ public static loadRes(path: string, parent?: Node): Promise { return new Promise(async (resolve, reject) => { try { if (Utils.isNull(path)) { resolve(null); return; } //加载资源 从对象池获取实例 const res = await bundleMgr.loadAsset(Constants.bundleName.prefabs, path, Prefab) as Prefab; const n = PoolManager.getNode(res) as Node; //设置父节点 if (parent) { n.parent = parent; } //返回结果 resolve(n); } catch (error) { console.error(`加载资源失败: ${path}`, error); reject(new Error(`加载资源失败:: ${path}`)); } }); } /** * 购买金币和钻石的动画 * @param type * @param starNode * @param targetNode * @param count * @param radius * @param callback */ public static async flyAnim(type: ITEM_TYPE, starNode: Node, targetNode: Node, count: number, radius: number, callback: Function) { let getPoint = (r, ox, oy, count) => { var point = []; //结果 var radians = (Math.PI / 180) * Math.round(360 / count), //弧度 i = 0; for (; i < count; i++) { var x = ox + r * Math.sin(radians * i), y = oy + r * Math.cos(radians * i); point.unshift(v2(x, y)); //为保持数据顺时针 } return point; } let start = starNode.worldPosition; var array = getPoint(radius, start.x, start.y, count); var nodeArray = new Array(); for (var i = 0; i < array.length; i++) { let ret: any = await bundleMgr.loadAsset(Constants.bundleName.prefabs, `items/${type}`, Prefab); var gold = PoolManager.getNode(ret); gold.setWorldScale(v3(0.7, 0.7, 0.7)); gold.parent = uiMgr.getCurentSceneRoot(); var randPos = v3(array[i].x + Utils.getRandomInt(0, 50), array[i].y + Utils.getRandomInt(0, 50), 1); gold.setWorldPosition(start); nodeArray.push({ gold, randPos }); } var notPlay = false; let dstPos = targetNode.worldPosition.clone(); for (let i = 0; i < nodeArray.length; i++) { let pos = nodeArray[i].randPos; let node = nodeArray[i].gold; nodeArray[i].gold.id = i; tween(node) .to(0.2, { worldPosition: pos }) .delay(i * 0.03) .to(0.7, { worldPosition: v3(dstPos.x, dstPos.y, 1) }) .call(() => { if(!notPlay) { notPlay = true; tween(targetNode) .to(0.1, { scale: v3(2, 2, 2) }) .to(0.1, { scale: v3(1, 1, 1) }) .call(() => { notPlay = false; }).start() } let idx: number = node["_id"]; callback(idx == nodeArray.length - 1); PoolManager.putNode(node); }) .start() } } /** * 加载特效 * @param type 路径 * @param worldPos 位置 * @param parent 父节点 * @param callback 回调函数 */ public static async loadEffect(type: EffectType,worldPos:Vec3,removeTime: number = 0, parent?: Node, callback?: Function){ let res: Node = await ResUtil.loadRes(type,parent); if(worldPos){ res.worldPosition = worldPos; } if(parent){ res.getComponentsInChildren(ParticleSystem) .forEach(e => { e.play(); }); } if(removeTime > 0){ res.getComponent(UIOpacity).scheduleOnce(function(){ this.getComponentsInChildren(ParticleSystem) .forEach(e => { e.stop(); }); PoolManager.putNode(this); }.bind(res),removeTime,); } } /** * 播放骨骼动画 * @param skeletal 骨骼动画 * @param name 动画名称 * @param time 播放时间 播放的时长会控制播放的速度快慢 * @param cb 播放完成回调 */ public static playSkeletalAnim(skeletal: SkeletalAnimation,name: string,time: number = 0, cb: Function = null){ if(!skeletal)return; //查找目标动画剪辑 const clip = skeletal.clips.find(c => c.name == name); if (!clip) { Logger.error(`[ResUtil.playSkeletalAnim] 未找到名为 "${name}" 的动画剪辑`); return; } //核心逻辑:通过调整播放速度,使动画总时长等于传入的time //公式推导:原始时长 = 播放速度 × 实际播放时间 → 播放速度 = 原始时长 / 目标时长 if(time > 0) { clip.speed = clip.duration / time; }else{//恢复默认速度(避免之前的修改影响后续播放) clip.speed = 1; } skeletal.play(name); if(cb){ skeletal.scheduleOnce(cb,time); } } }