ScreenShakeManager.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. import { _decorator, Component, Node, Vec3, tween, find, Camera } from 'cc';
  2. const { ccclass, property } = _decorator;
  3. /**
  4. * 画面震动管理器
  5. * 负责管理整个游戏画面的震动效果
  6. */
  7. export class ScreenShakeManager {
  8. private static instance: ScreenShakeManager = null;
  9. // 相机节点
  10. private cameraNode: Node = null;
  11. // 震动相关属性
  12. private originalCameraPosition: Vec3 = new Vec3();
  13. private isShaking: boolean = false;
  14. private currentShakeTween: any = null;
  15. // 震动设置
  16. private vibrationEnabled: boolean = true;
  17. private constructor() {
  18. // 私有构造函数,确保单例模式
  19. }
  20. /**
  21. * 获取单例实例
  22. */
  23. public static getInstance(): ScreenShakeManager {
  24. if (!ScreenShakeManager.instance) {
  25. ScreenShakeManager.instance = new ScreenShakeManager();
  26. }
  27. return ScreenShakeManager.instance;
  28. }
  29. /**
  30. * 初始化相机节点
  31. * @param cameraNode 相机节点引用
  32. */
  33. public initialize(cameraNode: Node): void {
  34. this.cameraNode = cameraNode;
  35. if (this.cameraNode) {
  36. this.originalCameraPosition = this.cameraNode.position.clone();
  37. console.log('[ScreenShakeManager] 相机节点初始化完成');
  38. } else {
  39. console.warn('[ScreenShakeManager] 相机节点为空,画面震动功能将不可用');
  40. }
  41. }
  42. /**
  43. * 播放画面震动效果
  44. * @param intensity 震动强度 (像素)
  45. * @param duration 震动持续时间 (秒)
  46. * @param frequency 震动频率 (次数)
  47. */
  48. public playScreenShake(intensity: number = 10, duration: number = 0.5, frequency: number = 8): void {
  49. // 检查震动是否启用
  50. if (!this.vibrationEnabled) {
  51. console.log('[ScreenShakeManager] 震动已禁用,跳过画面震动');
  52. return;
  53. }
  54. // 检查相机节点是否存在
  55. if (!this.cameraNode) {
  56. console.warn('[ScreenShakeManager] 相机节点不存在,无法播放画面震动');
  57. return;
  58. }
  59. // 如果正在震动,先停止当前震动
  60. this.stopScreenShake();
  61. console.log(`[ScreenShakeManager] 开始画面震动 - 强度: ${intensity}, 持续时间: ${duration}s, 频率: ${frequency}`);
  62. // 标记为正在震动
  63. this.isShaking = true;
  64. // 保存当前位置作为原始位置
  65. this.originalCameraPosition = this.cameraNode.position.clone();
  66. // 创建震动序列
  67. let shakeTween = tween(this.cameraNode);
  68. const singleShakeTime = duration / frequency;
  69. for (let i = 0; i < frequency; i++) {
  70. // 计算震动强度衰减
  71. const currentIntensity = intensity * (1 - i / frequency);
  72. // 随机方向震动
  73. const randomAngle = Math.random() * Math.PI * 2;
  74. const offsetX = Math.cos(randomAngle) * currentIntensity;
  75. const offsetY = Math.sin(randomAngle) * currentIntensity;
  76. const targetPosition = this.originalCameraPosition.clone();
  77. targetPosition.x += offsetX;
  78. targetPosition.y += offsetY;
  79. shakeTween = shakeTween.to(singleShakeTime, {
  80. position: targetPosition
  81. }, {
  82. easing: 'sineInOut'
  83. });
  84. }
  85. // 最后回到原始位置
  86. shakeTween = shakeTween.to(singleShakeTime * 0.5, {
  87. position: this.originalCameraPosition
  88. }, {
  89. easing: 'sineOut'
  90. }).call(() => {
  91. this.isShaking = false;
  92. this.currentShakeTween = null;
  93. console.log('[ScreenShakeManager] 画面震动结束');
  94. });
  95. this.currentShakeTween = shakeTween;
  96. this.currentShakeTween.start();
  97. }
  98. /**
  99. * 停止画面震动
  100. */
  101. public stopScreenShake(): void {
  102. if (this.currentShakeTween) {
  103. this.currentShakeTween.stop();
  104. this.currentShakeTween = null;
  105. }
  106. if (this.isShaking && this.cameraNode) {
  107. // 立即回到原始位置
  108. this.cameraNode.position = this.originalCameraPosition.clone();
  109. this.isShaking = false;
  110. console.log('[ScreenShakeManager] 画面震动已停止');
  111. }
  112. }
  113. /**
  114. * 检查是否正在震动
  115. */
  116. public isScreenShaking(): boolean {
  117. return this.isShaking;
  118. }
  119. /**
  120. * 设置震动启用状态
  121. */
  122. public setVibrationEnabled(enabled: boolean): void {
  123. this.vibrationEnabled = enabled;
  124. console.log(`[ScreenShakeManager] 震动设置: ${enabled ? '启用' : '禁用'}`);
  125. // 如果禁用震动且正在震动,立即停止
  126. if (!enabled && this.isShaking) {
  127. this.stopScreenShake();
  128. }
  129. }
  130. /**
  131. * 获取震动启用状态
  132. */
  133. public getVibrationEnabled(): boolean {
  134. return this.vibrationEnabled;
  135. }
  136. /**
  137. * 播放轻微震动(用于普通攻击)
  138. */
  139. public playLightShake(): void {
  140. this.playScreenShake(6, 0.3, 6);
  141. }
  142. /**
  143. * 播放中等震动(用于重击)
  144. */
  145. public playMediumShake(): void {
  146. this.playScreenShake(10, 0.5, 8);
  147. }
  148. /**
  149. * 播放强烈震动(用于爆炸或特殊攻击)
  150. */
  151. public playHeavyShake(): void {
  152. this.playScreenShake(15, 0.8, 12);
  153. }
  154. /**
  155. * 根据攻击类型播放对应的震动效果
  156. */
  157. public playShakeByAttackType(attackType: string, intensity?: number): void {
  158. switch (attackType.toLowerCase()) {
  159. case 'light':
  160. case 'normal':
  161. case 'melee':
  162. this.playLightShake();
  163. break;
  164. case 'heavy':
  165. case 'strong':
  166. this.playMediumShake();
  167. break;
  168. case 'explosive':
  169. case 'critical':
  170. case 'special':
  171. this.playHeavyShake();
  172. break;
  173. default:
  174. // 如果指定了强度,使用自定义震动
  175. if (intensity !== undefined) {
  176. this.playScreenShake(intensity, 0.5, 8);
  177. } else {
  178. this.playLightShake();
  179. }
  180. break;
  181. }
  182. }
  183. onDestroy() {
  184. // 清理震动效果
  185. this.stopScreenShake();
  186. // 清除单例引用
  187. if (ScreenShakeManager.instance === this) {
  188. ScreenShakeManager.instance = null;
  189. }
  190. }
  191. }