Browse Source

Toast动画

181404010226 4 months ago
parent
commit
f7036fec82

+ 85 - 0
Toast事件机制修改说明.md

@@ -0,0 +1,85 @@
+# Toast事件机制修改说明
+
+## 修改概述
+
+本次修改将Toast提示系统从直接节点操作改为事件驱动机制,提高了代码的解耦性和可维护性。
+
+## 主要修改内容
+
+### 1. EventBus事件扩展
+
+在 `assets/scripts/Core/EventBus.ts` 中添加了新的Toast事件:
+
+```typescript
+// Toast提示事件
+SHOW_TOAST = 'SHOW_TOAST',
+HIDE_TOAST = 'HIDE_TOAST'
+```
+
+### 2. ToastAni动画组件
+
+创建了 `assets/scripts/Animations/ToastAni.ts` 文件,实现了Toast的滑动动画效果:
+
+- **滑入动画**:从屏幕左边中间位置滑入到屏幕中央
+- **滑出动画**:从屏幕中央滑出到屏幕右边外侧
+- **位置重置**:动画完成后自动重置到初始位置
+- **事件监听**:监听 `SHOW_TOAST` 和 `HIDE_TOAST` 事件
+
+### 3. GameBlockSelection.ts 修改
+
+- 移除了 `toastNode` 属性的直接引用
+- 修改 `showInsufficientCoinsUI()` 方法使用事件机制
+- 修改 `showNoPlacedBlocksToast()` 方法使用事件机制
+
+### 4. EnemyController.ts 修改
+
+- 移除了 `toastPrefab` 属性
+- 修改 `showStartWavePromptUI()` 方法使用事件机制
+- 简化了波次提示的显示逻辑
+
+## 使用方法
+
+### 显示Toast提示
+
+```typescript
+// 显示Toast消息
+EventBus.getInstance().emit(GameEvents.SHOW_TOAST, {
+    message: '这是提示消息',
+    duration: 3.0  // 可选,默认3秒
+});
+```
+
+### 手动隐藏Toast
+
+```typescript
+// 手动隐藏Toast
+EventBus.getInstance().emit(GameEvents.HIDE_TOAST);
+```
+
+## 动画特性
+
+1. **固定位置**:Toast始终从屏幕左边中间位置进入,从右边中间滑出
+2. **不受Camera影响**:动画位置基于Canvas坐标系,不受Camera移动影响
+3. **自动管理**:节点默认为active状态,无需手动激活/隐藏
+4. **防重复**:动画进行中时会忽略新的显示请求
+5. **自动重置**:动画完成后自动重置到初始位置
+
+## 配置要求
+
+1. Canvas/Toast节点需要添加 `ToastAni` 组件
+2. Toast节点的子节点 `label` 需要有 `cc.Label` 组件
+3. 确保EventBus正常工作
+
+## 优势
+
+1. **解耦合**:各模块不再需要直接引用Toast节点
+2. **统一管理**:所有Toast显示通过事件系统统一管理
+3. **易扩展**:可以轻松添加新的Toast类型和样式
+4. **更好的动画**:提供了流畅的滑入滑出动画效果
+5. **减少依赖**:移除了对预制体的直接依赖
+
+## 注意事项
+
+1. 需要确保Canvas/Toast节点存在并正确配置ToastAni组件
+2. 事件参数中的message为必需参数,duration为可选参数
+3. 动画期间会忽略新的显示请求,避免动画冲突

File diff suppressed because it is too large
+ 210 - 193
assets/Scenes/GameLevel.scene


+ 117 - 0
assets/scripts/Animations/ToastAni.ts

@@ -0,0 +1,117 @@
+import { _decorator, Component, Node, UITransform, Label, tween, Vec3, find } from 'cc';
+import EventBus, { GameEvents } from '../Core/EventBus';
+
+const { ccclass, property } = _decorator;
+
+@ccclass('ToastAni')
+export class ToastAni extends Component {
+    
+    private originalPosition: Vec3 = new Vec3();
+    private isAnimating: boolean = false;
+    
+    onLoad() {
+        // 保存初始位置
+        this.originalPosition = this.node.position.clone();
+        
+        // 监听Toast事件
+        this.setupEventListeners();
+        
+        // 初始化位置到屏幕左边外侧
+        this.resetToInitialPosition();
+    }
+    
+    private setupEventListeners() {
+        EventBus.getInstance().on(GameEvents.SHOW_TOAST, this.onShowToast, this);
+        EventBus.getInstance().on(GameEvents.HIDE_TOAST, this.onHideToast, this);
+    }
+    
+    private onShowToast(data: { message: string, duration?: number }) {
+        if (this.isAnimating) {
+            return;
+        }
+        
+        // 设置Toast文本
+        this.setToastMessage(data.message);
+        
+        // 播放滑入动画
+        this.playSlideInAnimation();
+    }
+    
+    private onHideToast() {
+        if (!this.isAnimating) {
+            this.playSlideOutAnimation();
+        }
+    }
+    
+    private setToastMessage(message: string) {
+        const labelNode = this.node.getChildByName('label');
+        if (labelNode) {
+            const label = labelNode.getComponent(Label);
+            if (label) {
+                label.string = message;
+            }
+        }
+    }
+    
+    private resetToInitialPosition() {
+        // 重置到原始位置(屏幕外侧的起始位置)
+        this.node.setPosition(this.originalPosition);
+    }
+    
+    private playSlideInAnimation() {
+        if (this.isAnimating) {
+            return;
+        }
+        
+        this.isAnimating = true;
+        
+        // 确保从起始位置开始
+        this.resetToInitialPosition();
+        
+        // 滑入到屏幕中央位置显示
+        const targetPosition = new Vec3(0, this.originalPosition.y, this.originalPosition.z);
+        
+        tween(this.node)
+            .to(0.5, { position: targetPosition }, { easing: 'sineOut' })
+            .delay(0.3)
+            .call(() => {
+                this.playSlideOutAnimation();
+            })
+            .start();
+    }
+    
+    private playSlideOutAnimation() {
+        if (!this.isAnimating) {
+            return;
+        }
+        
+        // 获取Canvas尺寸来计算屏幕边界
+        const canvas = find('Canvas');
+        if (canvas) {
+            const canvasTransform = canvas.getComponent(UITransform);
+            if (canvasTransform) {
+                const canvasWidth = canvasTransform.contentSize.width;
+                // 获取Toast节点的宽度
+                const toastTransform = this.node.getComponent(UITransform);
+                const toastWidth = toastTransform ? toastTransform.contentSize.width : 200;
+                // 滑出到屏幕右边外侧消失
+                const rightPosition = new Vec3(canvasWidth / 2 + toastWidth / 2 + 50, this.node.position.y, this.node.position.z);
+                
+                tween(this.node)
+                    .to(0.5, { position: rightPosition }, { easing: 'sineIn' })
+                    .call(() => {
+                        // 动画完成后立即重置到初始位置
+                        this.resetToInitialPosition();
+                        this.isAnimating = false;
+                    })
+                    .start();
+            }
+        }
+    }
+    
+    onDestroy() {
+        // 移除事件监听
+        EventBus.getInstance().off(GameEvents.SHOW_TOAST, this.onShowToast, this);
+        EventBus.getInstance().off(GameEvents.HIDE_TOAST, this.onHideToast, this);
+    }
+}

+ 9 - 0
assets/scripts/Animations/ToastAni.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "c5d5db30-c3c2-4448-8f2b-9b8ff7dbfae9",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 18 - 77
assets/scripts/CombatSystem/BlockSelection/GameBlockSelection.ts

@@ -1,4 +1,4 @@
-import { _decorator, Component, Node, Button, Label, find, UITransform, Sprite, Color, Prefab, instantiate, Vec3 } from 'cc';
+import { _decorator, Component, Node, Button, Label, find } from 'cc';
 import { LevelSessionManager } from '../../Core/LevelSessionManager';
 import { BallController } from '../BallController';
 import { BlockManager } from '../BlockManager';
@@ -36,11 +36,12 @@ export class GameBlockSelection extends Component {
     })
     public coinLabelNode: Node = null;
 
-    @property({
-        type: Prefab,
-        tooltip: '拖拽Toast预制体到这里'
-    })
-    public toastPrefab: Prefab = null;
+    // 移除toastNode属性,改用事件机制
+    // @property({
+    //     type: Node,
+    //     tooltip: '拖拽Canvas/Toast节点到这里'
+    // })
+    // public toastNode: Node = null;
 
     @property({
         type: Node,
@@ -394,36 +395,12 @@ export class GameBlockSelection extends Component {
 
     // 显示金币不足UI
     private showInsufficientCoinsUI() {
-        if (!this.toastPrefab) {
-            console.error('Toast预制体未绑定,请在Inspector中拖拽Toast预制体');
-            return;
-        }
-
-        // 实例化Toast预制体
-        const toastNode = instantiate(this.toastPrefab);
+        // 使用事件机制显示Toast
+        EventBus.getInstance().emit(GameEvents.SHOW_TOAST, {
+            message: '金币不足!',
+            duration: 3.0
+        });
         
-        // 设置Toast的文本为"金币不足!"
-        const labelNode = toastNode.getChildByName('label');
-        if (labelNode) {
-            const label = labelNode.getComponent(Label);
-            if (label) {
-                label.string = '金币不足!';
-            }
-        }
-        
-        // 将Toast添加到Canvas下
-        const canvas = find('Canvas');
-        if (canvas) {
-            canvas.addChild(toastNode);
-        }
-
-        // 3秒后自动销毁Toast
-        this.scheduleOnce(() => {
-            if (toastNode && toastNode.isValid) {
-                toastNode.destroy();
-            }
-        }, 3.0);
-
         console.log('金币不足!');
     }
 
@@ -497,50 +474,14 @@ export class GameBlockSelection extends Component {
 
     // 显示没有上阵方块的Toast提示
     private showNoPlacedBlocksToast() {
-        console.log('[GameBlockSelection] 开始显示Toast提示');
+        console.log('[GameBlockSelection] 显示未上阵植物提示');
         
-        if (!this.toastPrefab) {
-            console.error('[GameBlockSelection] Toast预制体未绑定,请在Inspector中拖拽Toast预制体');
-            return;
-        }
-        console.log('[GameBlockSelection] Toast预制体已绑定');
-
-        // 实例化Toast预制体
-        const toastNode = instantiate(this.toastPrefab);
-        console.log('[GameBlockSelection] Toast节点已实例化:', toastNode.name);
-        
-        // 设置Toast的文本为"请至少上阵一个植物!"
-        const labelNode = toastNode.getChildByName('label');
-        console.log('[GameBlockSelection] 查找label子节点:', !!labelNode);
-        if (labelNode) {
-            const label = labelNode.getComponent(Label);
-            console.log('[GameBlockSelection] 获取Label组件:', !!label);
-            if (label) {
-                label.string = '请至少上阵一个植物!';
-            } else {
-                console.error('[GameBlockSelection] label节点没有Label组件');
-            }
-        } else {
-            console.error('[GameBlockSelection] 找不到名为"label"的子节点');
-        }
+        // 使用事件机制显示Toast
+        EventBus.getInstance().emit(GameEvents.SHOW_TOAST, {
+            message: '请至少上阵一个植物!',
+            duration: 3.0
+        });
         
-        // 将Toast添加到Canvas下
-        const canvas = find('Canvas');
-        if (canvas) {
-            canvas.addChild(toastNode);
-        } else {
-            console.error('[GameBlockSelection] 找不到Canvas节点');
-            return;
-        }
-
-        // 3秒后自动销毁Toast
-        this.scheduleOnce(() => {
-            if (toastNode && toastNode.isValid) {
-                toastNode.destroy();
-            } else {
-                console.warn('[GameBlockSelection] Toast节点无效,无法销毁');
-            }
-        }, 3.0);
         console.log('[GameBlockSelection] 请至少上阵一个植物!');
     }
     

+ 15 - 42
assets/scripts/CombatSystem/EnemyController.ts

@@ -1,4 +1,4 @@
-import { _decorator, Node, Label, Vec3, Prefab, instantiate, find, UITransform, resources, RigidBody2D } from 'cc';
+import { _decorator, Node, Label, Vec3, Prefab, find, UITransform, resources, RigidBody2D, instantiate } from 'cc';
 import { sp } from 'cc';
 import { ConfigManager, EnemyConfig } from '../Core/ConfigManager';
 import { EnemyComponent } from '../CombatSystem/EnemyComponent';
@@ -34,12 +34,12 @@ export class EnemyController extends BaseSingleton {
     @property({ type: Prefab, tooltip: '金币预制体 CoinDrop' })
     public coinPrefab: Prefab = null;
 
-    // Toast预制体,用于显示波次提示
-    @property({
-        type: Prefab,
-        tooltip: 'Toast预制体,用于显示波次提示'
-    })
-    public toastPrefab: Prefab = null;
+    // 移除Toast预制体属性,改用事件机制
+    // @property({
+    //     type: Prefab,
+    //     tooltip: 'Toast预制体,用于显示波次提示'
+    // })
+    // public toastPrefab: Prefab = null;
 
     // === 生成 & 属性参数(保留需要可在内部自行设定,Inspector 不再显示) ===
     private spawnInterval: number = 3;
@@ -530,7 +530,7 @@ export class EnemyController extends BaseSingleton {
         const fromTop = Math.random() > 0.5;
         
         // 实例化敌人
-        const enemy = instantiate(this.enemyPrefab);
+        const enemy = instantiate(this.enemyPrefab) as Node;
         enemy.name = 'Enemy'; // 确保敌人节点名称为Enemy
         
         // 添加到场景中
@@ -1008,42 +1008,18 @@ export class EnemyController extends BaseSingleton {
             return;
         }
         
-        if (!this.toastPrefab) {
-            console.warn('Toast预制体未绑定,无法显示波次提示');
-            // 如果没有Toast预制体,直接开始游戏
-            this.startGame();
-            return;
-        }
-
-        // 实例化Toast预制体
-        const toastNode = instantiate(this.toastPrefab);
-        
-        // 设置Toast的文本为"第x波敌人来袭!"
-        const labelNode = toastNode.getChildByName('label');
-        if (labelNode) {
-            const label = labelNode.getComponent(Label);
-            if (label) {
-                const toastText = `第${this.currentWave}波敌人来袭!`;
-                label.string = toastText;
-            }
-        }
-        
-        // 将Toast添加到Canvas下
-        const canvas = find('Canvas');
-        if (canvas) {
-            canvas.addChild(toastNode);
-        }
+        // 使用事件机制显示Toast
+        const toastText = `第${this.currentWave}波敌人来袭!`;
+        EventBus.getInstance().emit(GameEvents.SHOW_TOAST, {
+            message: toastText,
+            duration: duration
+        });
 
         // 暂停生成(确保未重复)
         this.pauseSpawning();
         
         if (duration > 0) {
             this.scheduleOnce(() => {
-                // 销毁Toast
-                if (toastNode && toastNode.isValid) {
-                    toastNode.destroy();
-                }
-                
                 // 再次通过事件系统检查游戏是否已经结束
                 let gameOverCheck = false;
                 EventBus.getInstance().emit(GameEvents.GAME_CHECK_OVER, (isOver: boolean) => {
@@ -1059,10 +1035,7 @@ export class EnemyController extends BaseSingleton {
                 this.startGame();
             }, duration);
         } else {
-            // 立即销毁Toast并开始游戏
-            if (toastNode && toastNode.isValid) {
-                toastNode.destroy();
-            }
+            // 立即开始游戏
             this.startGame();
         }
     }

+ 5 - 1
assets/scripts/Core/EventBus.ts

@@ -80,7 +80,11 @@ export enum GameEvents {
     
     // 子弹事件
     BULLET_CREATE_REQUEST = 'BULLET_CREATE_REQUEST',
-    BULLET_HIT_ENEMY = 'BULLET_HIT_ENEMY'
+    BULLET_HIT_ENEMY = 'BULLET_HIT_ENEMY',
+    
+    // Toast提示事件
+    SHOW_TOAST = 'SHOW_TOAST',
+    HIDE_TOAST = 'HIDE_TOAST'
 }
 
 export default class EventBus extends EventTarget {

+ 70 - 0
assets/scripts/FourUI/UpgradeSystem/UpgradeAni.ts

@@ -0,0 +1,70 @@
+import { _decorator, Component, Node, tween, Vec3 } from 'cc';
+
+const { ccclass, property } = _decorator;
+
+/**
+ * 升级面板动画控制器
+ * 负责管理升级面板的显示和隐藏动画
+ */
+@ccclass('UpgradeAni')
+export class UpgradeAni extends Component {
+    
+    /**
+     * 显示面板动画
+     * 面板从小到大的缩放动画,从屏幕中央弹出
+     */
+    public showPanel(): Promise<void> {
+        return new Promise((resolve) => {
+            // 设置初始状态:缩小到0,位置居中
+            this.node.setScale(Vec3.ZERO);
+            this.node.setPosition(0, 0, 0); // 确保面板在屏幕正中间
+            this.node.active = true;
+            
+            // 缩放动画:从0放大到1
+            tween(this.node)
+                .to(0.3, { scale: new Vec3(1, 1, 1) }, {
+                    easing: 'backOut' // 使用回弹缓动效果
+                })
+                .call(() => {
+                    resolve();
+                })
+                .start();
+        });
+    }
+    
+    /**
+     * 隐藏面板动画
+     * 面板从大到小的缩放动画
+     */
+    public hidePanel(): Promise<void> {
+        return new Promise((resolve) => {
+            // 缩放动画:从1缩小到0
+            tween(this.node)
+                .to(0.2, { scale: Vec3.ZERO }, {
+                    easing: 'backIn' // 使用回弹缓动效果
+                })
+                .call(() => {
+                    this.node.active = false;
+                    resolve();
+                })
+                .start();
+        });
+    }
+    
+    /**
+     * 立即隐藏面板(无动画)
+     */
+    public hidePanelImmediate(): void {
+        this.node.setScale(Vec3.ZERO);
+        this.node.active = false;
+    }
+    
+    /**
+     * 立即显示面板(无动画)
+     */
+    public showPanelImmediate(): void {
+        this.node.setScale(Vec3.ONE);
+        this.node.setPosition(0, 0, 0); // 确保面板在屏幕正中间
+        this.node.active = true;
+    }
+}

+ 9 - 0
assets/scripts/FourUI/UpgradeSystem/UpgradeAni.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "d35a54e9-246c-4515-8f73-fc4e65529cdd",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 30 - 7
assets/scripts/FourUI/UpgradeSystem/UpgradeController.ts

@@ -2,6 +2,7 @@ import { _decorator, Component, Node, Button, Label, Sprite, SpriteFrame, Textur
 import { SaveDataManager, WeaponData } from '../../LevelSystem/SaveDataManager';
 import { TopBarController } from '../TopBarController';
 import EventBus, { GameEvents } from '../../Core/EventBus';
+import { UpgradeAni } from './UpgradeAni';
 
 const { ccclass, property } = _decorator;
 
@@ -56,6 +57,9 @@ export class UpgradeController extends Component {
     @property(Prefab) lockedWeaponPrefab: Prefab = null;    // Lock.prefab
     @property(Prefab) unlockedWeaponPrefab: Prefab = null;  // Unlock.prefab
     
+    // 动画控制器
+    @property(UpgradeAni) upgradeAni: UpgradeAni = null;    // Canvas/UpgradeUI/UpgradePanel上的UpgradeAni组件
+    
     // 数据管理
     private saveDataManager: SaveDataManager = null;
     private weaponsConfig: { weapons: WeaponConfig[] } = null;
@@ -85,7 +89,13 @@ export class UpgradeController extends Component {
         
         // 刷新UI
         this.refreshWeaponList();
-        this.upgradePanel.active = false;
+        
+        // 初始化升级面板状态
+        if (this.upgradeAni) {
+            this.upgradeAni.hidePanelImmediate();
+        } else {
+            this.upgradePanel.active = false;
+        }
         
         console.log('[UpgradeController] 初始化完成');
     }
@@ -384,7 +394,7 @@ export class UpgradeController extends Component {
         }
         
         // 设置升级按钮 - Unlock.prefab中的Button节点
-        const upgradeButton = weaponNode.getChildByName('Button')?.getComponent(Button);
+        const upgradeButton = weaponNode.getChildByName('Upgrade')?.getComponent(Button);
         if (upgradeButton) {
             // 清除之前的事件监听
             upgradeButton.node.off(Button.EventType.CLICK);
@@ -524,7 +534,7 @@ export class UpgradeController extends Component {
     /**
      * 打开升级面板
      */
-    private openUpgradePanel(weaponId: string) {
+    private async openUpgradePanel(weaponId: string) {
         const weaponConfig = this.weaponsConfig.weapons.find(config => config.id === weaponId);
         if (!weaponConfig) {
             console.error(`未找到武器配置: ${weaponId}`);
@@ -541,16 +551,29 @@ export class UpgradeController extends Component {
         
         console.log(`打开升级面板: ${weaponConfig.name}, 当前等级: ${weaponData.level}`);
         
-        // 显示升级面板
-        this.upgradePanel.active = true;
+        // 刷新面板内容
         this.refreshUpgradePanel();
+        
+        // 使用动画显示升级面板
+        if (this.upgradeAni) {
+            await this.upgradeAni.showPanel();
+        } else {
+            // 如果没有动画组件,直接显示
+            this.upgradePanel.active = true;
+        }
     }
     
     /**
      * 关闭升级面板
      */
-    private closeUpgradePanel() {
-        this.upgradePanel.active = false;
+    private async closeUpgradePanel() {
+        // 使用动画隐藏升级面板
+        if (this.upgradeAni) {
+            await this.upgradeAni.hidePanel();
+        } else {
+            // 如果没有动画组件,直接隐藏
+            this.upgradePanel.active = false;
+        }
         this.currentSelectedWeapon = null;
     }
     

+ 0 - 9
assets/scripts/Test.meta

@@ -1,9 +0,0 @@
-{
-  "ver": "1.2.0",
-  "importer": "directory",
-  "imported": true,
-  "uuid": "bdd05762-1653-4d7c-bfb7-97a4b0e48eac",
-  "files": [],
-  "subMetas": {},
-  "userData": {}
-}

Some files were not shown because too many files changed in this diff