GameNode.ts 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. import { _decorator, geometry, Node, Camera, screen, EventTouch, instantiate, v3, tween, MeshRenderer, Vec3, find, input, Input, Tween, PhysicsSystem, RigidBody, Prefab, math } from 'cc';
  2. import { TileItem } from './TileItem';
  3. import { Main } from './Main';
  4. import { ResUtil } from '../core/utils/ResUtil';
  5. import { ComponentEx } from '../core/component/ComponentEx';
  6. import { audioMgr } from '../core/manager/AudioManager';
  7. import BusyLoadingManager, { BUSY_TYPE } from '../core/manager/BusyLoadingManager';
  8. import Data from '../core/manager/Data';
  9. import WindowManager from '../core/manager/WindowManager';
  10. import MsgHints from '../core/utils/MsgHints';
  11. import Utils from '../core/utils/Utils';
  12. import { DrawStarLayer } from '../gameui/DrawStarLayer';
  13. import { eventEmitter } from '../core/event/EventEmitter';
  14. import platformSystem from '../platform/platformSystem';
  15. import { GameConst, ITEM_TYPE } from '../core/common/GameConst';
  16. import { levelsData } from '../user/LevelsData';
  17. import i18n from '../core/i18n/runtime-scripts/LanguageData';
  18. const { Ray } = geometry;
  19. const { ccclass, property, executeInEditMode } = _decorator;
  20. @ccclass('GameNode')
  21. export class GameNode extends ComponentEx {
  22. public lock: boolean = false;
  23. public collectpos: Vec3[] = [];
  24. public collectTiles: Node[] = [];
  25. public allTiles: Node[] = [];
  26. public optionTiles: Node[] = [];
  27. public start() {
  28. setTimeout(() => {
  29. this.initWall();
  30. this.collectpos = [];
  31. this.GetNode("collectbox").children.map(a => {
  32. this.collectpos.push(a.worldPosition);
  33. a.active = false;
  34. })
  35. }, 100);
  36. eventEmitter.register(this, GameConst.CLEAR_ALL_BOX, () => {
  37. console.log("清空盒子")
  38. this.lock=false
  39. setTimeout(() => {
  40. for (let i = 0; i < this.collectTiles.length; ++i) {
  41. this.pushBask(this.collectTiles[i].name);
  42. }
  43. this.collectTiles.map(a => {
  44. a.destroy();
  45. })
  46. this.collectTiles = [];
  47. }, 500);
  48. })
  49. //提示
  50. eventEmitter.register(this, GameConst.USE_ITEM_HINT,this.prompt.bind(this))
  51. }
  52. onEnable() {
  53. input.on(Input.EventType.TOUCH_START, this.onTouchStart, this);
  54. input.on(Input.EventType.TOUCH_END, this.onTouchEnd, this);
  55. input.on(Input.EventType.TOUCH_MOVE, this.onTouchMove, this);
  56. }
  57. onDisable() {
  58. input.off(Input.EventType.TOUCH_START, this.onTouchStart, this);
  59. input.off(Input.EventType.TOUCH_END, this.onTouchEnd, this);
  60. input.off(Input.EventType.TOUCH_MOVE, this.onTouchMove, this);
  61. }
  62. moveToRightPos() {
  63. this.collectTiles.map((a, i) => {
  64. let tileItem = a.getComponent(TileItem)
  65. Tween.stopAllByTarget(a);
  66. tileItem.auto_rotation = true;
  67. // tileItem.isMoving = true;
  68. // this.parabolicMovementWithScale(a,this.collectpos[i].clone(),10,0.15,a.scale.clone(),v3(1, 1, 1),
  69. // (n: Node) => {
  70. const scale = tileItem.oScale.clone().subtract(new Vec3(0.25, 0.25, 0.25));
  71. tween(a).to(0.25, { worldPosition: this.collectpos[i].clone(), worldScale: scale }).call(() => {
  72. if (tileItem.removed) {
  73. Utils.remove(this.optionTiles,a);
  74. tween(a).to(0.2, { worldPosition: tileItem.removedPos }, { easing: 'backIn' }).call(async () => {
  75. // tileItem.isMoving = false;
  76. //最中央的播放移除特效
  77. if (tileItem.playRmovedEff) {
  78. //增加星星
  79. let pos = this.GetNode("Main Camera").getComponent(Camera).convertToUINode(a.worldPosition, find("Canvas"));
  80. let node_ani = await ResUtil.playSkAni("spine/effect_hecheng", "effect", find("Canvas"), pos);
  81. audioMgr.playOneShot(GameConst.audios.starCollect);
  82. platformSystem.platform.vibrateShort();
  83. Utils.flyAnim(ITEM_TYPE.Star, node_ani, Main.I._GameUI.starNode, 1, 0, (b) => {
  84. if (b) {
  85. Main.I._GameUI.star++;
  86. }
  87. });
  88. }
  89. a.destroy();
  90. this.moveToRightPos()
  91. this.checkResult()
  92. }).start()
  93. }
  94. }).start();
  95. // }
  96. // );
  97. })
  98. }
  99. chooseTile(tile: Node) {
  100. let chooseName = tile.name;
  101. this.optionTiles.push(tile);
  102. let tileItem:TileItem = tile.getComponent(TileItem);
  103. // if(tileItem.isMoving)return;
  104. tileItem.destoryCollider();
  105. for (var i = 0; i < this.allTiles.length; ++i) {
  106. if (this.allTiles[i] == tile) {
  107. this.allTiles.splice(i, 1);
  108. break;
  109. }
  110. }
  111. //计算目标位置
  112. let targetPos: Vec3;
  113. let bInsert = false;
  114. for (var i = this.collectTiles.length - 1; i >= 0; --i) {
  115. if (this.collectTiles[i].name == chooseName) {
  116. targetPos = this.collectpos[i + 1]?.clone() || this.collectpos[this.collectpos.length - 1].clone();
  117. this.collectTiles.splice(i + 1, 0, tile);
  118. bInsert = true;
  119. break;
  120. }
  121. }
  122. if (!bInsert) {
  123. targetPos = this.collectpos[this.collectTiles.length]?.clone() || this.collectpos[0].clone();
  124. this.collectTiles.push(tile);
  125. }
  126. this.moveToRightPos()
  127. //动画完成后执行原有逻辑
  128. let tmp = this.collectTiles.filter(a =>a.name == chooseName);
  129. if(tmp.length == 3) {
  130. for (var i = this.collectTiles.length - 1; i >= 0; --i) {
  131. if (this.collectTiles[i].name == chooseName) {
  132. let delNode = this.collectTiles[i];
  133. delNode.getComponent(TileItem).removed = true;
  134. this.collectTiles.splice(i, 1);
  135. delNode.getComponent(TileItem).removedPos = tmp[1].worldPosition;
  136. if (delNode == tmp[1])
  137. delNode.getComponent(TileItem).playRmovedEff = true;
  138. }
  139. }
  140. }
  141. this.checkResult();
  142. }
  143. checkResult() {
  144. if(this.lock)return
  145. //检测胜利和失败
  146. if (this.collectTiles.length >= 7) {
  147. this.lock=true
  148. if (!WindowManager.ins.isShow("OutOfBoxLayer"))
  149. WindowManager.ins.open("OutOfBoxLayer");
  150. } else if (this.collectTiles.length == 0 && this.allTiles.length == 0) {
  151. this.lock=true
  152. WindowManager.ins.open("DrawStarLayer").then((com: DrawStarLayer) => {
  153. com.setStar(Main.I._GameUI.star);
  154. })
  155. }
  156. }
  157. private touchTileItem: TileItem = null;
  158. onTouchStart(event: EventTouch) {
  159. if (Main.I._GameUI.pasue) return;
  160. if (Data.user.useMagnet) return;
  161. if (this.allTiles.length == 0) return;
  162. if (this.collectTiles.length >= 7) return;
  163. const p = event.getLocation();
  164. let camera = this.GetNode("Main Camera").getComponent(Camera);
  165. const r = new Ray();
  166. camera?.screenPointToRay(p.x, p.y, r);
  167. let b = PhysicsSystem.instance.raycastClosest(r, 1);
  168. if (b) {
  169. let collider = PhysicsSystem.instance.raycastClosestResult.collider;
  170. //console.log(collider.node.name, collider.getComponent(RigidBody).group);
  171. let item = collider.node.parent.getComponent(TileItem);
  172. if (item){
  173. item.enableCollider = false;
  174. let pos = item.node.getWorldPosition();
  175. pos.y += 0.3;
  176. item.node.setWorldPosition(pos);
  177. this.touchTileItem = item;
  178. }
  179. }
  180. }
  181. onTouchEnd(event: EventTouch) {
  182. if (Main.I._GameUI.pasue) return;
  183. if (Data.user.useMagnet) return;
  184. if (!this.touchTileItem) return;
  185. if(this.touchTileItem.isMoving){
  186. return;
  187. }
  188. if (this.allTiles.length == 0) return;
  189. if (this.collectTiles.length >= 7) {
  190. return;
  191. }
  192. const p = event.getLocation();
  193. let camera = this.GetNode("Main Camera").getComponent(Camera);
  194. const r = new Ray();
  195. camera?.screenPointToRay(p.x, p.y, r);
  196. let b = PhysicsSystem.instance.raycastClosest(r, 1);
  197. if (b) {
  198. let collider = PhysicsSystem.instance.raycastClosestResult.collider;
  199. let item = collider.node.parent.getComponent(TileItem);
  200. if (item && item == this.touchTileItem) {
  201. platformSystem.platform.vibrateShort();
  202. this.chooseTile(item.node);
  203. this.touchTileItem = null;
  204. audioMgr.playOneShot(GameConst.audios.tap);
  205. }else {
  206. this.touchTileItem.enableCollider = true;
  207. }
  208. }
  209. }
  210. onTouchMove(event: EventTouch) {
  211. if (Main.I._GameUI.pasue) return;
  212. if (Data.user.useMagnet) return;
  213. if (this.allTiles.length == 0) return;
  214. if (this.collectTiles.length >= 7) {
  215. return;
  216. }
  217. const p = event.getLocation();
  218. let camera = this.GetNode("Main Camera").getComponent(Camera);
  219. const r = new Ray();
  220. camera?.screenPointToRay(p.x, p.y, r);
  221. let b = PhysicsSystem.instance.raycastClosest(r, 1)
  222. if (b) {
  223. let collider = PhysicsSystem.instance.raycastClosestResult.collider
  224. let item = collider.node.parent.getComponent(TileItem);
  225. if (item) {
  226. if (this.touchTileItem && item == this.touchTileItem) {
  227. }else {
  228. if (this.touchTileItem) this.touchTileItem.enableCollider = true;
  229. item.enableCollider = false;
  230. let pos = item.node.getWorldPosition();
  231. pos.y += 0.3;
  232. item.node.setWorldPosition(pos);
  233. this.touchTileItem = item;
  234. }
  235. }else {
  236. if (this.touchTileItem) {
  237. // console.log("无目标")
  238. this.touchTileItem.enableCollider = true;
  239. this.touchTileItem = null;
  240. }
  241. }
  242. }
  243. }
  244. initWall() {
  245. if (!PhysicsSystem.instance) return;
  246. const OFFSET = 25;
  247. let camera = this.GetNode("Main Camera").getComponent(Camera);
  248. const r = new Ray();
  249. let size = screen.windowSize;
  250. //左边墙
  251. camera?.screenPointToRay(OFFSET, size.height / 2, r);
  252. if (PhysicsSystem.instance.raycastClosest(r)) {
  253. const result = PhysicsSystem.instance.raycastClosestResult;
  254. //this.GetNode("PlaneLeft").setWorldPosition(result.hitPoint);
  255. }
  256. //右边墙
  257. camera?.screenPointToRay(size.width - OFFSET, size.height / 2, r);
  258. if (PhysicsSystem.instance.raycastClosest(r)) {
  259. const result = PhysicsSystem.instance.raycastClosestResult;
  260. //this.GetNode("PlaneRight").setWorldPosition(result.hitPoint);
  261. }
  262. //上边墙
  263. camera?.screenPointToRay(size.width / 2, size.height - OFFSET * 4, r);
  264. if (PhysicsSystem.instance.raycastClosest(r)) {
  265. const result = PhysicsSystem.instance.raycastClosestResult;
  266. //this.GetNode("PlaneTop").setWorldPosition(result.hitPoint);
  267. }
  268. let minx = this.GetNode("PlaneLeft").worldPosition.x;
  269. let maxx = this.GetNode("PlaneRight").worldPosition.x;
  270. let scale = (maxx - minx) / 8.461091041564941;
  271. //this.GetNode("bottom").scale = v3(scale, scale, scale);
  272. //格子位置
  273. camera?.screenPointToRay(size.width / 2, 10, r);
  274. if (PhysicsSystem.instance.raycastClosest(r)) {
  275. const result = PhysicsSystem.instance.raycastClosestResult;
  276. //result.hitPoint
  277. let p: Vec3 = new Vec3(result.hitPoint.x,4,result.hitPoint.z);
  278. //this.GetNode("bottom").setWorldPosition(p);
  279. }
  280. //下边墙
  281. //this.GetNode("PlaneDown").setWorldPosition(this.GetNode("bottom").worldPosition.add3f(0, 0, -2.5 * scale));
  282. }
  283. async createTiles() {
  284. let Margin = 0
  285. let minx = this.GetNode("PlaneLeft").worldPosition.x + Margin;
  286. let maxx = this.GetNode("PlaneRight").worldPosition.x - Margin;
  287. let maxz = this.GetNode("PlaneDown").worldPosition.z + Margin;
  288. let minz = this.GetNode("PlaneTop").worldPosition.z - Margin;
  289. let obj = levelsData.getCurLevelInfo();
  290. //数量
  291. let tileCount = Math.floor(obj.count / 3);
  292. let names:Array<string> = levelsData.getModesNames();
  293. let tilepools = Utils.getRandomElements(names,obj.kind);
  294. tilepools = Utils.extendArray(tilepools,tileCount);
  295. Main.I._GameUI.pasue = true;
  296. BusyLoadingManager.ins.addBusy(BUSY_TYPE.RES);
  297. for (let i = 0; i < tileCount; i++) {
  298. for (let j = 0; j < 3; j++) {
  299. const globalIndex = (i * 3 + j) % 9;
  300. let lnode = this.GetNode(`l${globalIndex}`);
  301. setTimeout(async ()=>{
  302. let nomal_node:Node = await ResUtil.loadModelPrefabName(tilepools[i]);
  303. nomal_node.getComponentInChildren(MeshRenderer).shadowCastingMode = MeshRenderer.ShadowCastingMode.ON;
  304. let tile = nomal_node.addComponent(TileItem);
  305. let rigid = tile.addCollider();
  306. //nomal_node.scale = v3(1.25, 1.25, 1.25)
  307. if (Data.user.lv == 1) {
  308. nomal_node.setWorldPosition(lnode.getWorldPosition());
  309. nomal_node.parent = this.node;
  310. this.allTiles.push(nomal_node);
  311. }else{
  312. //let p = v3(Utils.getRandomInt(minx / 2, maxx / 2), Utils.getRandom(12, 15), Utils.getRandomInt(minz / 2, maxz / 2));
  313. let p = v3(Utils.getRandomInt(-3, 3), Utils.getRandom(12, 15), Utils.getRandomInt(-3, 3));
  314. nomal_node.setWorldRotationFromEuler(Utils.getRandom(0, 300), Utils.getRandom(0, 300), Utils.getRandom(0, 300))
  315. nomal_node.setWorldPosition(p);
  316. nomal_node.parent = this.node;
  317. rigid.applyImpulse(v3(0, 3, 0))
  318. this.allTiles.push(nomal_node);
  319. }
  320. },j * 0.03 * 1000);
  321. }
  322. if (i == tileCount - 1) {
  323. BusyLoadingManager.ins.removeBusy(BUSY_TYPE.RES);
  324. Main.I._GameUI.pasue = false;
  325. }
  326. }
  327. setTimeout(() => {
  328. if (Data.user.useMagnet) {
  329. Main.I._GameUI.pasue = true;
  330. Data.user.magnet--;
  331. let p = this.GetNode("magnet").worldPosition;
  332. this.GetNode("magnet").worldPosition = v3(-10, p.y, p.z);
  333. tween(this.GetNode("magnet")).to(0.5, { worldPosition: v3(0, p.y, p.z) }).call(async () => {
  334. let arr = [];
  335. for (var i = this.allTiles.length - 1; i >= 0; --i) {
  336. let pick = false;
  337. let item = this.allTiles[i];
  338. let tmp: Node = arr[0];
  339. if(tmp){
  340. if (tmp.name == item.name && arr.length < 3) {
  341. arr.push(item);
  342. pick = true;
  343. }
  344. }else{
  345. arr.push(item);
  346. pick = true;
  347. }
  348. if(pick) {
  349. let tile: TileItem = this.allTiles[i].getComponent(TileItem);
  350. tile.destoryCollider();
  351. this.GetNode("magnet_content").addChild(tile.node);
  352. tween(tile.node).to(0.5, { worldPosition: this.GetNode(arr.length % 2 ? "m_left" : "r_left").worldPosition }).start();
  353. this.allTiles.splice(i, 1);
  354. console.log("删除", tile.name, this.allTiles.length)
  355. }
  356. }
  357. Data.user.useMagnet = false;
  358. }).delay(1.5).to(0.5, { worldPosition: v3(10, p.y, p.z) }).call(() => {
  359. this.GetNode("magnet_content").removeAllChildren();
  360. Main.I._GameUI.pasue = false;
  361. if (Data.user.useTime && Data.user.time > 0)
  362. eventEmitter.dispatch(GameConst.USE_ITEM_TIME)
  363. }).start();
  364. }else {
  365. if (Data.user.useTime && Data.user.time > 0){
  366. eventEmitter.dispatch(GameConst.USE_ITEM_TIME)}
  367. }
  368. }, 2000);
  369. }
  370. /**
  371. * 提示
  372. */
  373. restart() {
  374. this.allTiles.map(a => a.destroy());
  375. this.allTiles = [];
  376. Main.I._GameUI.star = 0;
  377. this.collectTiles.map(a => a.destroy());
  378. this.collectTiles = [];
  379. this.createTiles();
  380. this.lock=false
  381. }
  382. /**移除*/
  383. public remove(){
  384. if (this.optionTiles.length === 0 || this.lock) return;
  385. // 获取最后一个收集的物品
  386. const lastItem = this.optionTiles.pop();
  387. if (!lastItem) return;
  388. Utils.remove(this.collectTiles,lastItem);
  389. const tile = lastItem.getComponent(TileItem);
  390. tile.removed = false;
  391. tile.auto_rotation = false;
  392. //播放撤销动画
  393. Tween.stopAllByTarget(lastItem);
  394. //const globalIndex = Math.floor(Math.random() * 9);
  395. let lnode = this.GetNode(`l${4}`);
  396. let p:Vec3 = lnode.position.clone();
  397. tween(tile.node)
  398. .to(0.35, {
  399. worldPosition: new Vec3(p.x,p.y + 1,p.z),
  400. worldScale: v3(1.25, 1.25, 1.25)
  401. })
  402. .call(() => {
  403. tile.addCollider();
  404. tile.enableCollider = true;
  405. this.allTiles.push(lastItem);
  406. //向上施加力 然后在向下重力效果
  407. tile.rigid.applyImpulse(v3(0, 3, 0))
  408. this.moveToRightPos(); // 重新排列收集框中的物品
  409. })
  410. .start();
  411. }
  412. /**乱序*/
  413. public mess(){
  414. if(this.allTiles.length === 0 || this.lock) return;
  415. this.allTiles.forEach((item, index) => {
  416. const tileItem = item.getComponent(TileItem);
  417. tileItem.auto_rotation = false;
  418. tileItem.enableCollider = true;
  419. Tween.stopAllByTarget(item);
  420. tween(item)
  421. .to(0.3, {
  422. worldPosition: this.GetNode("l4").worldPosition.clone().add(v3(0, 1, 0)),
  423. worldScale: v3(1.25, 1.25, 1.25)
  424. })
  425. .call(() => {
  426. const rigid = tileItem.addCollider();
  427. rigid.applyImpulse(v3(
  428. Utils.getRandom(-3, 3),
  429. Utils.getRandom(5, 8),
  430. Utils.getRandom(-3, 3)
  431. ));
  432. item.setWorldRotationFromEuler(Utils.getRandom(0, 300), Utils.getRandom(0, 300), Utils.getRandom(0, 300))
  433. })
  434. .start();
  435. });
  436. //audioManager.playOneShot(GameConst.audios.shuffle);
  437. platformSystem.platform.vibrateShort();
  438. }
  439. /**凑齐*/
  440. public prompt(){
  441. if(Data.user.hint <= 0){
  442. MsgHints.show(i18n("main.凑齐道具不足"))
  443. return;
  444. }
  445. if(this.collectTiles.length <= 0)return;
  446. let obj = {}
  447. for (var i = 0; i < this.collectTiles.length; ++i) {
  448. let name = this.collectTiles[i].name;
  449. if (!obj[name]) obj[name] = 0;
  450. obj[name]++;
  451. }
  452. let hint_succ = false;
  453. let bFind = false;
  454. let hint_name = ""
  455. for (const key in obj) {
  456. if (obj[key] == 2) {
  457. hint_name = key;
  458. bFind = true;
  459. break;
  460. }
  461. }
  462. //找到两个一样
  463. if (bFind) {
  464. console.log("找到两个一样")
  465. for (let i = 0; i < this.allTiles.length; ++i) {
  466. if (this.allTiles[i].name == hint_name) {
  467. this.chooseTile(this.allTiles[i]);
  468. break;
  469. }
  470. }
  471. hint_succ = true;
  472. }else {
  473. if (this.collectTiles.length >= 5) {
  474. MsgHints.show(i18n("main.未找到合适物品"));
  475. return
  476. }else {
  477. hint_name = this.collectTiles[0] ? this.collectTiles[0].name : this.allTiles[0].name;
  478. let lsit = this.allTiles.filter(a => {
  479. return a.name == hint_name;
  480. })
  481. lsit.map((a, i) => {
  482. setTimeout(() => {
  483. this.chooseTile(a);
  484. }, 350 * i);
  485. })
  486. hint_succ = true;
  487. }
  488. }
  489. if (hint_succ) {
  490. Data.user.hint--;
  491. }
  492. }
  493. //放回场景中
  494. async pushBask(name: string) {
  495. let Margin = 0;
  496. let maxz = this.GetNode("PlaneDown").worldPosition.z + Margin;
  497. let minz = this.GetNode("PlaneTop").worldPosition.z - Margin;
  498. let minx = this.GetNode("PlaneLeft").worldPosition.x + Margin;
  499. let maxx = this.GetNode("PlaneRight").worldPosition.x - Margin;
  500. let nomal_node = await ResUtil.loadModelPrefabName(name);
  501. nomal_node.name = name;
  502. let tile = nomal_node.addComponent(TileItem);
  503. let rigid = tile.addCollider();
  504. nomal_node.scale = v3(1.25,1.25, 1.25)
  505. nomal_node.parent = this.node;
  506. rigid.applyImpulse(v3(0, 3, 0))
  507. let p = v3(minx + (maxx - minx) / 2, Utils.getRandom(12, 15), minz + (maxz - minz) / 2);
  508. nomal_node.setWorldRotationFromEuler(Utils.getRandom(0, 300), Utils.getRandom(0, 300), Utils.getRandom(0, 300))
  509. nomal_node.setWorldPosition(p);
  510. this.allTiles.push(nomal_node);
  511. }
  512. /**
  513. * 抛物线运动工具(带缩放动画)
  514. * @param node 要移动的节点
  515. * @param startPos 起始坐标(世界坐标)
  516. * @param endPos 目标坐标(世界坐标)
  517. * @param height 抛物线最高点高度(相对于起点)
  518. * @param duration 动画时长(秒)
  519. * @param startScale 起始缩放(默认原始大小)
  520. * @param endScale 目标缩放(默认原始大小)
  521. * @param onComplete 完成回调
  522. */
  523. public parabolicMovementWithScale(
  524. n:Node,
  525. endPos: Vec3,
  526. height: number = 2,
  527. duration: number = 1.5,
  528. startScale: Vec3 = Vec3.ONE,
  529. endScale: Vec3 = Vec3.ONE,
  530. onComplete?: (n:Node) => void
  531. ){
  532. console.log("抛物开始....");
  533. let startPos: Vec3 = n.position.clone();
  534. if(startPos.z.toFixed(2) == endPos.z.toFixed(2)) {
  535. console.log("z周线位置抛物....回调了");
  536. tween(n).to(0.25, { worldPosition: endPos.clone(), worldScale: v3(1, 1, 1) }).call(() => {
  537. onComplete?.(n);
  538. }).start();
  539. }else{
  540. //初始化节点状态
  541. n.setWorldPosition(startPos.clone());
  542. n.setScale(startScale);
  543. //计算抛物线顶点
  544. const midPoint = new Vec3(
  545. (startPos.x + endPos.x) / 2,
  546. Math.max(startPos.y, endPos.y) + height,
  547. (startPos.z + endPos.z) / 2
  548. );
  549. //创建组合动画
  550. return tween(n)
  551. .to(duration,
  552. {
  553. worldPosition: endPos,
  554. worldScale: endScale
  555. },{
  556. onUpdate: (target: Node, ratio: number) => {
  557. //1. 计算抛物线位置
  558. const t = ratio;
  559. const invT = 1 - t;
  560. const tempPos = new Vec3();
  561. // XZ轴线性插值
  562. Vec3.lerp(tempPos, startPos, endPos, t);
  563. //Y轴抛物线计算
  564. const y = invT * invT * startPos.y
  565. + 2 * invT * t * midPoint.y
  566. + t * t * endPos.y;
  567. //2. 计算当前缩放比例
  568. const currentScale = new Vec3();
  569. Vec3.lerp(currentScale, startScale, endScale, t);
  570. //更新节点状态
  571. target.worldPosition = new Vec3(tempPos.x, y, tempPos.z);
  572. target.setScale(currentScale);
  573. }
  574. }
  575. )
  576. .call(() => {//最终确保缩放精确
  577. n.setScale(endScale);
  578. console.log("不相同的位置抛物....回调了");
  579. onComplete?.(n);
  580. })
  581. .start();
  582. }
  583. }
  584. }