UICommon.ts 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315
  1. import Gamecfg from "../common/gameCfg";
  2. import { gameMethod } from "../common/gameMethod";
  3. import { KindItem, OrderList } from "../common/Xys";
  4. import Config from "../Config";
  5. import { ChatEvent } from "../data/const/EventConst";
  6. import { AudioConst } from "../data/const/TypeConst";
  7. import GameDataCenter from "../data/GameDataCenter";
  8. import RollLabel, { RollLabelParam } from "../frameWork/compment/RollLabel";
  9. import UIHelp from "../logic/ui/UIHelp";
  10. import { FormulaCom } from "./Formula";
  11. import GameMath from "./GameMath";
  12. import { I18n } from "./I18nUtil";
  13. import ColorAssembler2D from "./LabelColor";
  14. import Load from "./Load";
  15. export class BezierData {
  16. startPos: cc.Vec2;
  17. c1: cc.Vec2; // 起点的控制点
  18. c2: cc.Vec2; //终点的控制点
  19. endPos: cc.Vec2;
  20. }
  21. class UICommon {
  22. // 初始化
  23. init() { }
  24. setIconByKindItem(node: cc.Node, item: number[], cb?: (isSucc: boolean) => void) {
  25. }
  26. // 给特定的节点加新增一个图片节点
  27. setNodeChild(node: cc.Node, url: string, x: number = 0, y: number = 0, scale: number = 1) {
  28. if (node.getChildByName("newImgTemp")) {
  29. node.getChildByName("newImgTemp").active = true
  30. return
  31. }
  32. let child = new cc.Node()
  33. child.parent = node
  34. child.name = "newImgTemp"
  35. child.x = x
  36. child.y = y
  37. child.scale = scale
  38. Load.loadTexture(child, url)
  39. }
  40. /**设置label或富文本的文字 */
  41. setLabel(node: cc.Node, value: string | number) {
  42. if (typeof value === 'number') {
  43. value = value.toString();
  44. } else if (value == undefined) {
  45. value = "";
  46. }
  47. // 文本和富文本只能二选一
  48. if (node.getComponent(cc.RichText)) {
  49. let defaultColor = "#9F90A9"//node.color.toHEX('#rrggbb');
  50. node.getComponent(cc.RichText).string = `<color=${defaultColor}>${value}</c>`;
  51. } else {
  52. node.getComponent(cc.Label).string = value;
  53. }
  54. }
  55. setLibaoLabel(node: cc.Node, orderinfo: OrderList) {
  56. this.setLabel(node, orderinfo?.desc)
  57. }
  58. //获取价格标题
  59. getLibaoLabel(orderinfo: OrderList) {
  60. return orderinfo?.desc
  61. }
  62. setI18nLangLabel(node: cc.Node, key: string, ...args) {
  63. let str = I18n.getI18nLang(key, ...args)
  64. this.setLabel(node, str)
  65. }
  66. setI18nTextLabel(node: cc.Node, key: string, ...args: any[]) {
  67. let str = I18n.getI18nText(key, ...args)
  68. this.setLabel(node, str)
  69. }
  70. /**设置图片富文本,仅支持道具,传入itemList为道具ID,如 [1,2] */
  71. getImageStr(str: string, itemList: KindItem[] | any[][]): string {
  72. if (itemList.length == 0) {
  73. return str
  74. }
  75. // itemList.forEach((element, itemIndex) => {
  76. // let itemCfg = GameDataCenter.item.getItemCfgBase(element)
  77. // if (itemCfg) {
  78. // str = str.replace(`{${itemIndex}}`, `<img src='${itemCfg.icon}'/>`)
  79. // }
  80. // });
  81. return str
  82. }
  83. // /**设置图片富文本,仅支持道具,传入imageList为图片资源ID,如 [1,2] @TODO 是否能够修改图片尺寸*/
  84. // setImageRt(node: cc.Node, str: string, imageList: number[] | string[]) {
  85. // if (imageList.length == 0) {
  86. // this.setLabel(node, str)
  87. // return
  88. // }
  89. // let index = 0
  90. // imageList.forEach(element => {
  91. // Load.getTexture(`item/${element}`, (succ: boolean, asset: cc.SpriteFrame) => {
  92. // index++
  93. // console.log("erqasdasasdasd", index, asset)
  94. // if (succ) {
  95. // node.getComponent(cc.RichText).imageAtlas["_spriteFrames"][element] = asset
  96. // }
  97. // if (index == imageList.length) {
  98. // console.log("erqasdasasdasd33")
  99. // imageList.forEach((element, imgIndex) => {
  100. // // <img src='emoji1'/>
  101. // str = str.replace(`{${imgIndex}}`, `<img src='${element}'/>`)
  102. // });
  103. // node.getComponent(cc.RichText).string = str
  104. // }
  105. // })
  106. // });
  107. // }
  108. /**图片、文字 去色,0原色 1去色,递归 */
  109. setState(node: cc.Node, state: 0 | 1, all: boolean = false) {
  110. let url = state == 0 ? "2d-sprite" : "2d-gray-sprite"
  111. if (all) {
  112. let sprites = node.getComponentsInChildren(cc.Sprite)
  113. let labels = node.getComponentsInChildren(cc.Label)
  114. sprites.forEach(sprite => {
  115. sprite.setMaterial(0, cc.Material.getBuiltinMaterial(url))
  116. })
  117. labels.forEach(label => {
  118. label.setMaterial(0, cc.Material.getBuiltinMaterial(url))
  119. })
  120. } else {
  121. if (node.getComponent(cc.Sprite) != null) {
  122. node.getComponent(cc.Sprite).setMaterial(0, cc.Material.getBuiltinMaterial(url))
  123. }
  124. if (node.getComponent(cc.Label) != null) {
  125. node.getComponent(cc.Label).setMaterial(0, cc.Material.getBuiltinMaterial(url))
  126. }
  127. }
  128. }
  129. /** spine动画置灰 */
  130. setSpineState(node: cc.Node, isGrayed: boolean) {
  131. if (node.getComponent(sp.Skeleton) != null) {
  132. if (isGrayed) {
  133. cc.assetManager.loadBundle('materials', (err, bundle: cc.AssetManager.Bundle) => {
  134. bundle.load('gray-spine', cc.Material, (err, asset) => {
  135. node.getComponent(sp.Skeleton).setMaterial(0, asset);
  136. });
  137. });
  138. } else {
  139. node.getComponent(sp.Skeleton).setMaterial(0, cc.Material.getBuiltinMaterial("2d-spine"));
  140. }
  141. };
  142. }
  143. /**按钮灰化,只有注册click事件,才会真正被禁用 */
  144. setBtnGrayState(node: cc.Node, isGray: boolean) {
  145. let button = node.getComponent(cc.Button);
  146. if (!button) {
  147. return;
  148. }
  149. button.interactable = !isGray;
  150. button.enableAutoGrayEffect = isGray;
  151. }
  152. /**判断按钮是否为失效状态 */
  153. isBtnGray(node: cc.Node): boolean {
  154. let button = node.getComponent(cc.Button);
  155. if (!button) {
  156. return false;
  157. }
  158. return !button.interactable;
  159. }
  160. /**节点注册事件 */
  161. onRegisterEvent(node: cc.Node, callback, target, params: any = [], audio: AudioConst = AudioConst.effect_click) {
  162. if (!node) {
  163. return;
  164. }
  165. let cb = callback
  166. if (callback == null) { return }
  167. callback = function (event: cc.Event.EventTouch) {
  168. // 判断下是否有按钮组件,有的话,如果按钮是未激活状态,则不执行回调方法
  169. let btn = node.getComponent(cc.Button)
  170. if (btn && btn.interactable == false) {
  171. return
  172. }
  173. cb.bind(target)(event, params)
  174. GameDataCenter.audio.playEffect(audio)
  175. event.stopPropagation()
  176. }
  177. node.on(cc.Node.EventType.TOUCH_END, callback, target)
  178. }
  179. /**节点取消注册事件 */
  180. unRegisterEvent(node: cc.Node) {
  181. if (!node) { return }
  182. node.off(cc.Node.EventType.TOUCH_END);
  183. }
  184. /**获取节点的世界坐标 */
  185. getWorldPos(node: cc.Node): cc.Vec2 {
  186. return node.convertToWorldSpaceAR(cc.Vec2.ZERO)
  187. }
  188. getWorldPosCenter(node: cc.Node): cc.Vec2 {
  189. let vec = new cc.Vec2(node.width * (0.5 - node.anchorX), node.height * (0.5 - node.anchorY))
  190. return node.convertToWorldSpaceAR(vec)
  191. }
  192. /**设置节点在世界坐标系下的相对坐标 */
  193. setWorldPos(node: cc.Node, pos: cc.Vec2) {
  194. let _pos = node.parent.convertToNodeSpaceAR(pos)
  195. node.x = _pos.x
  196. node.y = _pos.y
  197. }
  198. /**获取node位于target的坐标相对坐标 */
  199. getPositionInView(node: cc.Node, target: cc.Node): { x: number, y: number } {
  200. let worldPos = node.parent.convertToWorldSpaceAR(node.position);
  201. let viewPos = target.convertToNodeSpaceAR(worldPos);
  202. return viewPos;
  203. }
  204. /**通过name从根节点递归获取node */
  205. getTargetNodeByName(root: cc.Node, name: string) {
  206. if (root.name == name) {
  207. return root
  208. }
  209. for (let i = 0; i < root.children.length; i++) {
  210. const child = root.children[i];
  211. let targetNode = this.getTargetNodeByName(child, name)
  212. if (targetNode != null) {
  213. return targetNode
  214. }
  215. }
  216. return null
  217. }
  218. // 递归所有子节点并且更新节点分组
  219. setNodeAllGroups(node: cc.Node, group: string) {
  220. // 定义一个回调函数,这个函数将对每个节点执行操作
  221. let myCallback = function (node) {
  222. // 在这里处理节点
  223. node.group = group
  224. };
  225. // 开始递归遍历
  226. this.recursiveTraverseChildren(node, myCallback);
  227. }
  228. // 递归遍历所有子节点
  229. recursiveTraverseChildren(node: cc.Node, callback: Function) {
  230. // 对当前节点执行回调函数
  231. callback(node);
  232. // 遍历当前节点的所有子节点
  233. for (let i = 0; i < node.children.length; i++) {
  234. // 递归遍历子节点
  235. this.recursiveTraverseChildren(node.children[i], callback);
  236. }
  237. }
  238. // 安卓设备的适配在主activity中执行
  239. // ios设备,需要在游戏中修改widget
  240. // 返回是否为刘海,以及刘海高度
  241. isLiuhai(): [boolean, number] {
  242. let isLiuhai = false
  243. let height = this.getLiuhaiHeight()
  244. if (cc.sys.os == cc.sys.OS_IOS) {
  245. let _isLiuhai = jsb.reflection.callStaticMethod('SDKWrapper', 'isLiuhai:', '')
  246. isLiuhai = _isLiuhai == "1"
  247. } else if (cc.sys.os == cc.sys.OS_ANDROID) {
  248. let _isLiuhai = jsb.reflection.callStaticMethod("org/cocos2dx/javascript/AppActivity", "getIsLiuhai", "()Ljava/lang/String;");
  249. isLiuhai = _isLiuhai == "1"
  250. }
  251. return [isLiuhai, height]
  252. }
  253. /**获取屏幕高度 */
  254. getWinHeight() {
  255. return cc.winSize.height //- this.getLiuhaiHeight()
  256. }
  257. getLiuhaiHeight(): number {
  258. let height = 0
  259. try {
  260. if (cc.sys.os == cc.sys.OS_IOS) {
  261. let _height = jsb.reflection.callStaticMethod('SDKWrapper', 'getLiuhaiHeight:', '');
  262. height = Number(_height)
  263. } else if (cc.sys.os == cc.sys.OS_ANDROID) {
  264. height = jsb.reflection.callStaticMethod("org/cocos2dx/javascript/AppActivity", "getLiuhaiHeight", "()I");
  265. }
  266. // return height
  267. } catch (error) {
  268. // return height
  269. }
  270. return height
  271. }
  272. /**获取适配scale */
  273. getWidghtScale(height: number = cc.winSize.height): number {
  274. let defaultBili = 1920 / 1080
  275. let realBili = height / cc.winSize.width //高宽比
  276. return 1 + (realBili - defaultBili)
  277. }
  278. // //将字符转为表情
  279. // TransformationStr(text) {
  280. // let richText = "";
  281. // var arr = text.split("#");
  282. // for (var i = 0; i < arr.length; i++) {
  283. // if (i >= 1) {
  284. // let str = arr[i].slice(0, 5)
  285. // let str1 = arr[i].slice(5)
  286. // if (gameMethod.isEmpty(Load.getEmoji(str))) {
  287. // richText += `#${str + str1}`
  288. // } else {
  289. // richText += `<img src='${str}'/>${str1}`
  290. // }
  291. // } else {
  292. // richText += arr[i]
  293. // }
  294. // }
  295. // return richText
  296. // }
  297. /**原表富文本模式转换成前端富文本模式*/
  298. getConversionStr(text: string) {
  299. let tempStr = ''
  300. let str = ''
  301. let arr = text.split('');
  302. let isColor: boolean = false
  303. let isValue: boolean = true
  304. for (let i = 0; i < arr.length; i++) {
  305. if (arr[i] == '<') {
  306. tempStr = ''
  307. isColor = true
  308. isValue = false
  309. }
  310. if (isColor) {
  311. tempStr += arr[i]
  312. }
  313. if (isValue) {
  314. str += arr[i]
  315. }
  316. if (tempStr == '<div fontcolor=') {
  317. tempStr = '<color=#'
  318. str += tempStr
  319. isColor = false
  320. isValue = true
  321. }
  322. if (tempStr == '</div>') {
  323. tempStr = '</color>'
  324. str += tempStr
  325. isColor = false
  326. isValue = true
  327. }
  328. }
  329. return str
  330. }
  331. /**原表成前端*/
  332. getTypeStr(text: string) {
  333. let str = text.replace(/<div fontcolor=#/g, '<color=#')
  334. .replace(/<div fontcolor=/g, '<color=#')
  335. .replace(/<\/div>/g, '</color>')
  336. return str
  337. }
  338. // getEpsStrArr(eps: XyS.Eps | EpsPet) {
  339. // let bak: {
  340. // ek: string,
  341. // num: number,
  342. // ekShow: string, //名称
  343. // numShow: number, //展示数值
  344. // suffix: string, //后缀
  345. // }[] = []
  346. // //遍历配置
  347. // for (const key in eps) {
  348. // let arrt = gameCfg.attr.pool[`${key}_`]
  349. // if (!gameMethod.isEmpty(arrt)) {
  350. // switch (arrt.tip) {
  351. // case 'atk'://攻击 角色的物理攻击能力
  352. // case 'def_p'://物防 角色抵抗物理攻击的能力
  353. // case 'def_s'://法防 角色抵抗法术攻击的能力
  354. // case 'hp_max'://生命 角色的生命上限值
  355. // case 'hp'://生命 角色当前的生命值
  356. // case 'speed'://速度 决定单位在战斗中的出手频率
  357. // case 'def'://防御 角色抵抗物理和法术攻击的能力
  358. // //数值直接展示
  359. // bak.push({
  360. // ek: arrt.tip,
  361. // num: eps[arrt.tip],
  362. // ekShow: arrt.name, //名称
  363. // numShow: eps[arrt.tip], //展示数值
  364. // suffix: '%', //后缀
  365. // })
  366. // break;
  367. // case 'hit_rate'://命中 成功造成伤害的概率
  368. // case 'dodge_rate'://闪躲 躲避伤害的概率
  369. // case 'crit_rate'://暴击率 攻击方产生暴击的概率
  370. // case 'crit_ratio'://暴伤 暴击产生时,伤害的变化比例
  371. // case 'hit_magic'://控制 增强技能效果命中的比例
  372. // case 'dodge_magic'://抗控 抵抗技能效果命中的比例
  373. // case 'tenacity'://抗暴 受击方被暴击概率降低
  374. // case 'atk_per'://攻击 百分比增加单位的攻击力
  375. // case 'def_per'://防御 百分比增加单位的物理防御和法术防御
  376. // case 'hp_max_per'://生命 百分比增加单位的生命上限
  377. // case 'dam'://伤害加成 千分比增加造成的伤害
  378. // case 'res'://免伤 千分比减少受到的伤害
  379. // case 'cure'://治疗 千分比增减治疗别人的效果
  380. // case 'be_cure'://受疗 千分比增减受到的治疗效果
  381. // case 'dam_p'://物伤 千分比增加造成的物理伤害
  382. // case 'dam_s'://法伤 千分比增加造成的法术伤害
  383. // case 'res_p'://物免 千分比减少受到的物理伤害
  384. // case 'res_s'://法免 千分比减少受到的法术伤害
  385. // case 'speed_per'://速度 决定单位在战斗中的出手频率
  386. // case 'def_p_per'://物防 千分比增加单位的物理防御
  387. // case 'def_s_per'://法防 千分比增加单位的法术防御
  388. // case 'toughness'://韧性 千分比减免持续伤害类负面效果带来的伤害
  389. // //数值百分比加成
  390. // bak.push({
  391. // ek: arrt.tip,
  392. // num: eps[arrt.tip],
  393. // ekShow: arrt.name, //名称
  394. // numShow: eps[arrt.tip] / 10, //展示数值
  395. // suffix: '%', //后缀
  396. // })
  397. // break;
  398. // }
  399. // }
  400. // }
  401. // return bak
  402. // }
  403. /** 打字效果 */
  404. typingAni(label: cc.Label | cc.RichText, text: string, time: number = 1 / 60, cb: Function = () => { }) {
  405. if (!text || text.length <= 0) {
  406. cb()
  407. return
  408. }
  409. let html = '';
  410. let tempStr = ''
  411. let tempColorStr = ''
  412. let str: string[] = []
  413. let arr = text.split('');
  414. let len = arr.length;
  415. let step = 0;
  416. let isColor: boolean = false
  417. let isValue: boolean = false
  418. if (label instanceof cc.RichText) {
  419. for (let i = 0; i < len; i++) {
  420. if (arr[i] == '<') {
  421. tempColorStr = ''
  422. isValue = false
  423. }
  424. if (isValue) {
  425. tempStr = `${tempColorStr}${arr[i]}</color>`
  426. str.push(tempStr)
  427. tempStr = ''
  428. }
  429. if (arr[i] == '#') {
  430. isColor = true
  431. }
  432. if (arr[i] == '>' && tempColorStr != '') {
  433. isColor = false
  434. isValue = true
  435. tempColorStr = `<color=${tempColorStr}>`
  436. }
  437. if (isColor) {
  438. tempColorStr += arr[i]
  439. }
  440. }
  441. arr = str
  442. len = str.length
  443. }
  444. let typingFunc = () => {
  445. if (step >= len) {
  446. label.unschedule(typingFunc);
  447. cb && cb();
  448. } else {
  449. html += arr[step];
  450. label.string = html
  451. }
  452. step++
  453. }
  454. label.schedule(typingFunc, time, len)
  455. }
  456. /** 富文本打字效果 */
  457. typingRich(label: cc.RichText, text: string, wordsNum: number, time: number = 1 / 60, cb: Function = () => { }) {
  458. if (!text || text.length <= 0) {
  459. cb()
  460. return
  461. }
  462. let str = text;
  463. let charArr = str.replace(/<.+?\/?>/g, '').split('');
  464. let tempStrArr = [str],
  465. curstr = str;
  466. for (let i = charArr.length; i > 1; i--) {
  467. let lastIdx = curstr.lastIndexOf(charArr[i - 1]);
  468. let prevstr = curstr.slice(0, lastIdx);
  469. let nextstr = curstr.slice(lastIdx + 1, curstr.length);
  470. if ((i - 1) % wordsNum == 0) tempStrArr.push(prevstr + nextstr);
  471. curstr = prevstr + nextstr
  472. }
  473. let step = 0,
  474. len = tempStrArr.length;
  475. let typingFunc = () => {
  476. if (step >= len) {
  477. label.unschedule(typingFunc);
  478. cb && cb();
  479. } else {
  480. label.string = tempStrArr.pop()
  481. }
  482. step++
  483. }
  484. label.schedule(typingFunc, time, len)
  485. typingFunc()
  486. }
  487. // /**
  488. // * @description:给需要点击回调的RichText添加回调脚本
  489. // * @param {type} node 包含RichText组件的Node
  490. // * @param {type} component 需要处理点击事件的脚本
  491. // * @return {type}
  492. // */
  493. // createRichTextCallback(node, component) {
  494. // if (gameMethod.isEmpty(node)) {
  495. // console.log("node must be cc.Node!!!");
  496. // return;
  497. // }
  498. // let richText = node.getComponent(cc.RichText);
  499. // if (gameMethod.isEmpty(richText)) {
  500. // console.log("node must have RichText component!!!");
  501. // return;
  502. // }
  503. // let st = node.addComponent(RichTextEvent);
  504. // st.setObject(component);
  505. // }
  506. // /**
  507. // * @description:获取技能buff描述
  508. // * @param {type} buffId 需要处理点击事件的脚本
  509. // * @return {type}
  510. // */
  511. // getBuffDesc(buffId: string, withTitle: boolean = true): string {
  512. // let conf = gameCfg.skill_all_buff.getItem(buffId)
  513. // if (conf == null) {
  514. // console.log(" buff info is not exist!!!");
  515. // return ""
  516. // }
  517. // if (withTitle) {
  518. // return `<color=#24B6CB>[${conf.name}]</c>\n${conf.desc}`
  519. // }
  520. // return conf.desc
  521. // }
  522. //给 spine 重新拷贝一份 skeletonData 数据,让他们不重复
  523. public copySpine(skeleton: sp.Skeleton) {
  524. let date = new Date();
  525. // 记录当前播放的动画
  526. const animation = skeleton.animation
  527. const spdata = skeleton.skeletonData;
  528. let copy = new sp.SkeletonData();
  529. cc.js.mixin(copy, spdata);
  530. // @ts-ignore
  531. copy._uuid = spdata._uuid + "_" + date.getTime() + "_copy";
  532. let old = copy.name;
  533. let newName = copy.name + "_copy";
  534. copy.name = newName;
  535. copy.atlasText = copy.atlasText.replace(old, newName);
  536. // @ts-ignore
  537. copy.textureNames[0] = newName + ".png";
  538. // @ts-ignore
  539. copy.init && copy.init();
  540. skeleton.skeletonData = copy;
  541. // 继续播放的动画,不然会停止
  542. skeleton.setAnimation(0, animation, true);
  543. }
  544. /**
  545. * 用外部图片局部换装
  546. * @param skeleton 骨骼动画
  547. * @param slotName 需要替换的插槽名称
  548. * @param texture 外部图片
  549. */
  550. public changeSlot(skeleton: sp.Skeleton, slotName: string, texture: cc.Texture2D) {
  551. if (cc.sys.isBrowser) {
  552. const slot: sp.spine.Slot = skeleton.findSlot(slotName);
  553. const attachment: sp.spine.RegionAttachment = slot.getAttachment() as sp.spine.RegionAttachment;
  554. if (!slot || !attachment) {
  555. cc.error('updatePartialSkin')
  556. return;
  557. }
  558. // @ts-ignore
  559. const skeletonTexture = new sp.SkeletonTexture({});
  560. skeletonTexture.setRealTexture(texture);
  561. let region: sp.spine.TextureAtlasRegion = attachment.region as sp.spine.TextureAtlasRegion;
  562. region.u = 0;
  563. region.v = 0;
  564. region.u2 = 1;
  565. region.v2 = 1;
  566. region.width = texture.width * 5;
  567. region.height = texture.height * 5;
  568. region.originalWidth = texture.width * 5;
  569. region.originalHeight = texture.height * 5;
  570. region.rotate = false;
  571. region.texture = skeletonTexture;
  572. attachment.width = texture.width * 5;
  573. attachment.height = texture.height * 5;
  574. attachment.region = region;
  575. attachment.setRegion && attachment.setRegion(region)
  576. attachment.updateOffset && attachment.updateOffset();
  577. // attachment.updateUVs && attachment.updateUVs(attachment);
  578. slot.setAttachment(attachment);
  579. } else {
  580. // @ts-ignore
  581. const jsbTex = new middleware.Texture2D();
  582. jsbTex.setPixelsHigh(texture.height * 5);
  583. jsbTex.setPixelsWide(texture.width * 5);
  584. jsbTex.setNativeTexture(texture.getImpl());
  585. // @ts-ignore
  586. skeleton.updateRegion(slotName, jsbTex);
  587. }
  588. // skeleton 如果使用了缓存模式则需要刷新缓存
  589. skeleton.invalidAnimationCache();
  590. }
  591. // 移除所有子节点
  592. destoryAllChildren(node: cc.Node) {
  593. node.children.forEach(child => {
  594. child.destroy()
  595. })
  596. }
  597. // 根据品质获取颜色
  598. getColorByPz(pinzhi: number | string) {
  599. // 品质1·8的,9瑶光的自己拼
  600. switch (Number(pinzhi)) {
  601. case 1:
  602. return '#B3B3B3'
  603. case 2:
  604. return '#81B58B'
  605. case 3:
  606. return '#7CACCF'
  607. case 4:
  608. return '#A77CC5'
  609. case 5:
  610. return '#E6A76A'
  611. case 6:
  612. return '#E7C45A'
  613. case 7:
  614. return '#D45F59'
  615. case 8:
  616. return '#76DEE4'
  617. case 9:
  618. return '#EB91F0'
  619. case 10:
  620. return '#FF66D9'
  621. case 11:
  622. return '#98F22E'
  623. case 12:
  624. return '#FFCC19'
  625. default:
  626. return '#FFFFFF'
  627. }
  628. }
  629. // // 根据品质获取颜色
  630. // setColorTxtByPz(txt:string,pinzhi: number | string) {
  631. // let color = this.getColorByPz(pinzhi);
  632. // let str = `[color=${color}]`
  633. // }
  634. // 根据品质获取名称
  635. getNameByPz(pinzhi: number | string) {
  636. return I18n.getI18nLang(`zhenfa_pinzhi_name_${pinzhi}`);
  637. }
  638. getEnoughColor(isenough: boolean) {
  639. return isenough ? "6fdf89" : "E45849"
  640. }
  641. // 修正文字布局
  642. fixName(msg: string): string {
  643. // if (msg.length >= 4) {
  644. // return msg
  645. // }
  646. // let out = ""
  647. // for (let index = 0; index < msg.length; index++) {
  648. // out += msg[index]
  649. // // 添加空格
  650. // if (index + 1 == msg.length) {
  651. // break
  652. // }
  653. // if (msg.length == 2) {
  654. // out += " "
  655. // } else if (msg.length == 3) {
  656. // out += " "
  657. // }
  658. // }
  659. // return out;
  660. return msg;
  661. }
  662. // cocos画布的节点位置信息转化为微信画布位置信息
  663. getWxBtnPos(node: fgui.GObject): { left: number, top: number, width: number, height: number } {
  664. let visibleSize = cc.view.getVisibleSize();
  665. // console.log("==visibleSize==", visibleSize.width, visibleSize.height)
  666. // console.log("==position==", node.node.position)
  667. // let worldPos = FormulaCom.getWorldPos(node.node)
  668. let worldPos = fgui.GRoot.inst.localToGlobal(node.x, node.y)
  669. // console.log("==fgui worldPos==", fgui.GRoot.inst.localToGlobal(node.x, node.y))
  670. // console.log("==cc worldPos==", FormulaCom.getWorldPos(node.node))
  671. //获取系统信息
  672. let wx_size = wx?.getSystemInfoSync();
  673. // console.log("==wx_size==", wx_size.screenWidth, wx_size.screenHeight)
  674. //计算实际大小和可见区域尺寸的比例(这里以宽度为准)
  675. let size_scale_width = wx_size.screenWidth / visibleSize.width;
  676. let size_scale_height = wx_size.screenHeight / Config.realHeight;
  677. //计算创建用户信息按钮需要的属性,考虑锚点
  678. let offsetX = node.width * node.scaleX * (node.node.anchorX / 1)
  679. let offsetY = node.height * node.scaleY * ((1 - node.node.anchorY) / 1)
  680. // console.log("==anchorX==", node.node.anchorX)
  681. // console.log("==anchorY==", node.node.anchorY)
  682. // console.log("==offsetX==", offsetX)
  683. // console.log("==offsetY==", offsetY)
  684. let left = (worldPos.x - offsetX) * size_scale_width // (this.ui.btnUser.x + visibleSize.width / 2 - this.ui.btnUser.width / 2) * size_scale_width;
  685. let top = (worldPos.y + node.height / 2) * size_scale_height //fgui
  686. // let top = wx_size.screenHeight - (worldPos.y - offsetY - Config.safeAreaRect.y / 2 - node.height / 2) * size_scale_height //cc
  687. let width = node.width * size_scale_width * node.scaleX
  688. let height = node.height * size_scale_height * node.scaleY
  689. // let y = (Math.abs(this.ui.btnUser.y) - this.ui.btnUser.height / 2) * size_scale_width;
  690. // let width = this.ui.btnUser.width * size_scale_width;
  691. // let height = this.ui.btnUser.height * size_scale_width;
  692. // console.log("==left==", left)
  693. // console.log("==top==", top)
  694. // console.log("==width==", width)
  695. // console.log("==height==", height)
  696. return { left: left, top: top, width: width, height: height }
  697. }
  698. btnShake(node: cc.Node) {
  699. let tween = cc.tween(node)
  700. .delay(2)
  701. .to(0.05, { angle: -15 })
  702. .to(0.1, { angle: 15 })
  703. .to(0.1, { angle: -15 })
  704. .to(0.1, { angle: 15 })
  705. .to(0.05, { angle: 0 })
  706. cc.tween(node).repeatForever(tween).start()
  707. }
  708. /**
  709. * 抛物线飞行 //开始坐标 //中间坐标(高度) //结束坐标
  710. * p0是起始点
  711. * p1是中点
  712. * p2是终点
  713. * p1通过p2和p0的Xaxis相减获得
  714. * height是p0加上的高度
  715. * t 的取值范围是0-1(整个过程的归一化
  716. */
  717. paowuxian(parm: {
  718. node: cc.Node;
  719. s: { x: number; y: number };
  720. e: { x: number; y: number };
  721. h: number;
  722. tadd?: number; //时间加减速比例 默认100
  723. cbk?: Function;
  724. }) {
  725. //中间点
  726. let mv2 = cc.v2(parm.s.x + (parm.e.x - parm.s.x) / 2, (parm.s.y + parm.e.y) / 2 + parm.h);
  727. //设置时间
  728. let time = Math.sqrt(Math.abs(mv2.y - parm.s.y)) + Math.sqrt(Math.abs(mv2.y - parm.e.y));
  729. time /= 50;
  730. if (parm.tadd != null) {
  731. time = (time * parm.tadd) / 100;
  732. }
  733. //起始点
  734. parm.node.x = parm.s.x;
  735. parm.node.y = parm.s.y;
  736. cc.tween(parm.node)
  737. .bezierTo(time, mv2, mv2, cc.v2(parm.e.x, parm.e.y))
  738. .call(() => {
  739. if (parm.cbk != null) {
  740. parm.cbk();
  741. }
  742. })
  743. .start();
  744. }
  745. //抛物线 + 落地弹弹弹
  746. paowuxian_tan(
  747. parm: {
  748. node: cc.Node;
  749. s: { x: number; y: number };
  750. e: { x: number; y: number };
  751. h: number;
  752. jian: number; //弹的衰减比例 100 不衰减
  753. tadd?: number; //时间加减速比例 默认100
  754. cbk?: Function;
  755. },
  756. t?: number //当前第几次弹 初次是0 不算弹的
  757. ) {
  758. //跳
  759. this.paowuxian({
  760. node: parm.node,
  761. s: parm.s,
  762. e: parm.e,
  763. h: parm.h,
  764. tadd: parm.tadd,
  765. cbk: () => {
  766. //第几次弹
  767. if (t == null) {
  768. t = 0;
  769. }
  770. t += 1;
  771. //当前衰减比例
  772. let pers = Math.pow(parm.jian / 100, t);
  773. if (pers < 0.1) {
  774. //不再弹了
  775. if (parm.cbk != null) {
  776. parm.cbk();
  777. return;
  778. }
  779. }
  780. //目标
  781. let _h = parm.h * pers; //跳的高度 衰减
  782. let _e = {
  783. x: parm.e.x + (parm.e.x - parm.s.x) * pers, //跳的宽度 衰减
  784. y: parm.e.y, //水平坐标不变
  785. };
  786. //调用自己
  787. this.paowuxian_tan(
  788. {
  789. node: parm.node,
  790. s: parm.e, //开始 = 上一次的结束
  791. e: _e,
  792. h: _h,
  793. jian: parm.jian,
  794. tadd: parm.tadd,
  795. cbk: parm.cbk,
  796. },
  797. t
  798. );
  799. },
  800. });
  801. }
  802. // 引擎自带的贝塞尔曲线运动
  803. runBezierAct(actNode: cc.Node, duration: number, bezierDatas: BezierData[]) {
  804. if (bezierDatas.length <= 0) return
  805. let tw = cc.tween()
  806. for (let i = 0; i < bezierDatas.length; ++i) {
  807. tw.bezierTo(duration, bezierDatas[i].c1, bezierDatas[i].c2, bezierDatas[i].endPos)
  808. }
  809. actNode.setPosition(bezierDatas[0].startPos)
  810. tw.clone(actNode).start()
  811. }
  812. // 匀速贝塞尔曲线运动
  813. runUniformBezierAct(actNode: cc.Node, duration: number, bezierDatas: BezierData[], onComplete?: Function) {
  814. if (bezierDatas.length <= 0) return
  815. let tw = cc.tween()
  816. let allBezierPos: cc.Vec2[] = []
  817. for (let i = 0; i < bezierDatas.length; ++i) {
  818. let posArr = [bezierDatas[i].startPos, bezierDatas[i].c1, bezierDatas[i].c2, bezierDatas[i].endPos]
  819. allBezierPos = allBezierPos.concat(this._caculateBezierPoint(posArr))
  820. }
  821. let totalLineLen = this._caculateBezierLength(allBezierPos)
  822. let speed = totalLineLen / duration
  823. let isCanBezier = false
  824. for (let i = 1; i < allBezierPos.length; ++i) {
  825. let dis = cc.v2(allBezierPos[i].x - allBezierPos[i - 1].x, allBezierPos[i].y - allBezierPos[i - 1].y).len()
  826. if (dis > 0) {
  827. // 这里过滤掉两段贝塞尔曲线首位连接的点
  828. let t = dis / speed
  829. tw.to(t, { position: allBezierPos[i] })
  830. .call(() => {
  831. let angle = FormulaCom.getAngleByPos(allBezierPos[i - 1].x, allBezierPos[i - 1].y, allBezierPos[i].x, allBezierPos[i].y)
  832. // let angle = FormulaCom.getAngle(allBezierPos[i - 1],allBezierPos[i])
  833. // console.log("angle = ",angle)
  834. actNode.angle = angle
  835. })
  836. isCanBezier = true
  837. }
  838. }
  839. if (!isCanBezier) {
  840. console.error("allBezierPos.length == 0")
  841. return
  842. }
  843. actNode.setPosition(allBezierPos[0])
  844. tw.clone(actNode).call(() => {
  845. onComplete && onComplete();
  846. }).start()
  847. }
  848. // 计算所有贝塞尔曲线的点
  849. private _caculateBezierPoint(posArr: cc.Vec2[]) {
  850. let allBezierPos = []
  851. let allDis = cc.Vec2.distance(posArr[0], posArr[3])
  852. let gapCount = Math.ceil(allDis / 10)//距离5像素为一点
  853. let gap = 1 / gapCount // 每次迭代步长,这个值越小越精细,但是效率越低,这里迭代300次已经够了
  854. for (let i = 0; i <= 1; i += gap) {
  855. let pos = this._caculateBezierP(posArr, i)
  856. allBezierPos.push(pos)
  857. }
  858. return allBezierPos
  859. }
  860. // 计算贝塞尔曲线的长度
  861. private _caculateBezierLength(allBezierPos: cc.Vec2[]) {
  862. let totalLineLen = 0
  863. for (let i = 1; i < allBezierPos.length; ++i) {
  864. let dis = cc.v2(allBezierPos[i].x - allBezierPos[i - 1].x, allBezierPos[i].y - allBezierPos[i - 1].y).len()
  865. totalLineLen += dis
  866. }
  867. return totalLineLen
  868. }
  869. /**
  870. * 计算三阶贝塞尔在 t时刻 的位置
  871. * @param p 三阶贝塞尔的四个点,数组对应为 0起点,1起点控制点,2终点控制点,3终点
  872. * @param t 传入0-1的值,一个时间的迭代过程
  873. * @returns
  874. */
  875. private _caculateBezierP(p: cc.Vec2[], t: number): cc.Vec2 {
  876. // 三阶贝塞尔运算
  877. let bezierP: cc.Vec2 = cc.v2()
  878. bezierP.x = Math.floor(Math.pow(1 - t, 3) * p[0].x + 3 * t * Math.pow(1 - t, 2) * p[1].x + 3 * Math.pow(t, 2) * (1 - t) * p[2].x + Math.pow(t, 3) * p[3].x);
  879. bezierP.y = Math.floor(Math.pow(1 - t, 3) * p[0].y + 3 * t * Math.pow(1 - t, 2) * p[1].y + 3 * Math.pow(t, 2) * (1 - t) * p[2].y + Math.pow(t, 3) * p[3].y);
  880. return bezierP;
  881. }
  882. /** 判断隐藏礼包是否开启
  883. * @param ycgift 隐藏礼包数据
  884. * @param dc 档次
  885. * @param buyNum 购买次数
  886. * @param buyCons 购买金额
  887. */
  888. checkYcGiftOpen(tiaojian: number[], buyNum: number, buyCons: number) {
  889. if (gameMethod.isEmpty(tiaojian)) return false;
  890. //有配置全局充值金额并且全局充值大于固定金额显示
  891. if (!gameMethod.isEmpty(tiaojian[2]) && GameDataCenter.sevBack?.userInfo?.a?.iscz >= tiaojian[2]) { return true; }
  892. // 隐藏礼包条件判断
  893. if (tiaojian[0] > 0 && tiaojian[1] <= 0) {
  894. // 第一种 只判断次数
  895. if (buyNum >= tiaojian[0]) { return true; }
  896. } else if (tiaojian[0] <= 0 && tiaojian[1] > 0) {
  897. // 第二种 只判断金额
  898. if (buyCons >= tiaojian[1]) { return true; }
  899. } else if (tiaojian[0] > 0 && tiaojian[1] > 0) {
  900. // 第三种 判断次数和金额 满足一种
  901. if (buyNum >= tiaojian[0] || buyCons >= tiaojian[1]) { return true; }
  902. }
  903. return false;
  904. }
  905. // 获取属性名跟值 *传入值 "atk":10 返回 [攻击, 10]
  906. getEpsNameValue(key: string, value: number): string[] {
  907. let epsCfg = Gamecfg.userEp.getItem(key);
  908. value = value ? value : 0;
  909. if (gameMethod.isEmpty(epsCfg)) {
  910. // console.log('配置错误===', key)
  911. return [key, value + ""];
  912. }
  913. // return [this.fixName(epsCfg.name), (epsCfg.isPer ? `${value / 100}%` : value + "") || "0"];
  914. return [this.fixName(I18n.getUserEpName(epsCfg.key)), epsCfg.isPer ? `${value / 100}%` : value + ""]
  915. }
  916. /**
  917. * UI节点转换到目标节点下的坐标
  918. * @param node 节点
  919. * @param targetNode 目标节点
  920. * @returns {转换后的坐标的点|Point}
  921. */
  922. transPos(node: cc.Node, targetNode: cc.Node): cc.Vec2 {
  923. //转世界坐标
  924. var endGlobalPos = this.getWorldPos(node);
  925. if (!endGlobalPos) return null;
  926. //再转局部坐标
  927. var endPos = targetNode.convertToNodeSpaceAR(endGlobalPos);
  928. return endPos;
  929. }
  930. // 剪贴板复制功能
  931. public webCopyString(str: string) {
  932. // console.log('复制ing');
  933. var input = str + '';
  934. const el = document.createElement('textarea');
  935. el.value = input;
  936. el.setAttribute('readonly', '');
  937. // el.style.contain = 'strict';
  938. el.style.position = 'absolute';
  939. el.style.left = '-9999px';
  940. el.style.fontSize = '12pt'; // Prevent zooming on iOS
  941. const selection = getSelection();
  942. var originalRange = null;
  943. if (selection.rangeCount > 0) {
  944. originalRange = selection.getRangeAt(0);
  945. }
  946. document.body.appendChild(el);
  947. el.select();
  948. el.selectionStart = 0;
  949. el.selectionEnd = input.length;
  950. var success = false;
  951. try {
  952. success = document.execCommand('copy');
  953. } catch (err) { }
  954. document.body.removeChild(el);
  955. if (originalRange) {
  956. selection.removeAllRanges();
  957. selection.addRange(originalRange);
  958. }
  959. if (success) {
  960. UIHelp.ShowTips(I18n.getI18nText('common_copy_sucess'));
  961. } else {
  962. UIHelp.ShowTips(I18n.getI18nText('common_copy_fail'));
  963. }
  964. return success;
  965. }
  966. /**
  967. * 设置滚动变化的数字文本,不要再任何虚拟列表内使用
  968. * @param obj 需要控制的文本组件
  969. * @param num 变化的目标数字
  970. * @param cb 实时变化的数字值,自己在内部写需要执行和改变的文本(如果不传则直接对obj.text或这obj.title赋值
  971. * @param param 参数
  972. */
  973. SetLabelRoll(obj: fairygui.GTextField | fairygui.GLabel | fairygui.GRichTextField, num: number, cb: (val: number) => void = null, param: RollLabelParam = null) {
  974. let rolllabel = obj.node.getComponent(RollLabel)
  975. if (rolllabel == null) {
  976. rolllabel = obj.node.addComponent(RollLabel)
  977. rolllabel.obj = obj
  978. }
  979. rolllabel.setData(num, cb, param)
  980. }
  981. /**
  982. * 上漂文本
  983. * @param func 改变文本图标的方法
  984. * @param node 位置节点定位用
  985. * @param view 挂载上漂节点用的页面
  986. * @param cache 缓存
  987. * @param randomX
  988. * @param randomY
  989. * @param pac
  990. * @param popItem
  991. */
  992. PopTips(func: (obj: fairygui.GLabel) => void, node: fairygui.GComponent, view: fairygui.GComponent, cache: PopTipsCache[], randomX: number[] | number = null, randomY: number[] | number = null, pac: string = "Common", popItem: string = "PopTips") {
  993. let data: PopTipsCache = null
  994. for (let i of cache) {
  995. if (i.obj.visible == false) {
  996. data = i
  997. break
  998. }
  999. }
  1000. if (data == null) {
  1001. let obj = fgui.UIPackage.createObject(pac, popItem) as fairygui.GLabel
  1002. view.addChild(obj)
  1003. data = new PopTipsCache(obj)
  1004. cache.push(data)
  1005. }
  1006. if (typeof randomX == "number") {
  1007. data.obj.x = node.x + randomX
  1008. } else if (randomX) {
  1009. let left = randomX[0]
  1010. let right = randomX[1]
  1011. let randomposx = GameMath.getRandomNum(left, right)
  1012. data.obj.x = node.x + randomposx
  1013. } else {
  1014. data.obj.x = node.x
  1015. }
  1016. if (typeof randomY == "number") {
  1017. data.obj.y = node.y + randomY
  1018. } else if (randomY) {
  1019. let left = randomY[0]
  1020. let right = randomY[1]
  1021. let randomposy = GameMath.getRandomNum(left, right)
  1022. data.obj.y = node.y + randomposy
  1023. } else {
  1024. data.obj.y = node.y
  1025. }
  1026. func(data.obj)
  1027. data.startTween()
  1028. }
  1029. /** 数字转中文 */
  1030. NumToChinese(num) {
  1031. if (num < 0 || num > 100) {
  1032. return "输入的数字必须在0到100之间";
  1033. }
  1034. const units = ['', '十', '百', '千']; // 可以根据需要扩展
  1035. const chineseNums = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
  1036. let str = '';
  1037. let digit;
  1038. // 处理个位数
  1039. digit = num % 10;
  1040. if (digit !== 0) {
  1041. str = chineseNums[digit];
  1042. }
  1043. // 处理十位数
  1044. num = Math.floor(num / 10);
  1045. if (num !== 0) {
  1046. if (num == 1) {
  1047. str = '十' + str;
  1048. } else {
  1049. str = chineseNums[num] + '十' + str;
  1050. }
  1051. }
  1052. // 如果需要处理更大的数字,可以继续添加逻辑
  1053. // 移除开头的'零'
  1054. str = str.replace(/^零+/, '');
  1055. if (str === '') {
  1056. str = '零';
  1057. }
  1058. return str;
  1059. }
  1060. /** 圆上指定角度一点 */
  1061. GetPointOnCircle(radius: number, degree: number, center: cc.Vec2): cc.Vec2 {
  1062. let angle = this.DegreeToAngle(degree);
  1063. // 使用三角函数计算圆上该角度对应的点的x和y坐标
  1064. const x = center.x + radius * Math.cos(angle);
  1065. const y = center.y + radius * Math.sin(angle);
  1066. // 返回该点的坐标
  1067. return cc.v2(x, y);
  1068. }
  1069. /** 圆上任意一点 */
  1070. GetRandomPointOnCircle(radius: number, centerX = 0, centerY = 0): cc.Vec2 {
  1071. // 生成一个0到2π之间的随机角度(弧度)
  1072. const angle = Math.random() * 2 * Math.PI;
  1073. // 使用三角函数计算圆上该角度对应的点的x和y坐标
  1074. const x = centerX + radius * Math.cos(angle);
  1075. const y = centerY + radius * Math.sin(angle);
  1076. // 返回该点的坐标
  1077. return cc.v2(x, y);
  1078. }
  1079. /** 圆上角度范围一点 */
  1080. GetRandomPointOnAngle(radius: number, rangeAngle: number, center: cc.Vec2, point: cc.Vec2): cc.Vec2 {
  1081. // 生成一个0到2π之间的随机角度(弧度)
  1082. // const angle = Math.random() * 2 * Math.PI;
  1083. const dx = point.x - center.x;
  1084. const dy = point.y - center.y;
  1085. const angleRadians = Math.atan2(dy, dx);
  1086. let angle = this.AngleToDegree(angleRadians);
  1087. let newAngle = GameMath.getRandomNum(rangeAngle, rangeAngle * 2) - rangeAngle;
  1088. angle += newAngle;
  1089. let degree = this.DegreeToAngle(angle);
  1090. // 使用三角函数计算圆上该角度对应的点的x和y坐标
  1091. const x = center.x + radius * Math.cos(degree);
  1092. const y = center.y + radius * Math.sin(degree);
  1093. // 返回该点的坐标
  1094. return cc.v2(x, y);
  1095. }
  1096. /** 圆上指定角度一点 */
  1097. GetCirclePointOnAngle(radius: number, diffAngle: number, center: cc.Vec2, point: cc.Vec2): cc.Vec2 {
  1098. // 生成一个0到2π之间的随机角度(弧度)
  1099. // const angle = Math.random() * 2 * Math.PI;
  1100. const dx = point.x - center.x;
  1101. const dy = point.y - center.y;
  1102. const angleRadians = Math.atan2(dy, dx);
  1103. let angle = this.AngleToDegree(angleRadians);
  1104. let degree = angle + diffAngle;
  1105. let newAngle = this.DegreeToAngle(degree);
  1106. // 使用三角函数计算圆上该角度对应的点的x和y坐标
  1107. const x = center.x + radius * Math.cos(newAngle);
  1108. const y = center.y + radius * Math.sin(newAngle);
  1109. // 返回该点的坐标
  1110. return cc.v2(x, y);
  1111. }
  1112. /** 圆上分割角度后的坐标点 */
  1113. GetCirclePointDivideAngle(radius: number, divideCount: number, center: cc.Vec2): cc.Vec2[] {
  1114. // 生成一个0到2π之间的随机角度(弧度)
  1115. let per = divideCount / divideCount;
  1116. let posArr = [];
  1117. for (let i = 0; i < divideCount; i++) {
  1118. let degree = per * i;
  1119. // 使用三角函数计算圆上该角度对应的点的x和y坐标
  1120. const x = center.x + radius * Math.cos(degree);
  1121. const y = center.y + radius * Math.sin(degree);
  1122. posArr.push(cc.v2(x, y))
  1123. }
  1124. return posArr;
  1125. }
  1126. /** 圆上指定弧长的点 */
  1127. GetCircleAngleLenPoint(radius: number, len: number, center: cc.Vec2, point: cc.Vec2, index: number): cc.Vec2 {
  1128. const dx = point.x - center.x;
  1129. const dy = point.y - center.y;
  1130. const angleRadians = Math.atan2(dy, dx);
  1131. let degree = this.AngleToDegree(angleRadians);
  1132. let totalLen = 2 * Math.PI * radius;
  1133. let needDegree = len / totalLen * 360;//弧长对应角度
  1134. let newDegree = degree + index * needDegree;
  1135. let newAngle = this.DegreeToAngle(newDegree);
  1136. // 使用三角函数计算圆上该角度对应的点的x和y坐标
  1137. const x = center.x + radius * Math.cos(newAngle);
  1138. const y = center.y + radius * Math.sin(newAngle);
  1139. // 返回该点的坐标
  1140. return cc.v2(x, y);
  1141. }
  1142. /** 弧度转角度 */
  1143. public AngleToDegree(angleRadians) {
  1144. const angleDegrees = angleRadians * (180 / Math.PI);
  1145. return angleDegrees
  1146. }
  1147. /** 角度转弧度 */
  1148. public DegreeToAngle(degree: number) {
  1149. const angle = degree / (180 / Math.PI);
  1150. return angle;
  1151. }
  1152. /** 获取两点角度 */
  1153. public GetTwoPointAngle(center: cc.Vec2, point: cc.Vec2) {
  1154. const dx = point.x - center.x;
  1155. const dy = point.y - center.y;
  1156. const angleRadians = Math.atan2(dy, dx);
  1157. let angle = this.AngleToDegree(angleRadians);
  1158. return angle;
  1159. }
  1160. /** 求多个点的中心点 */
  1161. public CalculateCentroid(points: cc.Vec2[]): cc.Vec2 {
  1162. // 初始化x和y的总和
  1163. let sumX = 0;
  1164. let sumY = 0;
  1165. // 遍历所有点,累加x和y的坐标
  1166. points.forEach(point => {
  1167. sumX += point.x;
  1168. sumY += point.y;
  1169. });
  1170. // 计算平均x和y坐标
  1171. let centroidX = sumX / points.length;
  1172. let centroidY = sumY / points.length;
  1173. // 返回中心点坐标
  1174. return cc.v2(centroidX, centroidY);
  1175. }
  1176. }
  1177. export class PopTipsCache {
  1178. obj: fairygui.GLabel
  1179. tween1: cc.Tween;
  1180. tween2: cc.Tween;
  1181. constructor(obj: fairygui.GLabel) {
  1182. this.obj = obj
  1183. }
  1184. startTween() {
  1185. this.obj.alpha = 0
  1186. this.obj.visible = true
  1187. let y = this.obj.y - 120;
  1188. this.tween1 = cc.tween(this.obj).to(1, {
  1189. y: y,
  1190. }).call(() => {
  1191. this.obj.visible = false
  1192. })
  1193. this.tween2 = cc.tween(this.obj).to(0.2, {
  1194. alpha: 1,
  1195. }).delay(0.6).to(0.2, {
  1196. alpha: 0,
  1197. })
  1198. this.tween1.start()
  1199. this.tween2.start()
  1200. }
  1201. clearTween() {
  1202. this.tween1?.stop()
  1203. this.tween2?.stop()
  1204. this.obj.visible = false;
  1205. }
  1206. }
  1207. export let uiCommon = new UICommon()