import { _decorator, Component, Node, Vec3, tween, Tween, UITransform, Camera } from 'cc'; import EventBus, { GameEvents } from '../Core/EventBus'; const { ccclass, property } = _decorator; /** * GameStartMove * * This component is expected to be attached to the main camera node. * It provides high-level animation methods for block selection mode transitions: * 1. enterBlockSelectionMode() – complete animation for entering block selection (camera down + UI slide up) * 2. exitBlockSelectionMode() – complete animation for exiting block selection (camera up + UI slide down) * * Also provides low-level methods for specific use cases: * - moveUpSmooth() – move the camera back up with a smooth tween animation * - slideUpFromBottom() – slide UI up from bottom * - slideDibanDownAndHide() – slide UI down and hide */ @ccclass('GameStartMove') export class GameStartMove extends Component { /** The camera node to move. Defaults to the node the script is attached to. */ @property({ type: Node, tooltip: 'Camera node to move. Leave empty to use the current node.' }) public cameraNode: Node = null; /** Reference line node for camera positioning. When set, camera will move to show this line at the bottom. */ @property({ type: Node, tooltip: 'Reference line node. Camera will move to position this line at the bottom of the view.' }) public referenceLineNode: Node = null; /** Tween duration for the smooth move-up animation. */ @property({ tooltip: 'Duration for the smooth move-up tween (seconds).' }) public moveDuration: number = 0.4; private _originalPos: Vec3 = new Vec3(); /** 需要下滑的 diban 节点(外部拖拽赋值) */ @property({ type: Node, tooltip: 'diban 节点' }) public dibanNode: Node = null; /** Gray遮罩节点 */ // @property({ type: Node, tooltip: 'Canvas/GameLevelUI/Gray节点' }) // public grayNode: Node = null; /** 目标线节点,diban下滑结束后相机将移动到此线的位置 */ @property({ type: Node, tooltip: 'Target line node. Camera will move to show this line at the bottom after diban slide down animation.' }) public targetLineNode: Node = null; private _originalDibanPos: Vec3 = new Vec3(); onLoad () { // Use self node if cameraNode not assigned via the editor. if (!this.cameraNode) { this.cameraNode = this.node; } // Save initial position so we can always restore relative to it. this._originalPos.set(this.cameraNode.position); // Save diban original position if dibanNode is assigned if (this.dibanNode) { this._originalDibanPos.set(this.dibanNode.position); } } /** * Smoothly move the camera back up using a tween to original position. * Requires referenceLineNode to be set. */ public moveUpSmooth () { if (!this.cameraNode) return; const startPos = this.cameraNode.position.clone(); // Calculate movement based on reference line position const moveDistance = this.calculateMoveDistanceToShowLine(); if (moveDistance <= 0) return; const targetPos = new Vec3(startPos.x, startPos.y + moveDistance, startPos.z); // Stop any running tweens on this node to avoid conflicting animations. console.log('GameStartMove.moveUpSmooth 镜头上移,开始执行'); Tween.stopAllByTarget(this.cameraNode); tween(this.cameraNode) .to(this.moveDuration, { position: targetPos }, { easing: 'quadOut' }) .start(); } /** * 重置镜头到原始位置(平滑动画) * 用于返回主界面时确保镜头位置正常 * @param duration 动画时长,默认使用moveDuration */ public resetCameraToOriginalPosition(duration?: number) { if (!this.cameraNode) return; const animationDuration = duration !== undefined ? duration : this.moveDuration; console.log('GameStartMove.resetCameraToOriginalPosition 重置镜头到原始位置:', { currentPos: this.cameraNode.position, originalPos: this._originalPos, duration: animationDuration }); // 停止任何正在运行的镜头动画 Tween.stopAllByTarget(this.cameraNode); // 平滑移动到原始位置 tween(this.cameraNode) .to(animationDuration, { position: this._originalPos.clone() }, { easing: 'quadOut' }) .call(() => { console.log('GameStartMove.resetCameraToOriginalPosition 镜头重置完成'); // 隐藏Gray遮罩 // if (this.grayNode) { // this.grayNode.active = false; // } }) .start(); } /** * 立即重置镜头到原始位置(无动画) * 用于需要立即重置镜头位置的场景 */ public resetCameraToOriginalPositionImmediate() { if (!this.cameraNode) return; console.log('GameStartMove.resetCameraToOriginalPositionImmediate 立即重置镜头到原始位置:', { currentPos: this.cameraNode.position, originalPos: this._originalPos }); // 停止任何正在运行的镜头动画 Tween.stopAllByTarget(this.cameraNode); // 立即设置到原始位置 this.cameraNode.setPosition(this._originalPos.clone()); // 隐藏Gray遮罩 // if (this.grayNode) { // this.grayNode.active = false; // } console.log('GameStartMove.resetCameraToOriginalPositionImmediate 镜头立即重置完成'); } /** * Smoothly move the camera down to show the reference line at bottom. * Requires referenceLineNode to be set. * @param duration 动画时长,默认使用moveDuration */ public moveDownSmooth(duration?: number) { if (!this.cameraNode) return; const startPos = this.cameraNode.position.clone(); // Calculate movement based on reference line position const moveDistance = this.calculateMoveDistanceToShowLine(); if (moveDistance <= 0) return; const targetPos = new Vec3(startPos.x, startPos.y - moveDistance, startPos.z); console.log('GameStartMove.moveDownSmooth 镜头平滑下移,开始执行:', { startPos: startPos, targetPos: targetPos, moveDistance: moveDistance }); // Stop any running tweens on this node to avoid conflicting animations. Tween.stopAllByTarget(this.cameraNode); // 使用传入的duration或默认的moveDuration const animationDuration = duration !== undefined ? duration : this.moveDuration; tween(this.cameraNode) .to(animationDuration, { position: targetPos }, { easing: 'quadOut' }) .call(() => { console.log('GameStartMove.moveDownSmooth 镜头平滑下移完成'); // 镜头下移完成时显示Gray遮罩 // if (this.grayNode) { // this.grayNode.active = true; // } }) .start(); } /* ========= 私有辅助方法 ========= */ /** * Calculate the distance camera needs to move to show the reference line at the bottom of the view. * @returns The movement distance in world units */ private calculateMoveDistanceToShowLine(): number { if (!this.referenceLineNode || !this.cameraNode) { console.warn('GameStartMove.calculateMoveDistanceToShowLine: referenceLineNode or cameraNode not set'); return 0; } // Get the camera component const cameraComponent = this.cameraNode.getComponent(Camera); if (!cameraComponent) { console.warn('GameStartMove.calculateMoveDistanceToShowLine: Camera component not found'); return 0; } // Get the reference line's world position const lineWorldPos = this.referenceLineNode.getWorldPosition(); // Get camera's current world position const cameraWorldPos = this.cameraNode.getWorldPosition(); // Calculate camera's view height in world units // For orthographic camera, orthoHeight represents half of the view height const cameraViewHalfHeight = cameraComponent.orthoHeight; // Calculate the distance to move camera so that the line appears at the bottom // We want: cameraWorldPos.y - cameraViewHalfHeight = lineWorldPos.y // So: moveDistance = cameraWorldPos.y - lineWorldPos.y - cameraViewHalfHeight const moveDistance = cameraWorldPos.y - lineWorldPos.y - cameraViewHalfHeight; console.log('GameStartMove.calculateMoveDistanceToShowLine 计算移动距离:', { lineWorldPos: lineWorldPos, cameraWorldPos: cameraWorldPos, cameraViewHalfHeight: cameraViewHalfHeight, moveDistance: moveDistance }); return Math.max(0, moveDistance); // Ensure we don't move in wrong direction } /** * Calculate the distance camera needs to move to show the target line at the bottom of the view. * @returns The movement distance in world units */ private calculateMoveDistanceToShowTargetLine(): number { if (!this.targetLineNode || !this.cameraNode) { console.warn('GameStartMove.calculateMoveDistanceToShowTargetLine: targetLineNode or cameraNode not set'); return 0; } // Get the camera component const cameraComponent = this.cameraNode.getComponent(Camera); if (!cameraComponent) { console.warn('GameStartMove.calculateMoveDistanceToShowTargetLine: Camera component not found'); return 0; } // Get the target line's world position const targetLineWorldPos = this.targetLineNode.getWorldPosition(); // Get camera's current world position const cameraWorldPos = this.cameraNode.getWorldPosition(); // Calculate camera's view height in world units // For orthographic camera, orthoHeight represents half of the view height const cameraViewHalfHeight = cameraComponent.orthoHeight; // Calculate the distance to move camera so that the target line appears at the bottom // We want: cameraWorldPos.y - cameraViewHalfHeight = targetLineWorldPos.y // So: moveDistance = cameraWorldPos.y - targetLineWorldPos.y - cameraViewHalfHeight const moveDistance = cameraWorldPos.y - targetLineWorldPos.y - cameraViewHalfHeight; console.log('GameStartMove.calculateMoveDistanceToShowTargetLine 计算移动距离:', { targetLineWorldPos: targetLineWorldPos, cameraWorldPos: cameraWorldPos, cameraViewHalfHeight: cameraViewHalfHeight, moveDistance: moveDistance }); return moveDistance; // Allow both positive and negative movement } /** * 平滑移动相机到目标线位置 * @param duration 动画时长,默认 0.3s */ public moveCameraToTargetLineSmooth(duration: number = 0.3) { if (!this.cameraNode || !this.targetLineNode) { console.warn('GameStartMove.moveCameraToTargetLineSmooth: cameraNode or targetLineNode not set'); return; } const moveDistance = this.calculateMoveDistanceToShowTargetLine(); if (moveDistance === 0) { console.log('GameStartMove.moveCameraToTargetLineSmooth: 无需移动相机'); return; } const startPos = this.cameraNode.position.clone(); const targetPos = new Vec3(startPos.x, startPos.y - moveDistance, startPos.z); console.log('GameStartMove.moveCameraToTargetLineSmooth 相机移动到目标线:', { startPos: startPos, targetPos: targetPos, moveDistance: moveDistance }); // 停止任何正在运行的相机动画 Tween.stopAllByTarget(this.cameraNode); // 执行平滑移动动画 tween(this.cameraNode) .to(duration, { position: targetPos }, { easing: 'quadOut' }) .call(() => { console.log('GameStartMove.moveCameraToTargetLineSmooth 相机移动到目标线完成'); }) .start(); } /** * 获取参考线在diban父节点坐标系中的位置和diban高度 * @returns 包含referenceLineLocalPos和dibanHeight的对象,如果节点未设置则返回null */ private getReferenceLinePositionAndDibanHeight(): { referenceLineLocalPos: Vec3, dibanHeight: number } | null { if (!this.dibanNode || !this.referenceLineNode) { return null; } // 获取参考线的世界位置 const referenceLineWorldPos = this.referenceLineNode.getWorldPosition(); // 将参考线的世界位置转换为diban父节点的本地坐标 const referenceLineLocalPos = new Vec3(); this.dibanNode.parent.getComponent(UITransform).convertToNodeSpaceAR(referenceLineWorldPos, referenceLineLocalPos); // 获取diban高度 const dibanUITransform = this.dibanNode.getComponent(UITransform); const dibanHeight = dibanUITransform ? dibanUITransform.height : 0; return { referenceLineLocalPos, dibanHeight }; } /* ========= 高级动画方法 ========= */ /** * 上滑 diban节点,进入备战状态。同时镜头下移。 * @param duration 动画时长,默认 0.3s */ public slideUpFromBottom(duration: number = 0.3) { // 显示diban节点 this.dibanNode.active = true; // 获取参考线位置和diban高度 const positionData = this.getReferenceLinePositionAndDibanHeight(); if (!positionData) { return; } const { referenceLineLocalPos, dibanHeight } = positionData; // 计算diban的初始位置:diban顶部与参考线对齐 const startPos = new Vec3(this.dibanNode.position.x, referenceLineLocalPos.y - dibanHeight / 2, this.dibanNode.position.z); // 计算目标位置:diban底部与参考线对齐 const targetPos = new Vec3(this.dibanNode.position.x, referenceLineLocalPos.y + dibanHeight / 2, this.dibanNode.position.z); // 设置diban初始位置 this.dibanNode.setPosition(startPos); // 停止任何正在运行的动画 Tween.stopAllByTarget(this.dibanNode); // 执行上滑动画 tween(this.dibanNode) .to(duration, { position: targetPos }, { easing: 'quadInOut' }) .call(() => { const animationEndTime = Date.now(); // 动画完成后发送进入备战状态事件,触发游戏暂停 console.log('[GameStartMove] diban上滑完成,发送游戏暂停事件'); EventBus.getInstance().emit(GameEvents.GAME_PAUSE); }) .start(); // 同时执行camera平滑下移动画 this.moveDownSmooth(duration); } /** * 下滑 diban节点,结束备战进入playing 状态。 * @param duration 动画时长,默认 0.3s */ public slideDibanDownAndHide(duration: number = 0.3) { // 使用装饰器属性中的diban节点 if (!this.dibanNode) { console.warn('GameStartMove.slideDibanDownAndHide diban节点未设置'); return; } // 检查参考线节点 if (!this.referenceLineNode) { console.warn('GameStartMove.slideDibanDownAndHide 参考线节点未设置'); return; } // 获取当前位置 const currentPos = this.dibanNode.position.clone(); console.log('GameStartMove.slideDibanDownAndHide diban当前位置:', currentPos); // 获取参考线位置和diban高度 const positionData = this.getReferenceLinePositionAndDibanHeight(); if (!positionData) { return; } const { referenceLineLocalPos, dibanHeight } = positionData; // 计算目标位置:diban顶部与参考线对齐(下滑到初始位置) const targetPos = new Vec3(currentPos.x, referenceLineLocalPos.y - dibanHeight / 2, currentPos.z); console.log('GameStartMove.slideDibanDownAndHide diban目标位置:', targetPos); // 停止任何正在运行的动画 Tween.stopAllByTarget(this.dibanNode); // 执行下滑动画 tween(this.dibanNode) .to(duration, { position: targetPos }, { easing: 'quadOut' }) .call(() => { console.log('GameStartMove.slideDibanDownAndHide diban下滑动画完成'); // 动画完成时隐藏Gray遮罩 // if (this.grayNode) { // this.grayNode.active = false; // } // 动画完成后隐藏diban节点 this.dibanNode.active = false; // 动画完成后发送进入战斗状态事件,恢复游戏 console.log('[GameStartMove] diban下滑完成,发送游戏恢复事件'); EventBus.getInstance().emit(GameEvents.GAME_RESUME); }) .start(); // 在diban下滑动画开始的同时,相机平滑移动到目标线位置 this.moveCameraToTargetLineSmooth(0.3); console.log('GameStartMove.slideDibanDownAndHide diban下滑动画已启动'); } }