BaseUI.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. /** @format */
  2. import {Data, Mgr} from '../GameControl'
  3. import {Log} from '../utils/LogUtils'
  4. import {ccUtils} from '../utils/ccUtils'
  5. import {GAME_TYPE} from '../enums/Enum'
  6. import Bundle = cc.AssetManager.Bundle
  7. import {UI} from '../enums/UI'
  8. /**
  9. * BaseUI界面基础类
  10. *
  11. * 1. 快速关闭与屏蔽点击的选项配置
  12. * 2. 界面缓存设置(开启后界面关闭不会被释放,以便下次快速打开)
  13. * 3. 界面显示类型配置
  14. *
  15. * 4. 加载资源接口(随界面释放自动释放),this.loadRes(xxx)
  16. * 5. 由UIManager释放
  17. *
  18. * 5. 界面初始化回调(只调用一次)
  19. * 6. 界面打开回调(每次打开回调)
  20. * 7. 界面打开动画播放结束回调(动画播放完回调)
  21. * 8. 界面关闭回调
  22. * 9. 界面置顶回调
  23. *
  24. */
  25. const {ccclass, property} = cc._decorator
  26. /** 界面展示类型 */
  27. export enum UIShowTypes {
  28. UISingle, // 单界面显示
  29. UIAddition = 500, // 叠加显示
  30. UITop = 1000, // 最上层显示,消息弹窗,悬浮提示等UI
  31. }
  32. @ccclass
  33. export class BaseUI extends cc.Component {
  34. /** 快速关闭 */
  35. @property()
  36. quickClose: boolean = false
  37. /** 屏蔽点击选项 在UIConf设置屏蔽点击*/
  38. // @property
  39. // preventTouch: boolean = true;
  40. /** 缓存选项 */
  41. @property()
  42. cache: boolean = false
  43. /** 界面显示类型 */
  44. @property({type: cc.Enum(UIShowTypes)})
  45. showType: UIShowTypes = UIShowTypes.UISingle
  46. /** 阻止点击事件穿透 */
  47. @property()
  48. prevent: boolean = true
  49. /** 界面id */
  50. public uiID: number = 0
  51. /** 通过全局预制体加载的节点 */
  52. public itemsNodes: cc.Node[] = []
  53. private loadAsset: any[] = []
  54. private nodeLoadingMap: Map<cc.Node, boolean> = new Map()
  55. private nodeLoadInfoMap: Map<cc.Node, any[]> = new Map()
  56. /********************** UI的回调 ***********************/
  57. /**
  58. * 当界面被创建时回调,生命周期内只调用
  59. * @param args 可变参数
  60. */
  61. public init(...args): void {}
  62. /**
  63. * 当界面被打开时回调,每次调用Open时回调
  64. * @param args 可变参数
  65. * @param fromUI 从哪个UI打开的
  66. */
  67. public onShow(args, fromUI: number): void {}
  68. /**
  69. * 每次界面Open动画播放完毕时回调
  70. */
  71. public onOpenAniOver(): void {}
  72. /**
  73. * 当界面被关闭时回调,每次调用Close时回调
  74. * 返回值会传递给下一个界面
  75. */
  76. public onHide(): any {}
  77. /**
  78. * 当界面被置顶时回调,Open时并不会回调该函数
  79. * @param preID 前一个ui
  80. * @param args 可变参数,
  81. */
  82. public onTop(preID: number, ...args): void {}
  83. /**
  84. * 隐藏当前界面
  85. */
  86. public hide(): void {
  87. Mgr.ui.hideBaseUI(this)
  88. }
  89. /**
  90. * 动态加载加入引用计数
  91. * @param bundle
  92. * @param url
  93. * @param node
  94. * @param childUrl
  95. */
  96. public load<T extends cc.Asset>(
  97. bundle: cc.AssetManager.Bundle,
  98. url: string,
  99. type: typeof cc.Asset,
  100. onComplete?: (error: Error, assets: T) => void,
  101. node?: cc.Node,
  102. childUrl?: string,
  103. addRef?: boolean,
  104. notRecordRef?: boolean,
  105. ) {
  106. if ((node && !cc.isValid(node)) || !bundle) {
  107. cc.warn(
  108. 'BaseUI.load node is not Valid:',
  109. (node && !cc.isValid(node)) == true,
  110. 'bundle is Valid:',
  111. cc.isValid(bundle),
  112. )
  113. return
  114. }
  115. if (childUrl) {
  116. const child = cc.find(childUrl, node)
  117. if (child) {
  118. node = cc.find(childUrl, node)
  119. }
  120. }
  121. let sprite
  122. if (node) {
  123. sprite = node.getComponent(cc.Sprite)
  124. }
  125. if (sprite && sprite.spriteFrame && !notRecordRef) {
  126. let index = this.loadAsset.findIndex(value => value.asset == sprite.spriteFrame)
  127. if (index >= 0) {
  128. let data = this.loadAsset[index]
  129. if (data.sprite == sprite && data.url == bundle.name + url) {
  130. //相同的sprite重复加载了同一个资源,return
  131. return
  132. }
  133. if (sprite.addRefTag) {
  134. sprite.spriteFrame = null
  135. let same = this.loadAsset.splice(index, 1)
  136. same[0].asset.decRef()
  137. if (same[0].asset.refCount < 0) {
  138. console.error('refCount < 0', same[0].asset)
  139. }
  140. }
  141. //console.log(same[0].name, '--->uuid:', same[0]['_uuid'], '-->已在预制体加载计数-1')
  142. }
  143. }
  144. let onCompleteFuc = (err, asset: T) => {
  145. if (!err && this.node.activeInHierarchy) {
  146. let data: any = {asset: asset, url: bundle.name + url, addRef, decRef: false}
  147. if (type == cc.SpriteFrame) {
  148. if (!sprite) console.error('sprite is null', 'node.name-->', node.name, 'childUrl-->', childUrl)
  149. sprite.spriteFrame = asset
  150. data.sprite = sprite
  151. sprite.addRefTag = addRef
  152. }
  153. if (type == cc.AnimationClip) {
  154. data.animation = node.getComponent(cc.Animation)
  155. }
  156. if (!notRecordRef) {
  157. if (addRef) {
  158. asset.addRef()
  159. data.decRef = url.includes(UI[this.uiID])
  160. // console.log(
  161. // asset.name,
  162. // '--->uuid:',
  163. // asset['_uuid'],
  164. // '-->计数+1',
  165. // '-->isValid:',
  166. // asset.isValid,
  167. // asset,
  168. // )
  169. } else {
  170. if (type == cc.SpriteFrame) {
  171. let index = this.loadAsset.findIndex(value => value.asset == asset && !value.addRef)
  172. if (index == -1) {
  173. // console.log(
  174. // asset.name,
  175. // '--->uuid:',
  176. // asset['_uuid'],
  177. // '-->计数只为1',
  178. // '-->isValid:',
  179. // asset.isValid,
  180. // asset,
  181. // )
  182. asset.addRef()
  183. data.decRef = url.includes(UI[this.uiID])
  184. }
  185. }
  186. }
  187. this.loadAsset.push(data)
  188. }
  189. } else if (err) {
  190. //console.log(err)
  191. }
  192. onComplete && onComplete(err, asset)
  193. let arr = this.nodeLoadInfoMap.get(node)
  194. if (arr && arr.length > 0) {
  195. let info = arr.shift()
  196. bundle.load(info.url, info.type, info.onCompleteFuc)
  197. } else {
  198. this.nodeLoadingMap.set(node, false)
  199. }
  200. }
  201. if (node) {
  202. let isLoading = this.nodeLoadingMap.get(node)
  203. if (!isLoading) {
  204. this.nodeLoadingMap.set(node, true)
  205. // 等待当前资源释放帧结束,不然会有概率加载重复资源时完成时间小于资源释放,导致被提前释放
  206. this.scheduleOnce(() => {
  207. bundle.load(url, type, onCompleteFuc)
  208. })
  209. } else {
  210. let arr = this.nodeLoadInfoMap.get(node)
  211. if (arr) {
  212. arr.push({url, type, onCompleteFuc})
  213. } else {
  214. arr = [{url, type, onCompleteFuc}]
  215. this.nodeLoadInfoMap.set(node, arr)
  216. }
  217. }
  218. } else {
  219. bundle.load(url, type, onCompleteFuc)
  220. }
  221. }
  222. public loadTexImg(url: string, node?: cc.Node, childUrl?: string, addRef?: boolean) {
  223. this.load(Data.main.texBundle, url, cc.SpriteFrame, null, node, childUrl, addRef)
  224. }
  225. public loadNotRefTexImg(url: string, node?: cc.Node, childUrl?: string) {
  226. this.load(Data.main.texBundle, url, cc.SpriteFrame, null, node, childUrl, false, true)
  227. }
  228. //同步加载,不计入引用计数
  229. public loadSync<T extends cc.Asset>(bundle: cc.AssetManager.Bundle, url: string, type: typeof cc.Asset) {
  230. return new Promise((resolve: (asset: T) => void, reject) => {
  231. bundle.load(url, type, (err, asset: T) => {
  232. if (err) {
  233. resolve(null)
  234. } else {
  235. resolve(asset)
  236. }
  237. })
  238. })
  239. }
  240. /**
  241. * 减去当前UI生命周期内动态加载的资源引用
  242. */
  243. public decAllRef(): void {
  244. for (let i = this.itemsNodes.length - 1; i > 0; i--) {
  245. let node = this.itemsNodes.splice(i, 1)
  246. if (node[0]['uiID'] == this.uiID) {
  247. Data.main.itemsPoolMap.get(node[0].name)?.put(node[0])
  248. }
  249. }
  250. this.loadAsset.forEach(value => {
  251. if (value.decRef) {
  252. value.asset.decRef()
  253. }
  254. if (value.sprite) {
  255. value.sprite.spriteFrame = null
  256. }
  257. if (value.animation) {
  258. let clips = value.animation.getClips()
  259. if (clips) {
  260. clips.forEach(clip => {
  261. if (clip.name == value.asset.name) value.animation.removeClip(clip, true)
  262. })
  263. }
  264. }
  265. // console.log(
  266. // value.asset.name,
  267. // '--->uuid:',
  268. // value.asset['_uuid'],
  269. // '--->refCount:',
  270. // value.asset.refCount,
  271. // '-->尝试释放',
  272. // value,
  273. // )
  274. })
  275. this.loadAsset = []
  276. this.nodeLoadInfoMap.clear()
  277. this.nodeLoadingMap.clear()
  278. }
  279. creatAniNode(parent: cc.Node, bundle: Bundle, url: string, isDestroy?: boolean, finishFuc?: Function): cc.Node {
  280. let node = new cc.Node()
  281. node.name = url.substring(url.lastIndexOf('/') + 1)
  282. node.addComponent(cc.Sprite)
  283. node.parent = parent
  284. let ani = node.addComponent(cc.Animation)
  285. this.load(
  286. bundle,
  287. url,
  288. cc.AnimationClip,
  289. (err, animationClip: cc.AnimationClip) => {
  290. if (!err && this.node.activeInHierarchy) {
  291. ani.addClip(animationClip)
  292. ccUtils.playAni(animationClip.name, 0, node)
  293. ani.once('finished', () => {
  294. finishFuc && finishFuc()
  295. if (isDestroy) node.destroy()
  296. })
  297. }
  298. },
  299. node,
  300. )
  301. return node
  302. }
  303. }