import { _decorator,Node, geometry, Camera, screen, EventTouch, Prefab, instantiate, v3, tween, RigidBody, MeshRenderer, Vec3, find, input, Input, Tween, PhysicsSystem, view } from 'cc'; import { TileItem } from './TileItem'; import { Main } from './Main'; import { ResUtil } from '../core/utils/ResUtil'; import { ComponentEx } from '../core/component/ComponentEx'; import { audioMgr } from '../core/manager/AudioManager'; import BusyLoadingManager, { BUSY_TYPE } from '../core/manager/BusyLoadingManager'; import Data from '../core/manager/Data'; import WindowManager from '../core/manager/WindowManager'; import MsgHints from '../core/utils/MsgHints'; import Utils from '../core/utils/Utils'; import { DrawStarLayer } from '../gameui/DrawStarLayer'; import { eventEmitter } from '../core/event/EventEmitter'; import platformSystem from '../platform/platformSystem'; import { GameConst, ITEM_TYPE } from '../core/common/GameConst'; import { levelsData } from '../user/LevelsData'; import { PoolManager } from '../core/manager/PoolManager'; const { Ray } = geometry; const { ccclass, property, executeInEditMode } = _decorator; @ccclass('GameNode') export class GameNode extends ComponentEx { public lock: boolean = false; public collectpos: Vec3[] = []; public collectTiles: Node[] = []; public allTiles: Node[] = []; public optionTiles: Node[] = []; public start() { setTimeout(() => { this.initWall(); this.collectpos = []; this.GetNode("collectbox").children.map(a => { this.collectpos.push(a.worldPosition); a.active = false; }) }, 100); eventEmitter.register(this, GameConst.CLEAR_ALL_BOX, () => { console.log("清空盒子") this.lock=false setTimeout(() => { for (let i = 0; i < this.collectTiles.length; ++i) { this.pushBask(this.collectTiles[i].name); } this.collectTiles.map(a => { a.destroy(); }) this.collectTiles = []; }, 500); }) //提示 eventEmitter.register(this, GameConst.USE_ITEM_HINT,this.prompt.bind(this)) } onEnable() { input.on(Input.EventType.TOUCH_START, this.onTouchStart, this); input.on(Input.EventType.TOUCH_END, this.onTouchEnd, this); input.on(Input.EventType.TOUCH_MOVE, this.onTouchMove, this); } onDisable() { input.off(Input.EventType.TOUCH_START, this.onTouchStart, this); input.off(Input.EventType.TOUCH_END, this.onTouchEnd, this); input.off(Input.EventType.TOUCH_MOVE, this.onTouchMove, this); } moveToRightPos() { this.collectTiles.map((a, i) => { let tileItem = a.getComponent(TileItem) Tween.stopAllByTarget(a); tileItem.auto_rotation = true; tween(a).to(0.3, { worldPosition: this.collectpos[i].clone(), worldScale: v3(1, 1, 1) }).call(() => { if (tileItem.removed) { Utils.remove(this.optionTiles,a); tween(a).to(0.25, { worldPosition: tileItem.removedPos }, { easing: 'backIn' }).call(async () => { //最中央的播放移除特效 if (tileItem.playRmovedEff) { //增加星星 let pos = this.GetNode("Main Camera").getComponent(Camera).convertToUINode(a.worldPosition, find("Canvas")); let node_ani = await ResUtil.playSkAni("spine/effect_hecheng", "effect", find("Canvas"), pos); audioMgr.playOneShot(GameConst.audios.starCollect); platformSystem.platform.vibrateShort(); Utils.flyAnim(ITEM_TYPE.Star, node_ani, Main.I._GameUI.starNode, 1, 0, (b) => { if (b) { Main.I._GameUI.star++; } }); } PoolManager.putNode(a); this.moveToRightPos() this.checkResult() }).start() } }).start(); }) } chooseTile(tile: Node) { if (!tile || !tile.isValid) return; let tileItem = tile.getComponent(TileItem); if (!tileItem) return; if (tileItem.isMoving) return; let chooseName = tile.name; this.optionTiles.push(tile); // 删除点击对象从列表 tileItem.destoryCollider(); for (var i = 0; i < this.allTiles.length; ++i) { if (this.allTiles[i] == tile) { this.allTiles.splice(i, 1); break; } } // 计算目标位置 let targetPos: Vec3; let bInsert = false; for (var i = this.collectTiles.length - 1; i >= 0; --i) { if (this.collectTiles[i].name == chooseName) { targetPos = this.collectpos[i + 1]?.clone() || this.collectpos[this.collectpos.length - 1].clone(); this.collectTiles.splice(i + 1, 0, tile); bInsert = true; break; } } if (!bInsert) { targetPos = this.collectpos[this.collectTiles.length]?.clone() || this.collectpos[0].clone(); this.collectTiles.push(tile); } //添加上抛动画 标记物品为移动中 tileItem.isMoving = true; const startPos = tile.worldPosition.clone(); const midHeight = 10; // 上抛高度 const duration = 0.2; // 动画时长 this.parabolicMovementWithScale( tile, startPos, targetPos, midHeight, duration, tile.scale.clone(), v3(1, 1, 1), (n: Node) => { this.moveToRightPos() tile.getComponent(TileItem).isMoving = false; //动画完成后执行原有逻辑 let tmp = this.collectTiles.filter(a => { return a.name == chooseName; }) if (tmp.length >= 3) { for (var i = this.collectTiles.length - 1; i >= 0; --i) { if (this.collectTiles[i].name == chooseName) { let delNode = this.collectTiles[i]; delNode.getComponent(TileItem).removed = true; this.collectTiles.splice(i, 1); delNode.getComponent(TileItem).removedPos = tmp[1].worldPosition; if (delNode == tmp[1]) delNode.getComponent(TileItem).playRmovedEff = true; } } } this.checkResult(); } ); } checkResult() { if(this.lock)return //检测胜利和失败 if (this.collectTiles.length >= 7) { this.lock=true if (!WindowManager.ins.isShow("OutOfBoxLayer")) WindowManager.ins.open("OutOfBoxLayer"); } else if (this.collectTiles.length == 0 && this.allTiles.length == 0) { this.lock=true WindowManager.ins.open("DrawStarLayer").then((com: DrawStarLayer) => { com.setStar(Main.I._GameUI.star); }) } } private touchTileItem: TileItem = null; onTouchStart(event: EventTouch) { if (Main.I._GameUI.pasue) return; if (Data.user.useMagnet) return; if (this.allTiles.length == 0) return; if (this.collectTiles.length >= 7) return; const p = event.getLocation(); let camera = this.GetNode("Main Camera").getComponent(Camera); const r = new Ray(); camera?.screenPointToRay(p.x, p.y, r); let b = PhysicsSystem.instance.raycastClosest(r, 1); if (b) { let collider = PhysicsSystem.instance.raycastClosestResult.collider; if (!collider || !collider.node || !collider.node.parent) return; let item = collider.node.parent.getComponent(TileItem); if (!item || !item.node || !item.node.isValid) return; item.enableCollider = false; let pos = item.node.getWorldPosition(); pos.y += 0.3; item.node.setWorldPosition(pos); this.touchTileItem = item; } } onTouchEnd(event: EventTouch) { if (Main.I._GameUI.pasue) return; if (Data.user.useMagnet) return; if (!this.touchTileItem) return; if (this.allTiles.length == 0) return; if (this.collectTiles.length >= 7) { return; } const p = event.getLocation(); let camera = this.GetNode("Main Camera").getComponent(Camera); const r = new Ray(); camera?.screenPointToRay(p.x, p.y, r); let b = PhysicsSystem.instance.raycastClosest(r, 1); if (b) { let collider = PhysicsSystem.instance.raycastClosestResult.collider; // if(!collider.getComponent(RigidBody))return; let item = collider.node.parent.getComponent(TileItem); if (item && item == this.touchTileItem) { platformSystem.platform.vibrateShort(); this.chooseTile(item.node); this.touchTileItem = null; audioMgr.playOneShot(GameConst.audios.tap); }else { this.touchTileItem.enableCollider = true; } } } onTouchMove(event: EventTouch) { if (Main.I._GameUI.pasue) return; if (Data.user.useMagnet) return; if (this.allTiles.length == 0) return; if (this.collectTiles.length >= 7) { return; } const p = event.getLocation(); let camera = this.GetNode("Main Camera").getComponent(Camera); const r = new Ray(); camera?.screenPointToRay(p.x, p.y, r); let b = PhysicsSystem.instance.raycastClosest(r, 1) if (b) { let collider = PhysicsSystem.instance.raycastClosestResult.collider // if(!collider.getComponent(RigidBody))return; let item = collider.node.parent.getComponent(TileItem); if (item) { if (this.touchTileItem && item == this.touchTileItem) { }else { if (this.touchTileItem) this.touchTileItem.enableCollider = true; item.enableCollider = false; let pos = item.node.getWorldPosition(); pos.y += 0.3; // console.log("切换") item.node.setWorldPosition(pos); this.touchTileItem = item; } } else { if (this.touchTileItem) { // console.log("无目标") this.touchTileItem.enableCollider = true; this.touchTileItem = null; } } } } initWall() { if (!PhysicsSystem.instance) return; const OFFSET = 25; let camera = this.GetNode("Main Camera").getComponent(Camera); const r = new Ray(); let size = screen.windowSize; //左边墙 camera?.screenPointToRay(OFFSET, size.height / 2, r); if (PhysicsSystem.instance.raycastClosest(r)) { const result = PhysicsSystem.instance.raycastClosestResult; this.GetNode("PlaneLeft").setWorldPosition(result.hitPoint); } //右边墙 camera?.screenPointToRay(size.width - OFFSET, size.height / 2, r); if (PhysicsSystem.instance.raycastClosest(r)) { const result = PhysicsSystem.instance.raycastClosestResult; this.GetNode("PlaneRight").setWorldPosition(result.hitPoint); } //上边墙 camera?.screenPointToRay(size.width / 2, size.height - OFFSET * 4, r); if (PhysicsSystem.instance.raycastClosest(r)) { const result = PhysicsSystem.instance.raycastClosestResult; this.GetNode("PlaneTop").setWorldPosition(result.hitPoint); } let minx = this.GetNode("PlaneLeft").worldPosition.x; let maxx = this.GetNode("PlaneRight").worldPosition.x; let scale = (maxx - minx) / 8.461091041564941; this.GetNode("bottom").scale = v3(scale, scale, scale); //格子位置 camera?.screenPointToRay(size.width / 2, 10, r); if (PhysicsSystem.instance.raycastClosest(r)) { const result = PhysicsSystem.instance.raycastClosestResult; //result.hitPoint let p: Vec3 = new Vec3(result.hitPoint.x,4,result.hitPoint.z); this.GetNode("bottom").setWorldPosition(p); } //下边墙 this.GetNode("PlaneDown").setWorldPosition(this.GetNode("bottom").worldPosition.add3f(0, 0, -2.5 * scale)); } async createTiles() { let Margin = 0 let minx = this.GetNode("PlaneLeft").worldPosition.x + Margin; let maxx = this.GetNode("PlaneRight").worldPosition.x - Margin; let maxz = this.GetNode("PlaneDown").worldPosition.z + Margin; let minz = this.GetNode("PlaneTop").worldPosition.z - Margin; let obj = levelsData.getCurLevelInfo(); //数量 let tileCount = Math.floor(obj.count / 3); let names:Array = levelsData.getModesNames(); let tilepools = Utils.getRandomElements(names,obj.kind); tilepools = Utils.extendArray(tilepools,tileCount); Main.I._GameUI.pasue = true; BusyLoadingManager.ins.addBusy(BUSY_TYPE.RES); for (let i = 0; i < tileCount; i++) { let ret = await ResUtil.loadModelPrefabName(tilepools[i]) as Prefab; if (!ret) continue; //获取节点下的 MeshRenderer 组件 设置阴影投射模式 (ret.data.getComponentInChildren(MeshRenderer) as MeshRenderer).shadowCastingMode = MeshRenderer.ShadowCastingMode.ON; for (let j = 0; j < 3; j++) { const globalIndex = (i * 3 + j) % 9; let lnode = this.GetNode(`l${globalIndex}`); tween(this.node).delay(j * 0.03).call(() => { let nomal_node: Node = instantiate(ret); nomal_node.name = ret.name; let tile = nomal_node.addComponent(TileItem); let rigid = tile.addCollider(); nomal_node.scale = v3(1.25, 1.25, 1.25) if (Data.user.lv == 1) { nomal_node.setWorldPosition(lnode.getWorldPosition()); nomal_node.parent = this.node; this.allTiles.push(nomal_node); }else{ let p = v3(Utils.getRandomInt(minx / 2, maxx / 2), Utils.getRandom(12, 15), Utils.getRandomInt(minz / 2, maxz / 2)); nomal_node.setWorldRotationFromEuler(Utils.getRandom(0, 300), Utils.getRandom(0, 300), Utils.getRandom(0, 300)) nomal_node.setWorldPosition(p); nomal_node.parent = this.node; rigid.applyImpulse(v3(0, 3, 0)) this.allTiles.push(nomal_node); } }).start() } if (i == tileCount - 1) { BusyLoadingManager.ins.removeBusy(BUSY_TYPE.RES); Main.I._GameUI.pasue = false; } } setTimeout(() => { if (Data.user.useMagnet) { Main.I._GameUI.pasue = true; Data.user.magnet--; let p = this.GetNode("magnet").worldPosition; this.GetNode("magnet").worldPosition = v3(-10, p.y, p.z); tween(this.GetNode("magnet")).to(0.5, { worldPosition: v3(0, p.y, p.z) }).call(async () => { let arr = []; for (var i = this.allTiles.length - 1; i >= 0; --i) { let pick = false; let item = this.allTiles[i]; let tmp: Node = arr[0]; if(tmp){ if (tmp.name == item.name && arr.length < 3) { arr.push(item); pick = true; } }else{ arr.push(item); pick = true; } if(pick) { let tile: TileItem = this.allTiles[i].getComponent(TileItem); tile.destoryCollider(); this.GetNode("magnet_content").addChild(tile.node); tween(tile.node).to(0.5, { worldPosition: this.GetNode(arr.length % 2 ? "m_left" : "r_left").worldPosition }).start(); this.allTiles.splice(i, 1); console.log("删除", tile.name, this.allTiles.length) } } Data.user.useMagnet = false; }).delay(1.5).to(0.5, { worldPosition: v3(10, p.y, p.z) }).call(() => { this.GetNode("magnet_content").removeAllChildren(); Main.I._GameUI.pasue = false; if (Data.user.useTime && Data.user.time > 0) eventEmitter.dispatch(GameConst.USE_ITEM_TIME) }).start(); }else { if (Data.user.useTime && Data.user.time > 0){ eventEmitter.dispatch(GameConst.USE_ITEM_TIME)} } }, 2000); } /** * 提示 */ restart() { this.allTiles.map(a => a.destroy()); this.allTiles = []; Main.I._GameUI.star = 0; this.collectTiles.map(a => a.destroy()); this.collectTiles = []; this.createTiles(); this.lock=false } /**移除*/ public remove(){ if (this.optionTiles.length === 0 || this.lock) return; // 获取最后一个收集的物品 const lastItem = this.optionTiles.pop(); if (!lastItem) return; Utils.remove(this.collectTiles,lastItem); const tile = lastItem.getComponent(TileItem); tile.removed = false; tile.auto_rotation = false; //播放撤销动画 Tween.stopAllByTarget(lastItem); //const globalIndex = Math.floor(Math.random() * 9); let lnode = this.GetNode(`l${4}`); let p:Vec3 = lnode.position.clone(); tween(tile.node) .to(0.35, { worldPosition: new Vec3(p.x,p.y + 1,p.z), worldScale: v3(1.25, 1.25, 1.25) }) .call(() => { tile.addCollider(); tile.enableCollider = true; this.allTiles.push(lastItem); //向上施加力 然后在向下重力效果 tile.rigid.applyImpulse(v3(0, 3, 0)) this.moveToRightPos(); // 重新排列收集框中的物品 }) .start(); } /**乱序*/ public mess(){ if(this.allTiles.length === 0 || this.lock) return; this.allTiles.forEach((item, index) => { const tileItem = item.getComponent(TileItem); tileItem.auto_rotation = false; tileItem.enableCollider = true; Tween.stopAllByTarget(item); tween(item) .to(0.3, { worldPosition: this.GetNode("l4").worldPosition.clone().add(v3(0, 1, 0)), worldScale: v3(1.25, 1.25, 1.25) }) .call(() => { const rigid = tileItem.addCollider(); rigid.applyImpulse(v3( Utils.getRandom(-3, 3), Utils.getRandom(5, 8), Utils.getRandom(-3, 3) )); item.setWorldRotationFromEuler(Utils.getRandom(0, 300), Utils.getRandom(0, 300), Utils.getRandom(0, 300)) }) .start(); }); //audioManager.playOneShot(GameConst.audios.shuffle); platformSystem.platform.vibrateShort(); } /**凑齐*/ public prompt(){ if(this.collectTiles.length <= 0 || Data.user.hint <= 0)return; let obj = {} for (var i = 0; i < this.collectTiles.length; ++i) { let name = this.collectTiles[i].name; if (!obj[name]) obj[name] = 0; obj[name]++; } let hint_succ = false; let bFind = false; let hint_name = "" for (const key in obj) { if (obj[key] == 2) { hint_name = key; bFind = true; break; } } //找到两个一样 if (bFind) { console.log("找到两个一样") for (let i = 0; i < this.allTiles.length; ++i) { if (this.allTiles[i].name == hint_name) { this.chooseTile(this.allTiles[i]); break; } } hint_succ = true; }else { if (this.collectTiles.length >= 5) { MsgHints.show("未找到合适物品"); return }else { hint_name = this.collectTiles[0] ? this.collectTiles[0].name : this.allTiles[0].name; let lsit = this.allTiles.filter(a => { return a.name == hint_name; }) lsit.map((a, i) => { setTimeout(() => { this.chooseTile(a); }, 350 * i); }) hint_succ = true; } } if (hint_succ) { Data.user.hint--; } } //放回场景中 async pushBask(name: string) { let Margin = 0; let maxz = this.GetNode("PlaneDown").worldPosition.z + Margin; let minz = this.GetNode("PlaneTop").worldPosition.z - Margin; let minx = this.GetNode("PlaneLeft").worldPosition.x + Margin; let maxx = this.GetNode("PlaneRight").worldPosition.x - Margin; let ret = await ResUtil.loadModelPrefabName(name) as Prefab; let nomal_node: Node = instantiate(ret); nomal_node.name = name; let tile = nomal_node.addComponent(TileItem); let rigid = tile.addCollider(); nomal_node.scale = v3(1.25,1.25, 1.25) nomal_node.parent = this.node; rigid.applyImpulse(v3(0, 3, 0)) let p = v3(minx + (maxx - minx) / 2, Utils.getRandom(12, 15), minz + (maxz - minz) / 2); nomal_node.setWorldRotationFromEuler(Utils.getRandom(0, 300), Utils.getRandom(0, 300), Utils.getRandom(0, 300)) nomal_node.setWorldPosition(p); this.allTiles.push(nomal_node); } /** * 抛物线运动工具(带缩放动画) * @param node 要移动的节点 * @param startPos 起始坐标(世界坐标) * @param endPos 目标坐标(世界坐标) * @param height 抛物线最高点高度(相对于起点) * @param duration 动画时长(秒) * @param startScale 起始缩放(默认原始大小) * @param endScale 目标缩放(默认原始大小) * @param onComplete 完成回调 */ public parabolicMovementWithScale( node: Node, startPos: Vec3, endPos: Vec3, height: number = 2, duration: number = 1.5, startScale: Vec3 = Vec3.ONE, endScale: Vec3 = Vec3.ONE, onComplete?: (n:Node) => void ): Tween { if(Vec3.equals(startPos, endPos)) { onComplete?.(node); }else{ //初始化节点状态 node.setWorldPosition(startPos.clone()); node.setScale(startScale); //计算抛物线顶点 const midPoint = new Vec3( (startPos.x + endPos.x) / 2, Math.max(startPos.y, endPos.y) + height, (startPos.z + endPos.z) / 2 ); //创建组合动画 return tween(node) .to(duration, { worldPosition: endPos, worldScale: endScale }, { onUpdate: (target: Node, ratio: number) => { //1. 计算抛物线位置 const t = ratio; const invT = 1 - t; const tempPos = new Vec3(); // XZ轴线性插值 Vec3.lerp(tempPos, startPos, endPos, t); //Y轴抛物线计算 const y = invT * invT * startPos.y + 2 * invT * t * midPoint.y + t * t * endPos.y; //2. 计算当前缩放比例 const currentScale = new Vec3(); Vec3.lerp(currentScale, startScale, endScale, t); //更新节点状态 target.worldPosition = new Vec3(tempPos.x, y, tempPos.z); target.setScale(currentScale); } } ) .call(() => { // 最终确保缩放精确 node.setScale(endScale); onComplete?.(node); }) .start(); } } }