ResUtil.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. import { _decorator, Asset, Component, Constructor, director, Node, ParticleSystem, Prefab, SceneAsset, setPropertyEnumType, SkeletalAnimation, Sprite, SpriteFrame, tween, UIOpacity, UITransform, v2, v3, Vec3 } from 'cc';
  2. import { Utils } from './Utils';
  3. import { Constants } from '../data/Constants';
  4. import { bundleMgr } from '../core/manager/BundleManager';
  5. import { Logger } from '../extend/Logger';
  6. import { PoolManager } from '../core/manager/PoolManager';
  7. import { uiMgr } from '../core/manager/UIManager';
  8. const { ccclass, property } = _decorator;
  9. /**
  10. * 游戏道具类型枚举
  11. * @description 道具类型标识
  12. */
  13. export enum ITEM_TYPE {
  14. Diamond = "diamond",//钻石
  15. Coin = "coin",//金币
  16. Boomerang = "boomerang",//飞镖
  17. }
  18. /**
  19. * 游戏特效类型枚举
  20. * @description 所有特效资源分类标识
  21. */
  22. export enum EffectType {
  23. Bleeding = "effects/Prefabs/blood", //流血效果(子弹命中敌人)
  24. BarrelExplosion = "effects/Prefabs/OilBoom", //油桶爆炸(基础爆炸)
  25. RockDebris = "effects/Prefabs/StoneBoom",// 沙堆/石礅碎裂烟雾
  26. VehicleSmoke = "effects/Prefabs/HeavySmoke", // 坦克/汽车受损烟雾(浓烟)
  27. VehicleExplosion = "effects/Prefabs/TankBoom" // 坦克/汽车爆炸(强烈爆炸)
  28. }
  29. @ccclass('ResUtil')
  30. export class ResUtil {
  31. /**
  32. * 设置精灵贴图 只能prefabs包 texture目录下
  33. * @param path 资源路径
  34. * 只能是在prefabs这个bundle目录下、然后如果使用
  35. * @param target 精灵或者节点
  36. * @param cb 回调函数
  37. */
  38. public static setSpriteFrame(name: string, target: Sprite | Node) {
  39. if(Utils.isNull(name))return;
  40. const path:string = `texture/${name}/spriteFrame`;
  41. bundleMgr.loadAsset(Constants.bundleName.prefabs,path,SpriteFrame, (err: Error, clip: SpriteFrame) => {
  42. if(!err) {
  43. let sprite: Sprite = target instanceof Node ? target.getComponent(Sprite) : target;
  44. if (!sprite) {
  45. console.error('Target node does not have a Sprite component!');
  46. }else{
  47. sprite.spriteFrame = clip;
  48. }
  49. }
  50. });
  51. }
  52. /**
  53. * 设置精灵贴图 指定自定义的包
  54. * @param bundle 包名如果传空 默认prefabs包名
  55. * @param path 路径 如果在prefabs包下,就可以直接传递传图片名称或者path路径(hit_m or texture/hit_m/spriteFrame)
  56. * 如果不在prefabs包下就只能传递完成的路径(texture/hit_m/spriteFrame)
  57. * @param type 类型
  58. * @param target 图片或者节点
  59. * @param onComplete 设置完成的回调
  60. */
  61. public static setSpriteFrameAsset(
  62. bundle: string,
  63. path: string,
  64. type: Constructor<Asset>,
  65. target: Sprite | Node,
  66. onComplete?: (err: Error | null, asset?: any) => void){
  67. if(Utils.isNull(bundle)){
  68. bundle = Constants.bundleName.prefabs;
  69. if(!path.startsWith("texture/")){
  70. path = `texture/${path}/spriteFrame`;
  71. }
  72. }
  73. if(Utils.isNull(path))return;
  74. bundleMgr.loadAsset(bundle,path,type, (err: Error, clip: SpriteFrame) => {
  75. if(!err) {
  76. let sprite: Sprite = target instanceof Node ? target.getComponent(Sprite) : target;
  77. if (!sprite) {
  78. console.error('Target node does not have a Sprite component!');
  79. }else{
  80. sprite.spriteFrame = clip;
  81. }
  82. }
  83. onComplete?.(err,clip);
  84. });
  85. }
  86. /**
  87. * 预加载场景资源(支持进度回调)
  88. * @param {string} sceneName - 场景名称(如 "main" 或者"level1")
  89. * @param {Function} [onComplete] - 完成回调
  90. * @param {Function} [onProgress] - 加载进度回调
  91. * @param {Error | null} onProgress.err - 错误信息(成功时为 null)
  92. * @param {number} onProgress.progress - 加载进度(0~1)
  93. */
  94. public static preloadScene(
  95. sceneName: string,
  96. onComplete?: () => void,
  97. onProgress?: (err: Error | null, progress: number) => void
  98. ): void {
  99. if (Utils.isNull(sceneName)) {
  100. Logger.warn("[SceneLoader] Scene name cannot be null or empty!");
  101. return;
  102. }//开始预加载场景
  103. director.preloadScene(
  104. sceneName,
  105. (loadedCount: number, totalCount: number) => {//进度更新回调
  106. const progress = totalCount > 0 ? loadedCount / totalCount : 0;
  107. onProgress?.(null, progress); // 通知进度(未完成)
  108. },//加载完成回调
  109. (error: Error | null, sceneAsset?: SceneAsset) => {
  110. if (error) {
  111. Logger.error(`[SceneLoader] Failed to preload scene "${sceneName}":`, error);
  112. onProgress?.(error,0); // 通知失败
  113. return;
  114. }
  115. onComplete?.();
  116. //预加载成功
  117. Logger.log(`[SceneLoader] Scene "${sceneName}" preloaded successfully`);
  118. onProgress?.(null, 1);
  119. }
  120. );
  121. }
  122. /**
  123. * 加载敌人的预制体
  124. * @param name 敌人的名字
  125. * @param parent 父节点
  126. * @returns 返回一个敌人
  127. */
  128. public static loadEnemyRes(name: string, parent?: Node): Promise<Node | null> {
  129. return new Promise(async (resolve, reject) => {
  130. try {
  131. if(Utils.isNull(name)) {
  132. resolve(null);
  133. return;
  134. }
  135. //加载资源
  136. const path = `enemy/${name}`;
  137. const res = await bundleMgr.loadAsset(Constants.bundleName.prefabs, path, Prefab) as Prefab;
  138. //从对象池获取实例
  139. const enemy = PoolManager.getNode(res) as Node;
  140. //设置父节点
  141. if (parent) {
  142. enemy.parent = parent;
  143. }
  144. //返回结果
  145. resolve(enemy);
  146. } catch (error) {
  147. Logger.error(`[loadEnemyRes] 加载敌人资源失败: ${name}`, error);
  148. reject(new Error(`加载敌人资源失败: ${name}`));
  149. }
  150. });
  151. }
  152. /**
  153. * 加载枪的预制体
  154. * @param name 枪的名称
  155. * @param parent 父节点
  156. * @returns 返回一把枪
  157. */
  158. public static async loadGunRes(path: string,parent?: Node){
  159. return new Promise(async (resolve, reject) => {
  160. try {
  161. if(Utils.isNull(path)) {
  162. resolve(null);
  163. return;
  164. }
  165. const res: Prefab = await bundleMgr.loadAsset(Constants.bundleName.prefabs, path, Prefab) as Prefab;
  166. const gun: Node = PoolManager.getNode(res) as Node;
  167. if (parent) {
  168. gun.parent = parent;
  169. }
  170. resolve(gun);
  171. } catch (error) {
  172. Logger.error(`[loadGunRes] 加载枪资源失败: ${name}`, error);
  173. reject(error);
  174. }
  175. });
  176. }
  177. /**
  178. * 只加载prefabs资源ui和页面 预制体
  179. * @param name 名字 只能是预知体
  180. * @param parent 父节点
  181. * @returns 返回一个节点
  182. */
  183. public static loadRes(path: string, parent?: Node): Promise<Node | null> {
  184. return new Promise(async (resolve, reject) => {
  185. try {
  186. if (Utils.isNull(path)) {
  187. resolve(null);
  188. return;
  189. }
  190. //加载资源 从对象池获取实例
  191. const res = await bundleMgr.loadAsset(Constants.bundleName.prefabs, path, Prefab) as Prefab;
  192. const n = PoolManager.getNode(res) as Node;
  193. //设置父节点
  194. if (parent) {
  195. n.parent = parent;
  196. }
  197. //返回结果
  198. resolve(n);
  199. } catch (error) {
  200. console.error(`加载资源失败: ${path}`, error);
  201. reject(new Error(`加载资源失败:: ${path}`));
  202. }
  203. });
  204. }
  205. /**
  206. * 购买金币和钻石的动画
  207. * @param type
  208. * @param starNode
  209. * @param targetNode
  210. * @param count
  211. * @param radius
  212. * @param callback
  213. */
  214. public static async flyAnim(type: ITEM_TYPE, starNode: Node, targetNode: Node, count: number, radius: number, callback: Function) {
  215. let getPoint = (r, ox, oy, count) => {
  216. var point = []; //结果
  217. var radians = (Math.PI / 180) * Math.round(360 / count), //弧度
  218. i = 0;
  219. for (; i < count; i++) {
  220. var x = ox + r * Math.sin(radians * i),
  221. y = oy + r * Math.cos(radians * i);
  222. point.unshift(v2(x, y)); //为保持数据顺时针
  223. }
  224. return point;
  225. }
  226. let start = starNode.worldPosition;
  227. var array = getPoint(radius, start.x, start.y, count);
  228. var nodeArray = new Array();
  229. for (var i = 0; i < array.length; i++) {
  230. let ret: any = await bundleMgr.loadAsset(Constants.bundleName.prefabs, `items/${type}`, Prefab);
  231. var gold = PoolManager.getNode(ret);
  232. gold.setWorldScale(v3(0.7, 0.7, 0.7));
  233. gold.parent = uiMgr.getCurentSceneRoot();
  234. var randPos = v3(array[i].x + Utils.getRandomInt(0, 50), array[i].y + Utils.getRandomInt(0, 50), 1);
  235. gold.setWorldPosition(start);
  236. nodeArray.push({ gold, randPos });
  237. }
  238. var notPlay = false;
  239. let dstPos = targetNode.worldPosition.clone();
  240. for (let i = 0; i < nodeArray.length; i++) {
  241. let pos = nodeArray[i].randPos;
  242. let node = nodeArray[i].gold;
  243. nodeArray[i].gold.id = i;
  244. tween(node)
  245. .to(0.2, { worldPosition: pos })
  246. .delay(i * 0.03)
  247. .to(0.7, { worldPosition: v3(dstPos.x, dstPos.y, 1) })
  248. .call(() => {
  249. if(!notPlay) {
  250. notPlay = true;
  251. tween(targetNode)
  252. .to(0.1, { scale: v3(2, 2, 2) })
  253. .to(0.1, { scale: v3(1, 1, 1) })
  254. .call(() => {
  255. notPlay = false;
  256. }).start()
  257. }
  258. let idx: number = node["_id"];
  259. callback(idx == nodeArray.length - 1);
  260. PoolManager.putNode(node);
  261. })
  262. .start()
  263. }
  264. }
  265. /**
  266. * 加载特效
  267. * @param type 路径
  268. * @param worldPos 位置
  269. * @param parent 父节点
  270. * @param callback 回调函数
  271. */
  272. public static async loadEffect(type: EffectType,worldPos:Vec3,removeTime: number = 0, parent?: Node, callback?: Function){
  273. let res: Node = await ResUtil.loadRes(type,parent);
  274. if(worldPos){
  275. res.worldPosition = worldPos;
  276. }
  277. if(parent){
  278. res.getComponentsInChildren(ParticleSystem)
  279. .forEach(e => {
  280. e.play();
  281. });
  282. }
  283. if(removeTime > 0){
  284. res.getComponent(UIOpacity).scheduleOnce(function(){
  285. this.getComponentsInChildren(ParticleSystem)
  286. .forEach(e => {
  287. e.stop();
  288. });
  289. PoolManager.putNode(this);
  290. }.bind(res),removeTime,);
  291. }
  292. }
  293. /**
  294. * 播放骨骼动画
  295. * @param skeletal 骨骼动画
  296. * @param name 动画名称
  297. * @param time 播放时间 播放的时长会控制播放的速度快慢
  298. * @param cb 播放完成回调
  299. */
  300. public static playSkeletalAnim(skeletal: SkeletalAnimation,name: string,time: number = 0, cb: Function = null){
  301. if(!skeletal)return;
  302. //查找目标动画剪辑
  303. const clip = skeletal.clips.find(c => c.name == name);
  304. if (!clip) {
  305. Logger.error(`[ResUtil.playSkeletalAnim] 未找到名为 "${name}" 的动画剪辑`);
  306. return;
  307. }
  308. //核心逻辑:通过调整播放速度,使动画总时长等于传入的time
  309. //公式推导:原始时长 = 播放速度 × 实际播放时间 → 播放速度 = 原始时长 / 目标时长
  310. if(time > 0) {
  311. clip.speed = clip.duration / time;
  312. }else{//恢复默认速度(避免之前的修改影响后续播放)
  313. clip.speed = 1;
  314. }
  315. skeletal.play(name);
  316. if(cb){
  317. skeletal.scheduleOnce(cb,time);
  318. }
  319. }
  320. }