ccUtils.ts 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222
  1. /** @format */
  2. import {ConstValue} from '../data/ConstValue'
  3. class CCUtils {
  4. /**
  5. * 设置Label文本
  6. * @param str
  7. * @param node
  8. * @param childUrl
  9. */
  10. public setLabel(str: string, node: cc.Node, childUrl?: string) {
  11. if (!cc.isValid(node)) {
  12. return
  13. }
  14. if (childUrl) {
  15. const child = cc.find(childUrl, node)
  16. if (child) {
  17. node = cc.find(childUrl, node)
  18. }
  19. }
  20. let label = node.getComponent(cc.Label)
  21. if (node.getComponent('i18nLabel')) {
  22. label = node.getComponent('i18nLabel')
  23. }
  24. if (label) {
  25. label.string = str
  26. } else {
  27. cc.warn('setLabel err, str:', str)
  28. }
  29. }
  30. public labelForceUpdateRenderData(node: cc.Node, childUrl?: string) {
  31. if (!cc.isValid(node)) {
  32. return
  33. }
  34. if (childUrl) {
  35. const child = cc.find(childUrl, node)
  36. if (child) {
  37. node = cc.find(childUrl, node)
  38. }
  39. }
  40. let label = node.getComponent(cc.Label)
  41. if (label) {
  42. ;(<any>label)._forceUpdateRenderData()
  43. } else {
  44. cc.warn('labelForceUpdateRenderData err')
  45. }
  46. }
  47. /**
  48. * 设置LabelOutLine颜色
  49. * @param color '#XXXXXX'| cc.Color
  50. * @param node
  51. * @param childUrl
  52. */
  53. public setLabelOutlineColor(color: string | cc.Color, node: cc.Node, childUrl?: string) {
  54. if (!cc.isValid(node)) {
  55. return
  56. }
  57. if (childUrl) {
  58. node = cc.find(childUrl, node)
  59. }
  60. const label = node.getComponent(cc.LabelOutline)
  61. if (label) {
  62. if (typeof color == 'string') {
  63. label.color = new cc.Color().fromHEX(color)
  64. } else {
  65. label.color = color
  66. }
  67. } else {
  68. cc.warn('setLabelOutlineColor err, color:', color)
  69. }
  70. }
  71. /**
  72. * 设置RichLabel文本
  73. * @param str
  74. * @param node
  75. * @param childUrl
  76. */
  77. public setRichLabel(str: string, node: cc.Node, childUrl?: string) {
  78. if (!cc.isValid(node)) {
  79. return
  80. }
  81. if (childUrl) {
  82. node = cc.find(childUrl, node)
  83. }
  84. let label = node.getComponent(cc.RichText)
  85. if (node.getComponent('i18nLabel')) {
  86. label = node.getComponent('i18nLabel')
  87. }
  88. if (label) {
  89. label.string = str
  90. } else {
  91. cc.warn('setRichLabel err, str:', str)
  92. }
  93. }
  94. public setRichLabelGray(node: cc.Node, childUrl?: string, grayColor?: string) {
  95. if (!cc.isValid(node)) {
  96. return
  97. }
  98. if (childUrl) {
  99. node = cc.find(childUrl, node)
  100. }
  101. let label = node.getComponent(cc.RichText)
  102. let gray = grayColor ? grayColor : '#AAAAAA'
  103. if (label) {
  104. label.string = `<color=${gray}>${label.string.replace(/<color=[^>]*>/g, `<color=${gray}>`)}</color>`
  105. } else {
  106. cc.warn('setRichLabelGray err, label:', label)
  107. }
  108. }
  109. /**
  110. * 按照spriteFrame设置大小
  111. */
  112. public setSFAndScale(spriteFrame: cc.SpriteFrame, scale: number = 1, node: cc.Node, childUrl?: string) {
  113. if (!cc.isValid(node)) {
  114. return
  115. }
  116. if (childUrl) {
  117. node = cc.find(childUrl, node)
  118. }
  119. node.getComponent(cc.Sprite).spriteFrame = spriteFrame
  120. if (spriteFrame) {
  121. node.width = spriteFrame.getRect().width * scale
  122. node.height = spriteFrame.getRect().height * scale
  123. } else {
  124. cc.warn('setSFAndScale err')
  125. }
  126. }
  127. /**
  128. * 设置spriteFrame
  129. */
  130. public setSpriteFrame(spriteFrame: cc.SpriteFrame, node: cc.Node, childUrl?: string) {
  131. if (!cc.isValid(node)) {
  132. return
  133. }
  134. if (childUrl) {
  135. node = cc.find(childUrl, node)
  136. }
  137. let sprite = node.getComponent(cc.Sprite)
  138. if (!sprite) {
  139. cc.warn('setSFAndScale err no sprite')
  140. return
  141. }
  142. sprite.spriteFrame = spriteFrame
  143. }
  144. /**
  145. *
  146. * @param isInteract
  147. * @param node
  148. * @param childUrl
  149. */
  150. public setBtnInteract(isInteract: boolean, node: cc.Node, childUrl?: string, isHasWarn: boolean = true) {
  151. if (!cc.isValid(node)) {
  152. return
  153. }
  154. if (childUrl) {
  155. node = cc.find(childUrl, node)
  156. }
  157. const button = node.getComponent(cc.Button)
  158. if (button) {
  159. button.interactable = isInteract
  160. } else {
  161. if (isHasWarn) {
  162. cc.warn('setBtnInteract err')
  163. }
  164. }
  165. }
  166. /**
  167. * 进度条 设置进度
  168. * @param progress
  169. * @param node
  170. * @param childUrl
  171. */
  172. public setProgress(progress: number, node: cc.Node, childUrl?: string) {
  173. if (!cc.isValid(node)) {
  174. return
  175. }
  176. if (childUrl) {
  177. node = cc.find(childUrl, node)
  178. }
  179. const progressBar = node.getComponent(cc.ProgressBar)
  180. if (progressBar) {
  181. progressBar.progress = progress
  182. } else {
  183. cc.warn('setProgress err')
  184. }
  185. }
  186. /**
  187. * 滑动条 设置进度
  188. * @param progress
  189. * @param node
  190. * @param childUrl
  191. */
  192. public setSliderProgress(progress: number, node: cc.Node, childUrl?: string) {
  193. if (!cc.isValid(node)) {
  194. return
  195. }
  196. if (childUrl) {
  197. node = cc.find(childUrl, node)
  198. }
  199. const progressBar = node.getComponent(cc.Slider)
  200. if (progressBar) {
  201. progressBar.progress = progress
  202. } else {
  203. cc.warn('setSiderProgress err')
  204. }
  205. }
  206. /**
  207. *
  208. * @param isInteract
  209. * @param node(父节点)
  210. * @param childUrl
  211. */
  212. public setTogglesInteract(isInteract: boolean, node: cc.Node, childUrl?: string) {
  213. if (!cc.isValid(node)) {
  214. return
  215. }
  216. if (childUrl) {
  217. node = cc.find(childUrl, node)
  218. }
  219. const toggle = node.getComponent(cc.Toggle)
  220. if (toggle) {
  221. toggle.enabled = isInteract
  222. }
  223. }
  224. /**
  225. *
  226. * @param checkedIndex
  227. * @param node(父节点)
  228. * @param childUrl
  229. */
  230. public setTogglesChecked(checkedIndex: number, node: cc.Node, childUrl?: string) {
  231. if (!cc.isValid(node)) {
  232. return
  233. }
  234. if (childUrl) {
  235. node = cc.find(childUrl, node)
  236. }
  237. const children = node.children
  238. children.forEach((value, index) => {
  239. const toggle = value.getComponent(cc.Toggle)
  240. if (toggle) {
  241. toggle.isChecked = index == checkedIndex
  242. }
  243. })
  244. }
  245. public setColor(color: string, node: cc.Node, childUrl?: string) {
  246. if (!cc.isValid(node)) {
  247. return
  248. }
  249. if (childUrl) {
  250. node = cc.find(childUrl, node)
  251. }
  252. node.color = cc.Color.WHITE.fromHEX(color)
  253. }
  254. /**
  255. * 按钮延迟激活
  256. */
  257. public btnDelayActive(time: number, node: cc.Node, childUrl?: string) {
  258. if (!cc.isValid(node)) {
  259. return
  260. }
  261. if (childUrl) {
  262. node = cc.find(childUrl, node)
  263. }
  264. const btn = node.getComponent(cc.Button)
  265. if (btn) {
  266. btn.interactable = false
  267. node.stopAllActions()
  268. node.runAction(
  269. cc.sequence(
  270. cc.delayTime(time),
  271. cc.callFunc(() => {
  272. btn.interactable = true
  273. }),
  274. ),
  275. )
  276. } else {
  277. cc.warn('btnDelayActive err')
  278. }
  279. }
  280. /**
  281. * 设置EditBox文本
  282. * @param str
  283. * @param node
  284. * @param childUrl
  285. */
  286. public setEditBox(str: string, node: cc.Node, childUrl?: string) {
  287. if (!cc.isValid(node)) {
  288. return
  289. }
  290. if (childUrl) {
  291. const child = cc.find(childUrl, node)
  292. if (child) {
  293. node = cc.find(childUrl, node)
  294. }
  295. }
  296. const editBox = node.getComponent(cc.EditBox)
  297. if (editBox) {
  298. editBox.string = str
  299. } else {
  300. cc.warn('setEditBox err, str:', str)
  301. }
  302. }
  303. /**
  304. * 停止动画
  305. */
  306. public stopAni(node: cc.Node, clipName?: string, childUrl?: string) {
  307. if (!cc.isValid(node)) {
  308. return
  309. }
  310. if (childUrl) {
  311. node = cc.find(childUrl, node)
  312. }
  313. const ani = node.getComponent(cc.Animation)
  314. if (ani) {
  315. ani.play(clipName)
  316. ani.setCurrentTime(0, clipName)
  317. ani.sample(clipName)
  318. ani.stop(clipName)
  319. } else {
  320. //cc.warn('stopAni err')
  321. }
  322. }
  323. /**
  324. * 开始动画
  325. */
  326. public playAni(aniStr: string, startTime: number = 0, node: cc.Node, childUrl?: string): cc.Animation {
  327. if (!cc.isValid(node)) {
  328. return
  329. }
  330. if (childUrl) {
  331. node = cc.find(childUrl, node)
  332. }
  333. this.stopAni(node, aniStr)
  334. const ani = node.getComponent(cc.Animation)
  335. if (ani) {
  336. ani.play(aniStr, startTime)
  337. return ani
  338. } else {
  339. cc.warn('playAni err')
  340. }
  341. }
  342. public playNodeAllAni(node: cc.Node, childUrl?: string) {
  343. if (!cc.isValid(node)) {
  344. return
  345. }
  346. if (childUrl) {
  347. node = cc.find(childUrl, node)
  348. }
  349. node.active = true
  350. const children = node.children
  351. children.forEach(value => {
  352. const aniCom = value.getComponent(cc.Animation)
  353. if (aniCom) {
  354. this.stopAni(value)
  355. aniCom.play()
  356. }
  357. })
  358. }
  359. public stopNodeAllAni(node: cc.Node, childUrl?: string) {
  360. if (!cc.isValid(node)) {
  361. return
  362. }
  363. if (childUrl) {
  364. node = cc.find(childUrl, node)
  365. }
  366. node.active = false
  367. const children = node.children
  368. children.forEach(value => {
  369. const aniCom = value.getComponent(cc.Animation)
  370. if (aniCom) {
  371. this.stopAni(value)
  372. }
  373. })
  374. }
  375. /**
  376. * 判断当前节点是否在屏幕内
  377. */
  378. public isInScreen(node: cc.Node, yIn: boolean = false, xIn: boolean = false) {
  379. if (!cc.isValid(node)) {
  380. return
  381. }
  382. let isyIn = false
  383. let isxIn = false
  384. const worldPos = node.convertToWorldSpaceAR(cc.v2(0, 0))
  385. isyIn = worldPos.y - node.height <= 1136 && worldPos.y + node.height >= 0
  386. isxIn = worldPos.x - node.width <= 640 && worldPos.x + node.width >= 0
  387. if (yIn) {
  388. return isyIn
  389. }
  390. if (xIn) {
  391. return isxIn
  392. }
  393. return isxIn && isyIn
  394. }
  395. /**
  396. * 获取全部的子节点(递归)
  397. * @param node
  398. */
  399. public getAllChildrenByRecurve(node: cc.Node) {
  400. let ret: cc.Node[] = []
  401. ret = ret.concat(node.children)
  402. for (let i = 0; i < node.children.length; i++) {
  403. ret = ret.concat(this.getAllChildrenByRecurve(node.children[i]))
  404. }
  405. return ret
  406. }
  407. /**
  408. * 获取全部的子节点(广度)
  409. * @param node
  410. */
  411. public getAllChildrenBFS(node: cc.Node) {
  412. if (!node || !node.children.length) {
  413. return
  414. }
  415. const ret = []
  416. let stack = []
  417. // 先将第一层节点放入栈
  418. for (let i = 0, len = node.children.length; i < len; i++) {
  419. stack.push(node.children[i])
  420. }
  421. let item = null
  422. while (stack.length) {
  423. item = stack.shift()
  424. ret.push(item)
  425. // 如果该节点有子节点,继续添加进入栈底
  426. if (item.children && item.children.length) {
  427. stack = stack.concat(item.children)
  428. }
  429. }
  430. return ret
  431. }
  432. /**
  433. * 获取全部的子节点(深度)
  434. * @param node
  435. */
  436. public getAllChildrenDFS(node: cc.Node): cc.Node[] {
  437. if (!node || !node.children.length) {
  438. return
  439. }
  440. const ret = []
  441. let stack = []
  442. // 先将第一层节点放入栈
  443. for (let i = 0, len = node.children.length; i < len; i++) {
  444. stack.push(node.children[i])
  445. }
  446. let item = null
  447. while (stack.length) {
  448. item = stack.shift()
  449. ret.push(item)
  450. // 如果该节点有子节点,继续添加进入栈顶
  451. if (item.children && item.children.length) {
  452. stack = item.children.concat(stack)
  453. }
  454. }
  455. return ret
  456. }
  457. /**
  458. * 获取name应该对应的node
  459. * @param {节点} nodes
  460. * @param {*} val
  461. */
  462. public findNodeByName(node: cc.Node, name: string): cc.Node | null {
  463. if (node.name === name) {
  464. return node
  465. }
  466. for (let i = 0; i < node.children.length; i++) {
  467. if (this.findNodeByName(node.children[i], name)) {
  468. // 若找到则返回该节点
  469. return this.findNodeByName(node.children[i], name)
  470. }
  471. }
  472. return null
  473. }
  474. public setNodeColorGray(isGray: boolean, node: cc.Node, childUrl?: string) {
  475. if (!cc.isValid(node)) {
  476. return
  477. }
  478. if (childUrl) {
  479. node = cc.find(childUrl, node)
  480. }
  481. node.color = isGray ? cc.Color.GRAY : cc.Color.WHITE
  482. }
  483. /**
  484. * 将节点置灰
  485. * @param node
  486. * @param isGray
  487. */
  488. public setAllGray(isGray: boolean, node: cc.Node, childUrl?: string) {
  489. if (childUrl) {
  490. node = cc.find(childUrl, node)
  491. }
  492. const all = this.getAllChildrenDFS(node).concat(node)
  493. if (all) {
  494. for (let i = 0; i < all.length; i++) {
  495. if (all[i].name.indexOf('notGray') != -1) {
  496. continue
  497. }
  498. this.setBtnInteract(!isGray, all[i], null, false)
  499. this.setSpriteGray(isGray, all[i])
  500. //文字相关置灰
  501. const customKey = 'custom_originColor'
  502. let labGray = (obj: cc.LabelOutline | cc.Label) => {
  503. if (!obj?.isValid) return
  504. if (!isGray) {
  505. let originColor: string = obj[customKey]
  506. if (originColor) {
  507. if (obj instanceof cc.Label) {
  508. obj.node.color = cc.Color.WHITE.fromHEX(originColor)
  509. } else {
  510. obj.color = cc.Color.WHITE.fromHEX(originColor)
  511. }
  512. obj[customKey] = null
  513. }
  514. } else {
  515. let lbColor = obj instanceof cc.Label ? obj.node.color : obj.color
  516. if (!obj[customKey]) obj[customKey] = lbColor.toHEX()
  517. if (obj instanceof cc.Label) {
  518. obj.node.color = cc.Color.WHITE.fromHEX('#272727')
  519. } else {
  520. obj.color = cc.Color.WHITE.fromHEX('#A79D9C')
  521. }
  522. }
  523. }
  524. let label = all[i].getComponent(cc.Label)
  525. labGray(label)
  526. let labOutline = all[i].getComponent(cc.LabelOutline)
  527. labGray(labOutline)
  528. }
  529. }
  530. }
  531. /**
  532. * 将精灵置灰
  533. * @param node
  534. * @param isGray
  535. */
  536. public setSpriteGray(isGray: boolean, node: cc.Node, childUrl?: string) {
  537. if (!cc.isValid(node)) {
  538. return
  539. }
  540. if (childUrl) {
  541. node = cc.find(childUrl, node)
  542. }
  543. const sprite = node.getComponent(cc.Sprite)
  544. if (!sprite) {
  545. return
  546. }
  547. if (isGray) {
  548. sprite.setMaterial(0, cc.Material.getBuiltinMaterial('2d-gray-sprite'))
  549. } else {
  550. sprite.setMaterial(0, cc.Material.getBuiltinMaterial('2d-sprite'))
  551. }
  552. }
  553. /**
  554. * 返回使用数学计量单位K(千) M(兆) G(吉)....来表示的数字
  555. * @param n
  556. * @param fix 保留几位小数 0
  557. */
  558. // public getSCI(n: number | string | Long, fix?:number): string {
  559. // const base = Long.fromValue(n);
  560. // let baseString = n.toString(); // 小数部分
  561. // if (base.lessThanOrEqual(10000)) {
  562. // return base.toString();
  563. // }
  564. // const si = [
  565. // { value: 1, symbol: '' },
  566. // { value: 1E3, symbol: 'K' },
  567. // { value: 1E6, symbol: 'M' },
  568. // { value: 1E9, symbol: 'G' },
  569. // { value: 1E12, symbol: 'T' },
  570. // { value: 1E15, symbol: 'P' },
  571. // { value: 1E18, symbol: 'E' },
  572. // ];
  573. // if (fix && baseString.length > fix) {
  574. // if (baseString.length > 0) {
  575. // baseString = `.${baseString.substr(1, fix)}`;
  576. // } else {
  577. // baseString = '';
  578. // }
  579. // } else {
  580. // baseString = '';
  581. // }
  582. // let i;
  583. // for (i = si.length - 1; i > 0; i--) {
  584. // if (base.greaterThanOrEqual(si[i].value)) {
  585. // break;
  586. // }
  587. // }
  588. // return (base.divide(si[i].value)).toString() + baseString + si[i].symbol;
  589. // }
  590. /**
  591. * 将一个节点转换为Canvas下的坐标AR
  592. * @param node 指定的节点
  593. */
  594. public convertToWorldSpaceCanvasAR(node: cc.Node) {
  595. const pos = node.parent.convertToWorldSpaceAR(node.position)
  596. return cc.Canvas.instance.node.convertToNodeSpaceAR(pos)
  597. }
  598. /**
  599. * 将Canvas下的坐标转换为一个节点AR
  600. * @param node 指定的节点
  601. */
  602. public convertCanvasToNodeAR(node: cc.Node, pos: cc.Vec2 = cc.Vec2.ZERO) {
  603. const world = cc.Canvas.instance.node.convertToWorldSpaceAR(pos)
  604. return node.convertToNodeSpaceAR(world)
  605. }
  606. /**
  607. * node节点在target节点下的坐标AR
  608. * @param node 指定的节点
  609. */
  610. public convertNode2NodePosAR(node: cc.Node, target: cc.Node) {
  611. const pos = this.convertToWorldSpacePos(node)
  612. return this.convertWorldPosToNode(target, pos)
  613. }
  614. // 将一个节点转换为世界坐标
  615. public convertToWorldSpacePos(node: cc.Node) {
  616. return node.parent.convertToWorldSpaceAR(node.getPosition())
  617. }
  618. // 将世界坐标转为节点内坐标
  619. public convertWorldPosToNode(node: cc.Node, pos: cc.Vec2) {
  620. return node.convertToNodeSpaceAR(pos)
  621. }
  622. // 将触点坐标转为世界坐标
  623. public convertTouchPosToWorld(e) {
  624. let screenPos = e.getLocation()
  625. let dSize = cc.view.getDesignResolutionSize()
  626. let dV2 = cc.v2(dSize.width, dSize.height)
  627. let winV2 = cc.v2(cc.winSize.width, cc.winSize.height)
  628. return screenPos.sub(winV2.sub(dV2).divide(2))
  629. }
  630. // 角度转弧度
  631. angle2radian(angle: number): number {
  632. // 角度转弧度公式
  633. // π / 180 * 角度
  634. // 计算出弧度
  635. let radian = (Math.PI / 180) * angle
  636. // 返回弧度
  637. return radian
  638. }
  639. // 弧度转角度
  640. radian2angle(radian: number): number {
  641. // 弧度转角度公式
  642. // 180 / π * 弧度
  643. // 计算出角度
  644. let angle = (180 / Math.PI) * radian
  645. // 返回弧度
  646. return angle
  647. }
  648. // 角度转向量
  649. angle2pos(angle: number): cc.Vec2 {
  650. // tan = sin / cos
  651. // 将传入的角度转为弧度
  652. let radian = this.angle2radian(angle)
  653. // 算出cos,sin和tan
  654. let cos = Math.cos(radian) // 邻边 / 斜边
  655. let sin = Math.sin(radian) // 对边 / 斜边
  656. let tan = sin / cos // 对边 / 邻边
  657. // 结合在一起并归一化
  658. let vec = cc.v2(cos, sin).normalize()
  659. // 返回向量
  660. return vec
  661. }
  662. // 向量转角度
  663. pos2angle(vector: cc.Vec2, curNormalize: cc.Vec2 = cc.v2(1, 0)): number {
  664. // 将传入的向量归一化
  665. let dir = vector.normalize()
  666. // 计算出目标角度的弧度
  667. let radian = dir.signAngle(curNormalize)
  668. // 把弧度计算成角度
  669. let angle = -this.radian2angle(radian)
  670. // 返回角度
  671. return angle
  672. }
  673. /**
  674. * 震屏效果
  675. * @param duration 震屏时间
  676. */
  677. public shakeEffect(
  678. duration: number,
  679. node: cc.Node,
  680. origin: cc.Vec2,
  681. callBack?: Function,
  682. bgNode?: cc.Node,
  683. isShakeX = false,
  684. random: number = 5,
  685. ) {
  686. node.setPosition(origin)
  687. const speed = 0.02
  688. const action = []
  689. let num = duration / speed
  690. if (num > 10) {
  691. num = 10
  692. }
  693. let originScale = 1
  694. if (bgNode) {
  695. originScale = bgNode.scale
  696. bgNode.scale = originScale * (1 + 0.1)
  697. }
  698. for (let i = 0; i < num; i++) {
  699. let randomX = 0
  700. if (isShakeX) {
  701. randomX = Math.randomRangeInt(-random, random)
  702. }
  703. const randomY = Math.randomRangeInt(-random, random)
  704. action.push(cc.moveTo(speed, origin.sub(cc.v2(randomX * node.scale, randomY * node.scale))))
  705. }
  706. const shake = node.runAction(cc.repeatForever(cc.sequence(action)))
  707. setTimeout(() => {
  708. if (cc.isValid(node)) {
  709. node.stopAction(shake)
  710. node.setPosition(origin)
  711. if (bgNode) {
  712. bgNode.scale = originScale
  713. }
  714. callBack && callBack()
  715. }
  716. }, duration * 1000)
  717. }
  718. public capture() {
  719. // REAL_WIDTH REAL_HEIGHT 可能为小数向下取整避免有作为下标访问数组会数据错位
  720. const width = Math.floor(ConstValue.REAL_HEIGHT)
  721. const height = Math.floor(ConstValue.REAL_WIDTH)
  722. const node = new cc.Node()
  723. node.parent = cc.director.getScene()
  724. node.setPosition(width / 2, height / 2)
  725. const camera = node.addComponent(cc.Camera)
  726. const texture = new cc.RenderTexture()
  727. const gl = cc.game['_renderContext']
  728. texture.initWithSize(width, height, gl.STENCIL_INDEX8)
  729. camera.targetTexture = texture
  730. camera.render(cc.Canvas.instance.node)
  731. camera.enabled = false
  732. const data = texture.readPixels()
  733. const picData = new Uint8Array(width * height * 4)
  734. const rowBytes = width * 4
  735. for (let row = 0; row < height; row++) {
  736. const srow = height - 1 - row
  737. const start = srow * width * 4
  738. const reStart = row * width * 4
  739. for (let i = 0; i < rowBytes; i++) {
  740. picData[reStart + i] = data[start + i]
  741. }
  742. }
  743. camera.targetTexture = null
  744. node.destroy()
  745. texture.initWithData(picData, cc.Texture2D.PixelFormat.RGBA8888, width, height)
  746. const spriteFrame = new cc.SpriteFrame()
  747. spriteFrame.setTexture(texture)
  748. return spriteFrame
  749. }
  750. public getBundleAsync(name) {
  751. return new Promise((resolve: (bundle: cc.AssetManager.Bundle) => void, reject) => {
  752. let callFuc = (err, bundle) => {
  753. if (err) {
  754. console.error('loadBundle error name:', name)
  755. reject(null)
  756. } else {
  757. resolve(bundle)
  758. }
  759. }
  760. cc.assetManager.loadBundle(name, callFuc)
  761. })
  762. }
  763. /**
  764. *
  765. * 返回节点在世界坐标系下的对齐轴向的包围盒(AABB)不包含子节点的世界边框
  766. *
  767. * @param {节点} node节点
  768. */
  769. getBoundingBoxToWorld(node: any): cc.Rect {
  770. let w_n: number = node._contentSize.width
  771. let h_n: number = node._contentSize.height
  772. let rect_o = cc.rect(-node._anchorPoint.x * w_n, -node._anchorPoint.y * h_n, w_n, h_n)
  773. node._calculWorldMatrix()
  774. rect_o.transformMat4(rect_o, node._worldMatrix)
  775. return rect_o
  776. }
  777. /**
  778. *
  779. * 节点是否包含子节点
  780. *
  781. * @param {节点1} node1 容器判断节点
  782. * @param {节点2} node2 拖动节点
  783. */
  784. contains(node1: cc.Node, node2: cc.Node) {
  785. // 判断node2的中心点0,0是否在node1内
  786. return this.getBoundingBoxToWorld(node1).contains(node2.convertToWorldSpaceAR(cc.v2(0, 0)))
  787. }
  788. /**
  789. *
  790. * 节点是否包含子节点
  791. *
  792. * @param {节点1} node1
  793. * @param {世界坐标} p
  794. */
  795. containsP(node: cc.Node, p: cc.Vec3) {
  796. return this.getBoundingBoxToWorld(node).contains(cc.v2(p.x, p.y))
  797. }
  798. /**
  799. * 获取pos应该对应的node
  800. * @param {节点} nodes
  801. * @param {世界坐标} position
  802. */
  803. findNodeByPosition(nodes, position) {
  804. for (let iNode of nodes) {
  805. if (this.containsP(iNode, position) && iNode.active) {
  806. return iNode
  807. }
  808. }
  809. return null
  810. }
  811. setComponentByObj(component, obj) {
  812. for (let key in obj) {
  813. component[key] = obj[key]
  814. }
  815. }
  816. isEditorScene() {
  817. return cc.director.getScene().name == 'Editor'
  818. }
  819. drawLine(graphics, touches) {
  820. // 最小的移动距离
  821. const MIN_POINT_DISTANCE = 1
  822. // 从0开始移动1
  823. graphics.moveTo(touches[0].x, touches[0].y)
  824. // 从1开始移动n
  825. let lastIndex = 0
  826. for (let i = 1, l = touches.length; i < l; i++) {
  827. // 向量减法,并返回新结果
  828. if (touches[i].sub(touches[lastIndex]).mag() < MIN_POINT_DISTANCE) {
  829. continue
  830. }
  831. lastIndex = i
  832. // 画线
  833. graphics.lineTo(touches[i].x, touches[i].y)
  834. }
  835. graphics.stroke()
  836. }
  837. addBtnClickEvent(btnNode, fucName, target, componentName) {
  838. if (btnNode) {
  839. let button = btnNode.getComponent(cc.Button)
  840. if (button && button.clickEvents.length == 0) {
  841. let clickEventHandler = new cc.Component.EventHandler()
  842. clickEventHandler.target = target
  843. clickEventHandler.component = componentName
  844. clickEventHandler.handler = fucName
  845. button.clickEvents.push(clickEventHandler)
  846. }
  847. }
  848. }
  849. instantChildren(origin, num, parent?): cc.Node[] {
  850. let nodes = []
  851. if (!parent) parent = origin.parent
  852. for (let i = 0; i < num; i++) {
  853. let node = parent.children[i]
  854. if (!node) {
  855. node = cc.instantiate(origin)
  856. node.parent = parent
  857. }
  858. node.active = true
  859. nodes.push(node)
  860. }
  861. for (let i = num; i < parent.children.length; i++) {
  862. let node = parent.children[i]
  863. if (node) {
  864. node.active = false
  865. }
  866. }
  867. return nodes
  868. }
  869. getCirclePoints(r, pos, count, randomScope) {
  870. let points = []
  871. let radians = (Math.PI / 180) * Math.round(360 / count)
  872. for (let i = 0; i < count; i++) {
  873. let x = pos.x + r * Math.sin(radians * i)
  874. let y = pos.y + r * Math.cos(radians * i)
  875. points.unshift(cc.v3(x + (Math.random() - 0.5) * randomScope, y + (Math.random() - 0.5) * randomScope, 0))
  876. }
  877. return points
  878. }
  879. circleFlyNodes(
  880. nodes: cc.Node[],
  881. parent: cc.Node,
  882. startPos: cc.Vec3,
  883. toPos: cc.Vec3,
  884. nodeFinishCb: Function,
  885. finishCb: Function,
  886. ) {
  887. let circlePoints = this.getCirclePoints(60, startPos, nodes.length, 40)
  888. circlePoints.forEach((value, index) => {
  889. let tmp = nodes[index]
  890. tmp.parent = parent
  891. tmp.zIndex = 9999
  892. tmp.active = true
  893. tmp.position = startPos
  894. tmp.scale = 0.5
  895. let bezier = []
  896. for (let i = 0; i < 3; i++) {
  897. if (i == 2) {
  898. bezier.push(toPos)
  899. } else {
  900. bezier.push(cc.v2(Math.randomRangeInt(-400, 400), Math.randomRangeInt(-200, 200)))
  901. }
  902. }
  903. let action = cc.sequence(
  904. cc.moveTo(0.6, value),
  905. cc.delayTime(index * 0.03),
  906. cc.bezierTo(1, bezier).easing(cc.easeIn(3.0)),
  907. cc.callFunc(() => {
  908. if (cc.isValid(tmp)) {
  909. tmp.parent = null
  910. if (nodeFinishCb) {
  911. nodeFinishCb(tmp)
  912. } else {
  913. tmp.destroy()
  914. }
  915. }
  916. if (index == circlePoints.length - 1) {
  917. finishCb && finishCb()
  918. }
  919. }),
  920. )
  921. cc.tween(tmp).then(action).start()
  922. })
  923. }
  924. /**
  925. * 屏蔽多点触摸
  926. */
  927. private canTouch = true
  928. public cannotTouch() {
  929. if (this.canTouch) {
  930. this.canTouch = false
  931. setTimeout(() => {
  932. this.canTouch = true
  933. }, 100)
  934. return false
  935. }
  936. return true
  937. }
  938. editorLoadSync(url) {
  939. if (!CC_EDITOR) return
  940. let uuid = Editor.assetdb.remote.urlToUuid('db://assets/' + url)
  941. return new Promise((resolve: (asset) => void, reject) => {
  942. cc.assetManager.loadAny({uuid}, (err, asset) => {
  943. if (err) {
  944. resolve(null)
  945. } else {
  946. resolve(asset)
  947. }
  948. })
  949. })
  950. }
  951. editorLoadSprite(url, node, childUrl?: string) {
  952. if (!CC_EDITOR) return
  953. ccUtils.editorLoadSync(url + '.png' + url.substring(url.lastIndexOf('/'))).then(spriteFrame => {
  954. if (childUrl) {
  955. node = cc.find(childUrl, node)
  956. }
  957. let sprite = node.getComponent(cc.Sprite)
  958. if (sprite) sprite.spriteFrame = spriteFrame
  959. })
  960. }
  961. setPos(node, position: cc.Vec2) {
  962. node.x = position.x
  963. node.y = position.y
  964. }
  965. getPos(node) {
  966. return cc.v2(node.x, node.y)
  967. }
  968. resetAniNode(node, pos?: cc.Vec2) {
  969. node.opacity = 255
  970. node.angle = 0
  971. node.stopAllActions()
  972. if (pos) this.setPos(node, pos)
  973. }
  974. deepCopy(obj: any): any {
  975. const newobj: any = Array.isArray(obj) ? [] : {}
  976. for (const arr in obj) {
  977. if (typeof obj[arr] === 'object' && obj[arr] !== null) {
  978. newobj[arr] = this.deepCopy(obj[arr])
  979. } else {
  980. newobj[arr] = obj[arr]
  981. }
  982. }
  983. return newobj
  984. }
  985. getPosAngle(x1, y1, x2, y2) {
  986. // 直角的边长
  987. let x = Math.abs(x1 - x2)
  988. let y = Math.abs(y1 - y2)
  989. let z = Math.sqrt(x * x + y * y)
  990. let angle = Math.round((Math.asin(y / z) / Math.PI) * 180)
  991. if (y2 > y1 && x2 < x1) {
  992. angle = 180 - angle
  993. } else if (y2 < y1 && x2 < x1) {
  994. angle = 180 + angle
  995. } else if (y2 < y1 && x2 > x1) {
  996. angle = 360 - angle
  997. } else if (y2 == y1 && x2 > x1) {
  998. angle = 0
  999. } else if (y2 == y1 && x2 < x1) {
  1000. angle = 180
  1001. } else if (y2 > y1 && x2 == x1) {
  1002. angle = 90
  1003. } else if (y2 < y1 && x2 == x1) {
  1004. angle = 270
  1005. }
  1006. return angle
  1007. }
  1008. tweenFloat(
  1009. from: number,
  1010. to: number,
  1011. duration: number,
  1012. onUpdate: (t: number) => void,
  1013. onComplete?: Function,
  1014. autoStart: boolean = true,
  1015. ) {
  1016. let o: Record<string, number> = {_value: from}
  1017. Object.defineProperty(o, 'value', {
  1018. get: () => o._value,
  1019. set: (v: number) => {
  1020. o._value = v
  1021. onUpdate && onUpdate(o._value)
  1022. },
  1023. })
  1024. let tween = cc.tween(o).to(duration, {value: to}).call(onComplete)
  1025. if (autoStart) {
  1026. tween.start()
  1027. }
  1028. return tween
  1029. }
  1030. generateAttackPoints(r, n, isLeft, repeat) {
  1031. // 计算每个角度的间隔
  1032. let angleStep = 90 / n
  1033. // 存储点的坐标
  1034. let points = []
  1035. // 计算每个点的坐标
  1036. for (let i = -45; i <= 45; i += angleStep) {
  1037. // 将角度转换为弧度
  1038. let radians = (i * Math.PI) / 180
  1039. // 计算当前点的 x 和 y 坐标
  1040. let x = +(r * Math.cos(radians)).toFixed(1)
  1041. let y = +(r * Math.sin(radians)).toFixed(1)
  1042. x = isLeft ? -x : x
  1043. // 将点的坐标添加到数组中
  1044. points.push({x, y})
  1045. }
  1046. points.sort((a, b) => {
  1047. if (isLeft) {
  1048. return a.x - b.x
  1049. } else {
  1050. return b.x - a.x
  1051. }
  1052. })
  1053. for (let i = 0; i < repeat; i++) {
  1054. points = points.concat(points)
  1055. }
  1056. return points
  1057. }
  1058. isEmpty(value: any): boolean {
  1059. if (value === null || value === undefined) {
  1060. return true
  1061. }
  1062. // 判断是否是字符串
  1063. if (typeof value === 'string' && value.trim() === '') {
  1064. return true
  1065. }
  1066. // 判断是否是数组或对象,长度为 0 则为空
  1067. if (Array.isArray(value) && value.length === 0) {
  1068. return true
  1069. }
  1070. if (typeof value === 'object' && Object.keys(value).length === 0) {
  1071. return true
  1072. }
  1073. return false
  1074. }
  1075. //初始化进度条
  1076. initProgressBar(node: cc.Node, progress: number, needSum: number, childUrl?: string) {
  1077. this.setProgress(progress / needSum, node)
  1078. //进度条标有进度
  1079. if (childUrl != null) {
  1080. ccUtils.setLabel(progress + '/' + needSum, node, childUrl)
  1081. }
  1082. }
  1083. getAllUrlParams(url) {
  1084. // 获取 URL 中的查询参数部分
  1085. let queryString = url.split('?')[1]
  1086. // 定义一个对象来存储参数
  1087. let params = {}
  1088. if (!queryString) return params
  1089. // 分割查询参数,以 '&' 符号分隔
  1090. let queryParams = queryString.split('&')
  1091. // 遍历每个参数,并将其解析到 params 对象中
  1092. queryParams.forEach(param => {
  1093. // 分割键值对,以 '=' 符号分隔
  1094. let keyValue = param.split('=')
  1095. let key = decodeURIComponent(keyValue[0]) // 解码键
  1096. let value = decodeURIComponent(keyValue[1] || '') // 解码值(默认为空字符串)
  1097. // 如果 params 对象中已经存在相同的键,则将值转换为数组
  1098. if (params[key]) {
  1099. if (Array.isArray(params[key])) {
  1100. params[key].push(value)
  1101. } else {
  1102. params[key] = [params[key], value]
  1103. }
  1104. } else {
  1105. params[key] = value
  1106. }
  1107. })
  1108. return params
  1109. }
  1110. //显示呼吸灯效果
  1111. showEffectsBLN(isShow: boolean, node: cc.Node, childUrl?: string, time?: number) {
  1112. if (childUrl) {
  1113. node = cc.find(childUrl, node)
  1114. }
  1115. let hz = 0.75
  1116. if (time) hz = time
  1117. if (!node) return
  1118. node.active = true
  1119. if (node['effect'] == isShow) return
  1120. node['effect'] = isShow
  1121. let t = cc.tween
  1122. node.opacity = 0
  1123. node.stopAllActions()
  1124. if (!isShow) {
  1125. node.opacity = 0
  1126. return
  1127. }
  1128. let showEffect = (tmpNode: cc.Node) => {
  1129. t(tmpNode)
  1130. .then(t().to(hz, {opacity: 255}))
  1131. .call(() => {
  1132. isShow ? hideEffect(tmpNode) : (node.opacity = 0)
  1133. })
  1134. .start()
  1135. }
  1136. let hideEffect = (tmpNode: cc.Node) => {
  1137. t(tmpNode)
  1138. .then(t().to(hz, {opacity: 0}))
  1139. .call(() => {
  1140. isShow ? showEffect(tmpNode) : (node.opacity = 0)
  1141. })
  1142. .start()
  1143. }
  1144. showEffect(node)
  1145. }
  1146. }
  1147. export const ccUtils = new CCUtils()