ui.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. import { _decorator, Component, director, error, instantiate, Node, Prefab, UITransform, Widget, assetManager, AssetManager, tween, v3, UIOpacity, Vec3, ProgressBar, Sprite, Tween } from "cc";
  2. import ui_base from "./ui_base";
  3. import { ResolutionAutoFit } from "./ui_ResolutionAutoFit";
  4. const { ccclass, property } = _decorator;
  5. @ccclass('ui_updater')
  6. class ui_updater extends Component {
  7. update(dt: number) {
  8. ui_base.updateAll(dt);
  9. }
  10. }
  11. export enum GameUILayers {
  12. GAME,
  13. JOY_STICK,
  14. HUD,
  15. POPUP,
  16. ALERT,
  17. NOTICE,
  18. LOADING,
  19. OVERLAY
  20. }
  21. type Constructor<T extends ui_base> = new () => T;
  22. /**ui管理模块*/
  23. class ui {
  24. private static _instance: ui;
  25. public static getInstance(): ui {
  26. if (!this._instance) this._instance = new ui();
  27. return this._instance;
  28. }
  29. public delay(ms: number): Promise<void> {
  30. return new Promise(resolve => setTimeout(resolve, ms));
  31. }
  32. public delay_second(s: number): Promise<void> {
  33. return new Promise(resolve => setTimeout(resolve, s * 1000));
  34. }
  35. public progressBar_anim(bar: ProgressBar, progress: number, duration: number = 0.2): Promise<void> {
  36. if (bar.progress >= progress) {
  37. bar.progress = progress;
  38. return;
  39. } else {
  40. return new Promise(resolve => tween(bar).to(duration, { progress: progress }).call(() => { resolve() }).start());
  41. }
  42. }
  43. public fillRange_anim(sp: Sprite, progress: number, duration: number = 0.2, limt: -1 | 0 | 1 = 0): Promise<void> {
  44. if ((limt > 0 && sp.fillRange >= progress) && (limt < 0 && sp.fillRange <= progress)) {
  45. sp.fillRange = progress;
  46. return;
  47. } else {
  48. return new Promise(resolve => tween(sp).to(duration, { fillRange: progress }).call(() => { resolve() }).start());
  49. }
  50. }
  51. public scale_anim(node: Node, duration: number = 0.2, start: number = 0.5, end: number = 1,): Promise<void> {
  52. node.setScale(start, start, 1);
  53. return new Promise(resolve => tween(node).to(duration, { scale: v3(end, end, 1) }).call(() => { resolve() }).start());
  54. }
  55. public scale_elasticOut_anim(node: Node, duration: number = 0.2, start: number = 0.5, end: number = 1,): Promise<void> {
  56. node.setScale(start, start, 1);
  57. return new Promise(resolve => tween(node).to(duration, { scale: v3(end, end, 1) }, { easing: 'elasticOut' }).call(() => { resolve() }).start());
  58. }
  59. public fade_anim(node: Node, duration: number = 0.2, startOpacity: number = 0, targetOpacity: number = 255): Promise<void> {
  60. const opacity = node.getComponent(UIOpacity) ?? node.addComponent(UIOpacity);
  61. opacity.opacity = startOpacity;
  62. return new Promise((resolve) => { tween(opacity).to(duration, { opacity: targetOpacity }).call(() => { resolve() }).start(); });
  63. }
  64. public shake_anim(node: Node, duration: number = 0.2, intensity: number = 5, shakeCount: number = 7): Promise<void> {
  65. return new Promise((resolve) => {
  66. const originalPosition = node.position.clone(); // 保存原始位置
  67. const shakeDuration = duration / (shakeCount * 2); // 每次抖动的持续时间
  68. const shakeTween = tween(node);
  69. for (let i = 0; i < shakeCount; i++) {
  70. const offsetX = (i % 2 === 0 ? intensity : -intensity); // 交替偏移
  71. shakeTween.to(shakeDuration, { position: v3(originalPosition.x + offsetX, originalPosition.y, originalPosition.z) })
  72. .to(shakeDuration, { position: originalPosition }); // 回到原位
  73. }
  74. shakeTween.call(() => { resolve() }).start(); // 开始抖动动画
  75. });
  76. }
  77. public rotate_shake_anim(node: Node, duration: number = 0.3, intensity: number = 5, shakeCount: number = 5): Promise<void> {
  78. return new Promise((resolve) => {
  79. const originalAngle = node.angle; // 保存原始角度
  80. const shakeDuration = duration / (shakeCount * 2); // 每次晃动的持续时间
  81. const shakeTween = tween(node);
  82. for (let i = 0; i < shakeCount; i++) {
  83. const offsetAngle = (i % 2 === 0 ? intensity : -intensity); // 交替旋转
  84. shakeTween.to(shakeDuration, { angle: originalAngle + offsetAngle })
  85. .to(shakeDuration, { angle: originalAngle }); // 回到原位
  86. }
  87. shakeTween.call(() => { resolve() }).start(); // 开始角度晃动动画
  88. });
  89. }
  90. public scale_shake_anim(node: Node, duration: number = 0.3, intensity: number = 0.1, shakeCount: number = 10, reset: number | null = null): Promise<void> {
  91. if (reset) {
  92. Tween.stopAllByTarget(node);
  93. node.setScale(reset, reset, reset);
  94. }
  95. return new Promise((resolve) => {
  96. const originalScale = node.scale.clone(); // 保存原始缩放
  97. const shakeDuration = duration / (shakeCount * 2); // 每次震动的持续时间
  98. const shakeTween = tween(node);
  99. for (let i = 0; i < shakeCount; i++) {
  100. const offsetScale = (i % 2 === 0 ? intensity : -intensity); // 交替缩放
  101. shakeTween.to(shakeDuration, { scale: v3(originalScale.x + offsetScale, originalScale.y + offsetScale, originalScale.z) })
  102. .to(shakeDuration, { scale: originalScale }); // 回到原位
  103. }
  104. shakeTween.call(() => { resolve() }).start(); // 开始缩放震动动画
  105. });
  106. }
  107. public opacity_shake_anim(node: Node, duration: number = 0.8, intensity: number = 80, shakeCount: number = 5): Promise<void> {
  108. const opacity = node.getComponent(UIOpacity) ?? node.addComponent(UIOpacity);
  109. const originalOpacity = opacity.opacity;
  110. const shakeDuration = duration / (shakeCount * 2);
  111. return new Promise((resolve) => {
  112. const shakeTween = tween(opacity);
  113. for (let i = 0; i < shakeCount; i++) {
  114. const offsetOpacity = (i % 2 === 0 ? intensity : -intensity);
  115. shakeTween.to(shakeDuration, { opacity: Math.min(Math.max(originalOpacity + offsetOpacity, 0), 255) })
  116. .to(shakeDuration, { opacity: originalOpacity });
  117. }
  118. shakeTween.call(() => { resolve() }).start(); // 开始透明度震动动画
  119. });
  120. }
  121. /**处理弹跳动画*/
  122. public bounce_anim(node: Node, height: number = 100, duration: number = 0.5): Promise<void> {
  123. return new Promise((resolve) => {
  124. tween(node)
  125. .to(duration, { position: node.position.clone().add(new Vec3(0, height, 0)) }, { easing: 'bounceOut' }) // 向上弹跳
  126. .to(duration, { position: node.position }, { easing: 'bounceIn' }) // 回到原位
  127. .call(() => { resolve() }) //完成动画
  128. .start(); //开始动画
  129. });
  130. }
  131. /**
  132. * 奖励物飞行动画
  133. * @param node 奖励物节点
  134. * @param startPos 起始爆开点
  135. * @param targetPos 最终目标点
  136. * @param explosionDistance 爆开距离
  137. * @param explosionDuration 爆开时间
  138. * @param stayDuration 停留时间
  139. * @param moveDuration 移动到目标的时间
  140. * @returns
  141. */
  142. public reward_fly_anim(node: Node, startPos: Vec3, targetPos: Vec3, explosionDistance: number = 100, explosionDuration: number = 0.3, stayDuration: number = 0.5, moveDuration: number = 0.3): Promise<void> {
  143. node.setPosition(startPos);
  144. return new Promise((resolve) => {
  145. //随机方向
  146. const randomDirection = new Vec3(Math.random() * 2 - 1, Math.random() * 2 - 1, 0).normalize();
  147. const explosionEndPos = startPos.add(randomDirection.multiplyScalar(explosionDistance)); // 爆炸效果的位置
  148. tween(node)
  149. .to(explosionDuration, { worldPosition: explosionEndPos }) // 爆炸动画
  150. .delay(stayDuration) //停留时间
  151. .to(moveDuration, { worldPosition: targetPos }, { easing: 'cubicIn' })
  152. .call(() => { resolve() }).start();
  153. });
  154. }
  155. private _uiCanvas: Node;
  156. private _uiRoot: Node;
  157. private createFullScreenNode() {
  158. let canvas = this._uiCanvas.getComponent(UITransform);
  159. let node = new Node();
  160. node.layer = this._uiCanvas.layer;
  161. let uiTransform = node.addComponent(UITransform);
  162. uiTransform.width = canvas.width;
  163. uiTransform.height = canvas.height;
  164. let widget = node.addComponent(Widget);
  165. widget.isAlignBottom = true;
  166. widget.isAlignTop = true;
  167. widget.isAlignLeft = true;
  168. widget.isAlignRight = true;
  169. widget.left = 0;
  170. widget.right = 0;
  171. widget.top = 0;
  172. widget.bottom = 0;
  173. return node;
  174. }
  175. private getLayers(): GameUILayers[] {
  176. return Object.values(GameUILayers).filter(value => typeof value === 'number') as GameUILayers[];
  177. }
  178. /**
  179. * @en init,`don't call more than once`.
  180. * @zh 初始化UIMgr,`不要多次调用`
  181. * */
  182. public init(uiCanvas: Node | Prefab) {
  183. if (this._uiCanvas) return;
  184. if (!uiCanvas) throw error('uiCanvas must be a Node or Prefab');
  185. if (uiCanvas instanceof Node) {
  186. this._uiCanvas = uiCanvas;
  187. }
  188. else {
  189. this._uiCanvas = instantiate(uiCanvas);
  190. director.getScene().addChild(this._uiCanvas);
  191. }
  192. this._uiCanvas.name = '__ui_canvas__';
  193. director.addPersistRootNode(this._uiCanvas);
  194. if (!this._uiCanvas.getComponent(ui_updater)) {
  195. this._uiCanvas.addComponent(ui_updater);
  196. }
  197. let canvas = this._uiCanvas.getComponent(UITransform);
  198. this._uiCanvas.addComponent(ResolutionAutoFit);
  199. this._uiRoot = this.createFullScreenNode();
  200. this._uiRoot.name = 'root'
  201. canvas.node.addChild(this._uiRoot);
  202. const layers = this.getLayers();
  203. //create layers
  204. for (let i = 0; i < layers.length; ++i) {
  205. let layerNode = this.createFullScreenNode();
  206. layerNode.name = 'layer_' + GameUILayers[layers[i]];
  207. this._uiRoot.addChild(layerNode);
  208. }
  209. }
  210. /**获取层级节点*/
  211. public getLayerNode(layerIndex: number): Node {
  212. return this._uiRoot.children[layerIndex] || this._uiRoot;
  213. }
  214. /**关闭所有界面*/
  215. public closeAll() {
  216. ui_base.closeAll();
  217. }
  218. /**关闭和释放所有界面 */
  219. public closeAndReleaseAll() {
  220. ui_base.closeAndReleaseAll();
  221. }
  222. /**关闭某个界面*/
  223. public close<T extends ui_base>(uiCls: Constructor<T>): void {
  224. this.get(uiCls)?.close();
  225. }
  226. /**获取界面*/
  227. public get<T extends ui_base>(uiCls: Constructor<T>): T {
  228. let all = (ui_base as any)._uis;
  229. for (let i = 0; i < all.length; ++i) {
  230. let c = all[i];
  231. if (c instanceof uiCls) {
  232. return c;
  233. }
  234. }
  235. return null;
  236. }
  237. /**某个界面是否显示中*/
  238. public isShowing<T extends ui_base>(uiCls: Constructor<T>): boolean {
  239. return (ui_base as any)._hasCls(uiCls);
  240. }
  241. private _clss_loading: Set<any> = new Set();
  242. /**是否有正在加载中的界面*/
  243. public isLoading<T extends ui_base>(uiCls: Constructor<T> | null): boolean {
  244. if (!uiCls) return this._clss_loading.size > 0;
  245. return this._clss_loading.has(uiCls);
  246. }
  247. /***
  248. * @en show ui by the given parameters.
  249. * @zh 显示UI
  250. * @param uiCls the class, must inherits from the class `UIController`.
  251. * @returns the instance of `uiCls`
  252. * */
  253. public async show<T extends ui_base>(uiCls: Constructor<T>, ...data: any[]): Promise<T | null> {
  254. // if (this.isLoading(uiCls)) return null;
  255. // if (this.isShowing(uiCls)) return null;
  256. let ui = new uiCls();
  257. this._clss_loading.add(uiCls);
  258. let bundleName = ui.bundle;
  259. if (bundleName) {
  260. let bundle = assetManager.getBundle(bundleName);
  261. if (!bundle) {
  262. try {
  263. const loadedBundle = await this.loadBundleAsync(bundleName);
  264. return await this._create(loadedBundle, ui, uiCls, ...data);
  265. } catch (err) {
  266. console.error(err);
  267. this._clss_loading.delete(uiCls);
  268. }
  269. } else {
  270. return await this._create(bundle, ui, uiCls, ...data);
  271. }
  272. } else {
  273. this._clss_loading.delete(uiCls);
  274. console.error("ui no bundle name");
  275. return null;
  276. }
  277. }
  278. private async _create<T extends ui_base>(bundle: AssetManager.Bundle, ui: T, uiCls: Constructor<T>, ...p: any[]): Promise<T> {
  279. try {
  280. const data: Prefab = await this.loadPrefabAsync(bundle, ui.prefab);
  281. let node: Node = instantiate(data);
  282. let parent = this.getLayerNode(ui.layer);
  283. parent.addChild(node);
  284. (ui as any)._setup(uiCls, node, ...p);
  285. this._clss_loading.delete(uiCls);
  286. return ui;
  287. } catch (err) {
  288. console.error(err);
  289. this._clss_loading.delete(uiCls);
  290. return;
  291. }
  292. }
  293. private loadBundleAsync(bundleName: string): Promise<AssetManager.Bundle> {
  294. return new Promise((resolve, reject) => {
  295. assetManager.loadBundle(bundleName, null, (err, loadedBundle) => {
  296. if (err) {
  297. reject(err);
  298. } else {
  299. resolve(loadedBundle);
  300. }
  301. });
  302. });
  303. }
  304. private loadPrefabAsync(bundle: AssetManager.Bundle, prefabName: string): Promise<Prefab> {
  305. return new Promise((resolve, reject) => {
  306. bundle.load(prefabName, (err, data: Prefab) => {
  307. if (err) {
  308. reject(err);
  309. } else {
  310. resolve(data);
  311. }
  312. });
  313. });
  314. }
  315. }
  316. export const gui = ui.getInstance();
  317. export { ui_base };