GameNode.ts 26 KB

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