| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- import { _decorator, Component, Node, Vec3, tween, Color,find, Sprite, resources, sp, Material } from 'cc';
- import EventBus, { GameEvents } from '../Core/EventBus';
- const { ccclass, property } = _decorator;
- /**
- * 小球撞击动画管理器
- * 负责在小球撞击时播放特效动画
- */
- @ccclass('BallAni')
- export class BallAni extends Component {
-
- // 撞击特效预制体缓存
- private static impactEffectSkeleton: sp.SkeletonData = null;
- private static isLoading: boolean = false;
-
- // 白色描边材质缓存
- private static whiteMaterial: Material = null;
- private static isMaterialLoading: boolean = false;
-
- // 当前播放中的特效节点列表
- private activeEffects: Node[] = [];
-
- // 当前播放中的方块动画列表
- private activeBlockAnimations: Map<Node, any> = new Map();
-
- // 敌人生成状态
- private enemySpawningActive: boolean = false;
-
- start() {
- // 预加载撞击特效资源
- this.preloadImpactEffect();
- // 预加载白色描边材质
- this.preloadWhiteMaterial();
-
- // 监听敌人生成状态事件
- const eventBus = EventBus.getInstance();
- eventBus.on(GameEvents.ENEMY_SPAWNING_STARTED, this.onEnemySpawningStarted, this);
- eventBus.on(GameEvents.ENEMY_SPAWNING_STOPPED, this.onEnemySpawningStopped, this);
- }
-
- private onEnemySpawningStarted() {
- this.enemySpawningActive = true;
- }
-
- private onEnemySpawningStopped() {
- this.enemySpawningActive = false;
- // 当敌人生成停止时,立即检查是否需要发射子弹
- const eventBus = EventBus.getInstance();
- eventBus.emit(GameEvents.BALL_FIRE_BULLET, {
- canFire: (canFire: boolean) => { }
- });
- }
-
- /**
- * 播放方块被撞击动画
- * @param blockNode 方块节点
- */
- public playBlockHitAnimation(blockNode: Node) {
- if (!blockNode || !blockNode.isValid) {
- console.warn('[BallAni] 方块节点无效');
- return;
- }
-
- // 如果该方块已经在播放动画,跳过
- if (this.activeBlockAnimations.has(blockNode)) {
- console.log('[BallAni] 方块动画已在播放中,跳过');
- return;
- }
-
- // 检查是否有活跃敌人,只有有敌人时才播放缩放动画
- const eventBus = EventBus.getInstance();
- let hasActiveEnemies = false;
-
- // 通过事件系统检查是否有活跃敌人
- eventBus.emit(GameEvents.BALL_FIRE_BULLET, {
- canFire: (canFire: boolean) => {
- hasActiveEnemies = canFire;
- }
- });
-
- // 立即执行动画
- this.executeBlockAnimation(blockNode, hasActiveEnemies);
-
- // 如果敌人生成未激活,立即检查是否需要发射子弹
- if (!this.enemySpawningActive) {
- eventBus.emit(GameEvents.BALL_FIRE_BULLET, {
- canFire: (canFire: boolean) => { }
- });
- }
- }
-
- /**
- * 执行方块动画的具体逻辑
- * @param blockNode 方块节点
- * @param hasActiveEnemies 是否有活跃敌人
- */
- private executeBlockAnimation(blockNode: Node, hasActiveEnemies: boolean) {
- if (!blockNode || !blockNode.isValid) {
- console.warn('[BallAni] 方块节点无效');
- return;
- }
-
- // 如果该方块已经在播放动画,跳过
- if (this.activeBlockAnimations.has(blockNode)) {
- console.log('[BallAni] 方块动画已在播放中,跳过');
- return;
- }
-
- // 新的预制体结构:Sprite组件在Node子节点上
- const nodeChild = blockNode.getChildByName('Node');
- const sprite = nodeChild ? nodeChild.getComponent(Sprite) : null;
-
- // 查找预制体根节点和Weapon节点
- // 如果传入的是子节点(如"Node"),需要找到预制体根节点
- let prefabRoot = blockNode;
- if (blockNode.name === 'Node' && blockNode.parent) {
- prefabRoot = blockNode.parent;
- }
-
- const weaponNode = prefabRoot.getChildByName('Weapon');
-
- // 保存原始材质和缩放
- // 优先保存customMaterial,如果没有则保存当前material
- const originalMaterial = sprite ? (sprite.customMaterial || sprite.material) : null;
- const originalBlockScale = new Vec3(blockNode.scale.x, blockNode.scale.y, blockNode.scale.z);
- const originalWeaponScale = weaponNode ? new Vec3(weaponNode.scale.x, weaponNode.scale.y, weaponNode.scale.z) : null;
-
-
- if (hasActiveEnemies) {
- // 有敌人时播放方块缩放动画(包括Node和Weapon子节点)
- const shrinkBlockScale = new Vec3(0.8, 0.8, 1);
-
- // 应用白色描边材质到Node子节点的Sprite组件
- if (sprite && BallAni.whiteMaterial) {
- sprite.material = BallAni.whiteMaterial;
- }
-
- // 对方块预制体进行缩放动画
- // Weapon节点需要在其原有0.4倍基础上再进行缩放动画
- const weaponShrinkScale = originalWeaponScale ? new Vec3(
- originalWeaponScale.x * 0.8, // 在0.4倍基础上再缩小到0.8倍
- originalWeaponScale.y * 0.8,
- originalWeaponScale.z
- ) : null;
-
- const animationTween = tween(blockNode)
- .to(0.1, { scale: shrinkBlockScale }, {
- onStart: () => {
- // 开始动画时,同时缩放Weapon节点
- if (weaponNode && weaponShrinkScale) {
- tween(weaponNode)
- .to(0.1, { scale: weaponShrinkScale })
- .start();
- }
- }
- })
- .to(0.2, { scale: originalBlockScale }, {
- onStart: () => {
- // 恢复动画时,同时恢复Weapon节点
- if (weaponNode && originalWeaponScale) {
- tween(weaponNode)
- .to(0.2, { scale: originalWeaponScale })
- .start();
- }
- }
- })
- .call(() => {
- // 确保Weapon节点恢复到原始缩放
- if (weaponNode && originalWeaponScale) {
- weaponNode.scale = originalWeaponScale;
- }
- // 动画完成时恢复原始材质
- if (sprite && originalMaterial) {
- // 如果原始材质是customMaterial,则恢复customMaterial
- if (sprite.customMaterial === originalMaterial) {
- sprite.material = sprite.customMaterial;
- } else {
- sprite.material = originalMaterial;
- }
- } else if (sprite) {
- // 如果没有保存的原始材质,尝试恢复到customMaterial
- if (sprite.customMaterial) {
- sprite.material = sprite.customMaterial;
- } else {
- sprite.material = null;
- }
- }
- // 动画完成,从活动列表中移除
- this.activeBlockAnimations.delete(blockNode);
- })
- .start();
-
- // 添加到活动动画列表
- this.activeBlockAnimations.set(blockNode, animationTween);
- } else {
- // 没有敌人时直接结束动画,不应用材质
- const animationTween = tween({})
- .delay(0.2)
- .call(() => {
- // 动画完成,从活动列表中移除
- this.activeBlockAnimations.delete(blockNode);
- })
- .start();
-
- // 添加到活动动画列表
- this.activeBlockAnimations.set(blockNode, animationTween);
- }
- }
-
- /**
- * 预加载撞击特效资源
- */
- private preloadImpactEffect() {
- if (BallAni.impactEffectSkeleton || BallAni.isLoading) {
- return;
- }
-
- BallAni.isLoading = true;
- const path = 'Animation/WeaponTx/tx0005/tx0005';
- resources.load(path, sp.SkeletonData, (err, sData: sp.SkeletonData) => {
- BallAni.isLoading = false;
- if (err || !sData) {
- console.warn('[BallAni] 加载撞击特效失败:', err);
- return;
- }
- BallAni.impactEffectSkeleton = sData;
- console.log('[BallAni] 撞击特效资源加载成功');
- });
- }
-
- /**
- * 预加载白色描边材质
- */
- private preloadWhiteMaterial() {
- if (BallAni.whiteMaterial || BallAni.isMaterialLoading) {
- return;
- }
-
- BallAni.isMaterialLoading = true;
- const materialPath = 'shaders/ui-sprite-white-material';
- resources.load(materialPath, Material, (err, material: Material) => {
- BallAni.isMaterialLoading = false;
- if (err || !material) {
- console.warn('[BallAni] 加载白色描边材质失败:', err);
- return;
- }
- BallAni.whiteMaterial = material;
- console.log('[BallAni] 白色描边材质加载成功');
- });
- }
-
- /**
- * 在指定位置播放撞击特效
- * @param worldPosition 世界坐标位置
- */
- public playImpactEffect(worldPosition: Vec3) {
- // 如果资源未加载,直接加载并播放
- if (!BallAni.impactEffectSkeleton) {
- const path = 'Animation/WeaponTx/tx0005/tx0005';
- resources.load(path, sp.SkeletonData, (err, sData: sp.SkeletonData) => {
- if (err || !sData) {
- console.warn('[BallAni] 加载撞击特效失败:', err);
- return;
- }
- BallAni.impactEffectSkeleton = sData;
- this.createAndPlayEffect(worldPosition, sData);
- });
- return;
- }
-
- this.createAndPlayEffect(worldPosition, BallAni.impactEffectSkeleton);
- }
-
- /**
- * 创建并播放特效
- */
- private createAndPlayEffect(worldPosition: Vec3, skeletonData: sp.SkeletonData) {
- const effectNode = new Node('ImpactEffect');
- const skeleton = effectNode.addComponent(sp.Skeleton);
- skeleton.skeletonData = skeletonData;
- skeleton.premultipliedAlpha = false;
- skeleton.setAnimation(0, 'animation', false);
- skeleton.setCompleteListener(() => {
- this.removeEffect(effectNode);
- });
-
- const canvas = find('Canvas');
- if (canvas) {
- canvas.addChild(effectNode);
- effectNode.setWorldPosition(worldPosition);
- // 设置特效缩放
- effectNode.setScale(0.8, 0.8, 1);
- // 添加到活动特效列表
- this.activeEffects.push(effectNode);
- } else {
- effectNode.destroy();
- }
- }
-
- /**
- * 移除特效节点
- * @param effectNode 要移除的特效节点
- */
- private removeEffect(effectNode: Node) {
- // 从活动特效列表中移除
- const index = this.activeEffects.indexOf(effectNode);
- if (index !== -1) {
- this.activeEffects.splice(index, 1);
- }
-
- // 销毁节点
- if (effectNode && effectNode.isValid) {
- effectNode.destroy();
- }
- }
-
-
- /**
- * 停止指定方块的动画
- * @param blockNode 方块节点
- */
- public stopBlockAnimation(blockNode: Node) {
- const animation = this.activeBlockAnimations.get(blockNode);
- if (animation) {
- animation.stop();
- this.activeBlockAnimations.delete(blockNode);
- }
- }
-
- /**
- * 清理所有活动的特效
- */
- public clearAllEffects() {
- for (const effect of this.activeEffects) {
- if (effect && effect.isValid) {
- effect.destroy();
- }
- }
- this.activeEffects = [];
- }
-
- /**
- * 清除所有方块动画
- */
- public clearAllBlockAnimations() {
- // 停止所有方块动画
- for (const [blockNode, animation] of this.activeBlockAnimations) {
- if (animation) {
- animation.stop();
- }
- }
- this.activeBlockAnimations.clear();
- }
-
- onDestroy() {
- // 解绑事件监听
- const eventBus = EventBus.getInstance();
- eventBus.off(GameEvents.ENEMY_SPAWNING_STARTED, this.onEnemySpawningStarted, this);
- eventBus.off(GameEvents.ENEMY_SPAWNING_STOPPED, this.onEnemySpawningStopped, this);
-
- // 组件销毁时清理所有特效和动画
- this.clearAllEffects();
- this.clearAllBlockAnimations();
- }
-
- /**
- * 获取BallAni实例
- * @returns BallAni实例
- */
- public static getInstance(): BallAni | null {
- const gameArea = find('Canvas/GameLevelUI/GameArea');
- if (!gameArea) {
- console.warn('[BallAni] 未找到GameArea节点');
- return null;
- }
-
- const ballAni = gameArea.getComponent(BallAni);
- if (!ballAni) {
- console.warn('[BallAni] GameArea节点上未找到BallAni组件');
- return null;
- }
-
- return ballAni;
- }
- }
|