GameStartMove.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. import { _decorator, Component, Node, Vec3, tween, Tween, UITransform, Camera } from 'cc';
  2. const { ccclass, property } = _decorator;
  3. /**
  4. * GameStartMove
  5. *
  6. * This component is expected to be attached to the main camera node.
  7. * It provides high-level animation methods for block selection mode transitions:
  8. * 1. enterBlockSelectionMode() – complete animation for entering block selection (camera down + UI slide up)
  9. * 2. exitBlockSelectionMode() – complete animation for exiting block selection (camera up + UI slide down)
  10. *
  11. * Also provides low-level methods for specific use cases:
  12. * - moveDownInstant() – instantly move the camera down by a fixed offset
  13. * - moveUpSmooth() – move the camera back up with a smooth tween animation
  14. * - slideUpFromBottom() – slide UI up from bottom
  15. * - slideDibanDownAndHide() – slide UI down and hide
  16. */
  17. @ccclass('GameStartMove')
  18. export class GameStartMove extends Component {
  19. /** The camera node to move. Defaults to the node the script is attached to. */
  20. @property({
  21. type: Node,
  22. tooltip: 'Camera node to move. Leave empty to use the current node.'
  23. })
  24. public cameraNode: Node = null;
  25. /** Reference line node for camera positioning. When set, camera will move to show this line at the bottom. */
  26. @property({
  27. type: Node,
  28. tooltip: 'Reference line node. Camera will move to position this line at the bottom of the view.'
  29. })
  30. public referenceLineNode: Node = null;
  31. /** Tween duration for the smooth move-up animation. */
  32. @property({ tooltip: 'Duration for the smooth move-up tween (seconds).' })
  33. public moveDuration: number = 0.4;
  34. private _originalPos: Vec3 = new Vec3();
  35. /** 需要下滑的 diban 节点(外部拖拽赋值) */
  36. @property({ type: Node, tooltip: 'diban 节点' })
  37. public dibanNode: Node = null;
  38. /** Gray遮罩节点 */
  39. @property({ type: Node, tooltip: 'Canvas/GameLevelUI/Gray节点' })
  40. public grayNode: Node = null;
  41. /** 目标线节点,diban下滑结束后相机将移动到此线的位置 */
  42. @property({
  43. type: Node,
  44. tooltip: 'Target line node. Camera will move to show this line at the bottom after diban slide down animation.'
  45. })
  46. public targetLineNode: Node = null;
  47. private _originalDibanPos: Vec3 = new Vec3();
  48. onLoad () {
  49. // Use self node if cameraNode not assigned via the editor.
  50. if (!this.cameraNode) {
  51. this.cameraNode = this.node;
  52. }
  53. // Save initial position so we can always restore relative to it.
  54. this._originalPos.set(this.cameraNode.position);
  55. // Save diban original position if dibanNode is assigned
  56. if (this.dibanNode) {
  57. this._originalDibanPos.set(this.dibanNode.position);
  58. }
  59. }
  60. /**
  61. * Instantly move the camera down to show the reference line at bottom.
  62. * Requires referenceLineNode to be set.
  63. */
  64. public moveDownInstant () {
  65. if (!this.cameraNode) return;
  66. const pos = this.cameraNode.position.clone();
  67. // Calculate movement based on reference line position
  68. const moveDistance = this.calculateMoveDistanceToShowLine();
  69. if (moveDistance > 0) {
  70. pos.y -= moveDistance;
  71. this.cameraNode.setPosition(pos);
  72. console.log('GameStartMove.moveDownInstant 镜头下移,当前位置:', pos);
  73. }
  74. // 镜头下移时显示Gray遮罩
  75. if (this.grayNode) {
  76. this.grayNode.active = true;
  77. }
  78. }
  79. /**
  80. * Smoothly move the camera back up using a tween to original position.
  81. * Requires referenceLineNode to be set.
  82. */
  83. public moveUpSmooth () {
  84. if (!this.cameraNode) return;
  85. const startPos = this.cameraNode.position.clone();
  86. // Calculate movement based on reference line position
  87. const moveDistance = this.calculateMoveDistanceToShowLine();
  88. if (moveDistance <= 0) return;
  89. const targetPos = new Vec3(startPos.x, startPos.y + moveDistance, startPos.z);
  90. // Stop any running tweens on this node to avoid conflicting animations.
  91. console.log('GameStartMove.moveUpSmooth 镜头上移,开始执行');
  92. Tween.stopAllByTarget(this.cameraNode);
  93. tween(this.cameraNode)
  94. .to(this.moveDuration, { position: targetPos }, { easing: 'quadOut' })
  95. .start();
  96. }
  97. /**
  98. * Smoothly move the camera down to show the reference line at bottom.
  99. * Requires referenceLineNode to be set.
  100. * @param duration 动画时长,默认使用moveDuration
  101. */
  102. public moveDownSmooth(duration?: number) {
  103. if (!this.cameraNode) return;
  104. const startPos = this.cameraNode.position.clone();
  105. // Calculate movement based on reference line position
  106. const moveDistance = this.calculateMoveDistanceToShowLine();
  107. if (moveDistance <= 0) return;
  108. const targetPos = new Vec3(startPos.x, startPos.y - moveDistance, startPos.z);
  109. console.log('GameStartMove.moveDownSmooth 镜头平滑下移,开始执行:', {
  110. startPos: startPos,
  111. targetPos: targetPos,
  112. moveDistance: moveDistance
  113. });
  114. // Stop any running tweens on this node to avoid conflicting animations.
  115. Tween.stopAllByTarget(this.cameraNode);
  116. // 使用传入的duration或默认的moveDuration
  117. const animationDuration = duration !== undefined ? duration : this.moveDuration;
  118. tween(this.cameraNode)
  119. .to(animationDuration, { position: targetPos }, { easing: 'quadOut' })
  120. .call(() => {
  121. console.log('GameStartMove.moveDownSmooth 镜头平滑下移完成');
  122. // 镜头下移完成时显示Gray遮罩
  123. if (this.grayNode) {
  124. this.grayNode.active = true;
  125. }
  126. })
  127. .start();
  128. }
  129. /* ========= 私有辅助方法 ========= */
  130. /**
  131. * Calculate the distance camera needs to move to show the reference line at the bottom of the view.
  132. * @returns The movement distance in world units
  133. */
  134. private calculateMoveDistanceToShowLine(): number {
  135. if (!this.referenceLineNode || !this.cameraNode) {
  136. console.warn('GameStartMove.calculateMoveDistanceToShowLine: referenceLineNode or cameraNode not set');
  137. return 0;
  138. }
  139. // Get the camera component
  140. const cameraComponent = this.cameraNode.getComponent(Camera);
  141. if (!cameraComponent) {
  142. console.warn('GameStartMove.calculateMoveDistanceToShowLine: Camera component not found');
  143. return 0;
  144. }
  145. // Get the reference line's world position
  146. const lineWorldPos = this.referenceLineNode.getWorldPosition();
  147. // Get camera's current world position
  148. const cameraWorldPos = this.cameraNode.getWorldPosition();
  149. // Calculate camera's view height in world units
  150. // For orthographic camera, orthoHeight represents half of the view height
  151. const cameraViewHalfHeight = cameraComponent.orthoHeight;
  152. // Calculate the distance to move camera so that the line appears at the bottom
  153. // We want: cameraWorldPos.y - cameraViewHalfHeight = lineWorldPos.y
  154. // So: moveDistance = cameraWorldPos.y - lineWorldPos.y - cameraViewHalfHeight
  155. const moveDistance = cameraWorldPos.y - lineWorldPos.y - cameraViewHalfHeight;
  156. console.log('GameStartMove.calculateMoveDistanceToShowLine 计算移动距离:', {
  157. lineWorldPos: lineWorldPos,
  158. cameraWorldPos: cameraWorldPos,
  159. cameraViewHalfHeight: cameraViewHalfHeight,
  160. moveDistance: moveDistance
  161. });
  162. return Math.max(0, moveDistance); // Ensure we don't move in wrong direction
  163. }
  164. /**
  165. * Calculate the distance camera needs to move to show the target line at the bottom of the view.
  166. * @returns The movement distance in world units
  167. */
  168. private calculateMoveDistanceToShowTargetLine(): number {
  169. if (!this.targetLineNode || !this.cameraNode) {
  170. console.warn('GameStartMove.calculateMoveDistanceToShowTargetLine: targetLineNode or cameraNode not set');
  171. return 0;
  172. }
  173. // Get the camera component
  174. const cameraComponent = this.cameraNode.getComponent(Camera);
  175. if (!cameraComponent) {
  176. console.warn('GameStartMove.calculateMoveDistanceToShowTargetLine: Camera component not found');
  177. return 0;
  178. }
  179. // Get the target line's world position
  180. const targetLineWorldPos = this.targetLineNode.getWorldPosition();
  181. // Get camera's current world position
  182. const cameraWorldPos = this.cameraNode.getWorldPosition();
  183. // Calculate camera's view height in world units
  184. // For orthographic camera, orthoHeight represents half of the view height
  185. const cameraViewHalfHeight = cameraComponent.orthoHeight;
  186. // Calculate the distance to move camera so that the target line appears at the bottom
  187. // We want: cameraWorldPos.y - cameraViewHalfHeight = targetLineWorldPos.y
  188. // So: moveDistance = cameraWorldPos.y - targetLineWorldPos.y - cameraViewHalfHeight
  189. const moveDistance = cameraWorldPos.y - targetLineWorldPos.y - cameraViewHalfHeight;
  190. console.log('GameStartMove.calculateMoveDistanceToShowTargetLine 计算移动距离:', {
  191. targetLineWorldPos: targetLineWorldPos,
  192. cameraWorldPos: cameraWorldPos,
  193. cameraViewHalfHeight: cameraViewHalfHeight,
  194. moveDistance: moveDistance
  195. });
  196. return moveDistance; // Allow both positive and negative movement
  197. }
  198. /**
  199. * 平滑移动相机到目标线位置
  200. * @param duration 动画时长,默认 0.3s
  201. */
  202. public moveCameraToTargetLineSmooth(duration: number = 0.3) {
  203. if (!this.cameraNode || !this.targetLineNode) {
  204. console.warn('GameStartMove.moveCameraToTargetLineSmooth: cameraNode or targetLineNode not set');
  205. return;
  206. }
  207. const moveDistance = this.calculateMoveDistanceToShowTargetLine();
  208. if (moveDistance === 0) {
  209. console.log('GameStartMove.moveCameraToTargetLineSmooth: 无需移动相机');
  210. return;
  211. }
  212. const startPos = this.cameraNode.position.clone();
  213. const targetPos = new Vec3(startPos.x, startPos.y - moveDistance, startPos.z);
  214. console.log('GameStartMove.moveCameraToTargetLineSmooth 相机移动到目标线:', {
  215. startPos: startPos,
  216. targetPos: targetPos,
  217. moveDistance: moveDistance
  218. });
  219. // 停止任何正在运行的相机动画
  220. Tween.stopAllByTarget(this.cameraNode);
  221. // 执行平滑移动动画
  222. tween(this.cameraNode)
  223. .to(duration, { position: targetPos }, { easing: 'quadOut' })
  224. .call(() => {
  225. console.log('GameStartMove.moveCameraToTargetLineSmooth 相机移动到目标线完成');
  226. })
  227. .start();
  228. }
  229. /**
  230. * 获取参考线在diban父节点坐标系中的位置和diban高度
  231. * @returns 包含referenceLineLocalPos和dibanHeight的对象,如果节点未设置则返回null
  232. */
  233. private getReferenceLinePositionAndDibanHeight(): { referenceLineLocalPos: Vec3, dibanHeight: number } | null {
  234. if (!this.dibanNode || !this.referenceLineNode) {
  235. return null;
  236. }
  237. // 获取参考线的世界位置
  238. const referenceLineWorldPos = this.referenceLineNode.getWorldPosition();
  239. // 将参考线的世界位置转换为diban父节点的本地坐标
  240. const referenceLineLocalPos = new Vec3();
  241. this.dibanNode.parent.getComponent(UITransform).convertToNodeSpaceAR(referenceLineWorldPos, referenceLineLocalPos);
  242. // 获取diban高度
  243. const dibanUITransform = this.dibanNode.getComponent(UITransform);
  244. const dibanHeight = dibanUITransform ? dibanUITransform.height : 0;
  245. return { referenceLineLocalPos, dibanHeight };
  246. }
  247. /* ========= 高级动画方法 ========= */
  248. /**
  249. * 上滑 diban节点,进入备战状态。同时镜头下移。
  250. * @param duration 动画时长,默认 0.3s
  251. */
  252. public slideUpFromBottom(duration: number = 0.3) {
  253. // 显示diban节点
  254. this.dibanNode.active = true;
  255. // 获取参考线位置和diban高度
  256. const positionData = this.getReferenceLinePositionAndDibanHeight();
  257. if (!positionData) {
  258. return;
  259. }
  260. const { referenceLineLocalPos, dibanHeight } = positionData;
  261. // 计算diban的初始位置:diban顶部与参考线对齐
  262. const startPos = new Vec3(this.dibanNode.position.x, referenceLineLocalPos.y - dibanHeight / 2, this.dibanNode.position.z);
  263. // 计算目标位置:diban底部与参考线对齐
  264. const targetPos = new Vec3(this.dibanNode.position.x, referenceLineLocalPos.y + dibanHeight / 2, this.dibanNode.position.z);
  265. // 设置diban初始位置
  266. this.dibanNode.setPosition(startPos);
  267. // 停止任何正在运行的动画
  268. Tween.stopAllByTarget(this.dibanNode);
  269. // 执行上滑动画
  270. tween(this.dibanNode)
  271. .to(duration, { position: targetPos }, { easing: 'quadInOut' })
  272. .call(() => {
  273. const animationEndTime = Date.now();
  274. })
  275. .start();
  276. // 同时执行camera平滑下移动画
  277. this.moveDownSmooth(duration);
  278. }
  279. /**
  280. * 下滑 diban节点,结束备战进入playing 状态。
  281. * @param duration 动画时长,默认 0.3s
  282. */
  283. public slideDibanDownAndHide(duration: number = 0.3) {
  284. // 使用装饰器属性中的diban节点
  285. if (!this.dibanNode) {
  286. console.warn('GameStartMove.slideDibanDownAndHide diban节点未设置');
  287. return;
  288. }
  289. // 检查参考线节点
  290. if (!this.referenceLineNode) {
  291. console.warn('GameStartMove.slideDibanDownAndHide 参考线节点未设置');
  292. return;
  293. }
  294. // 获取当前位置
  295. const currentPos = this.dibanNode.position.clone();
  296. console.log('GameStartMove.slideDibanDownAndHide diban当前位置:', currentPos);
  297. // 获取参考线位置和diban高度
  298. const positionData = this.getReferenceLinePositionAndDibanHeight();
  299. if (!positionData) {
  300. return;
  301. }
  302. const { referenceLineLocalPos, dibanHeight } = positionData;
  303. // 计算目标位置:diban顶部与参考线对齐(下滑到初始位置)
  304. const targetPos = new Vec3(currentPos.x, referenceLineLocalPos.y - dibanHeight / 2, currentPos.z);
  305. console.log('GameStartMove.slideDibanDownAndHide diban目标位置:', targetPos);
  306. // 停止任何正在运行的动画
  307. Tween.stopAllByTarget(this.dibanNode);
  308. // 执行下滑动画
  309. tween(this.dibanNode)
  310. .to(duration, { position: targetPos }, { easing: 'quadOut' })
  311. .call(() => {
  312. console.log('GameStartMove.slideDibanDownAndHide diban下滑动画完成');
  313. // 动画完成时隐藏Gray遮罩
  314. if (this.grayNode) {
  315. this.grayNode.active = false;
  316. }
  317. // 动画完成后隐藏diban节点
  318. this.dibanNode.active = false;
  319. })
  320. .start();
  321. // 在diban下滑动画开始的同时,相机平滑移动到目标线位置
  322. this.moveCameraToTargetLineSmooth(0.3);
  323. console.log('GameStartMove.slideDibanDownAndHide diban下滑动画已启动');
  324. }
  325. }