Selaa lähdekoodia

完成名单、资料、通行证的动画效果

181404010226 1 viikko sitten
vanhempi
commit
62c66568d2

+ 8 - 3
assets/Scenes/MainScene.scene

@@ -214,7 +214,7 @@
     "_priority": 0,
     "_fov": 45,
     "_fovAxis": 0,
-    "_orthoHeight": 666.9999999999999,
+    "_orthoHeight": 667,
     "_near": 0,
     "_far": 2000,
     "_color": {
@@ -2886,6 +2886,8 @@
       "__id__": 85
     },
     "avatarResourcePath": "avatars/",
+    "animDuration": 0.5,
+    "useCustomAnimation": true,
     "avatarItemPrefab": {
       "__id__": 88
     },
@@ -6651,6 +6653,8 @@
     "closeButton": {
       "__id__": 203
     },
+    "animDuration": 0.5,
+    "cardShadow": null,
     "_id": "79lbHz96dAnq/TLzTrjQto"
   },
   {
@@ -8399,6 +8403,7 @@
     "infoText": {
       "__id__": 234
     },
+    "animDuration": 0.5,
     "_id": "823nias1FJPaLFZnkVnyqF"
   },
   {
@@ -9334,7 +9339,7 @@
     "__prefab": null,
     "clickEvents": [],
     "_interactable": true,
-    "_transition": 0,
+    "_transition": 1,
     "_normalColor": {
       "__type__": "cc.Color",
       "r": 255,
@@ -9498,7 +9503,7 @@
     "__prefab": null,
     "clickEvents": [],
     "_interactable": true,
-    "_transition": 0,
+    "_transition": 1,
     "_normalColor": {
       "__type__": "cc.Color",
       "r": 255,

+ 259 - 4
assets/scripts/PassCardManager.ts

@@ -1,4 +1,4 @@
-import { _decorator, Component, Node, Label, Sprite, Button, resources, SpriteFrame } from 'cc';
+import { _decorator, Component, Node, Label, Sprite, Button, resources, SpriteFrame, tween, Vec3, UIOpacity, UITransform, Color } from 'cc';
 const { ccclass, property } = _decorator;
 
 /**
@@ -56,13 +56,47 @@ export class PassCardManager extends Component {
     })
     closeButton: Button = null;
 
+    @property({
+        tooltip: '通行证面板动画时长(秒)',
+        range: [0.1, 2.0, 0.1]
+    })
+    animDuration: number = 0.5;
+
+    @property({
+        type: Node,
+        tooltip: '卡片阴影节点(可选)'
+    })
+    cardShadow: Node = null;
+
     // 当前通行证数据
     private currentPassData: PassCardData = null;
 
+    // 用于动画控制的UIOpacity组件
+    private passCardOpacity: UIOpacity = null;
+    private shadowOpacity: UIOpacity = null;
+
     start() {
         // 初始隐藏通行证面板
         if (this.passCardPanel) {
             this.passCardPanel.active = false;
+            
+            // 确保面板有UIOpacity组件
+            this.passCardOpacity = this.passCardPanel.getComponent(UIOpacity);
+            if (!this.passCardOpacity) {
+                this.passCardOpacity = this.passCardPanel.addComponent(UIOpacity);
+            }
+
+            // 如果有阴影节点,初始化它
+            if (this.cardShadow) {
+                this.shadowOpacity = this.cardShadow.getComponent(UIOpacity);
+                if (!this.shadowOpacity) {
+                    this.shadowOpacity = this.cardShadow.addComponent(UIOpacity);
+                }
+                this.cardShadow.active = false;
+            } else {
+                // 自动创建阴影节点
+                this.createShadowNode();
+            }
         }
 
         // 注册关闭按钮点击事件
@@ -77,6 +111,35 @@ export class PassCardManager extends Component {
         }
     }
 
+    /**
+     * 创建卡片阴影节点
+     */
+    private createShadowNode(): void {
+        if (!this.passCardPanel || this.cardShadow) return;
+
+        // 创建阴影节点
+        this.cardShadow = new Node('CardShadow');
+        this.passCardPanel.parent.insertChild(this.cardShadow, this.passCardPanel.getSiblingIndex());
+        
+        // 设置阴影大小
+        const cardTransform = this.passCardPanel.getComponent(UITransform);
+        if (cardTransform) {
+            const shadowTransform = this.cardShadow.addComponent(UITransform);
+            shadowTransform.width = cardTransform.width * 0.9;
+            shadowTransform.height = cardTransform.height * 0.9;
+        }
+        
+        // 添加阴影图像
+        const shadowSprite = this.cardShadow.addComponent(Sprite);
+        shadowSprite.color = new Color(0, 0, 0, 180);
+        
+        // 添加透明度控制
+        this.shadowOpacity = this.cardShadow.addComponent(UIOpacity);
+        this.shadowOpacity.opacity = 0;
+        
+        this.cardShadow.active = false;
+    }
+
     /**
      * 关闭按钮点击事件处理
      */
@@ -103,9 +166,129 @@ export class PassCardManager extends Component {
         // 更新UI显示
         this.updatePassCardUI();
 
-        // 显示面板
+        // 显示面板并播放动画
         this.passCardPanel.active = true;
+        this.playShowAnimation();
         console.log('通行证面板已显示');
+
+        // 确保阴影也显示
+        if (this.cardShadow) {
+            this.cardShadow.active = true;
+        }
+    }
+
+    /**
+     * 播放显示动画
+     */
+    private playShowAnimation(): void {
+        // 重置面板的初始状态
+        this.passCardPanel.setScale(new Vec3(0.6, 0.6, 1));
+        this.passCardPanel.setPosition(new Vec3(200, -100, 0)); // 从更远的地方传递过来
+        this.passCardPanel.setRotationFromEuler(new Vec3(0, 0, -15)); // 初始旋转角度
+        
+        // 设置初始透明度
+        if (this.passCardOpacity) {
+            this.passCardOpacity.opacity = 0;
+        }
+
+        // 设置阴影初始状态
+        if (this.cardShadow && this.shadowOpacity) {
+            this.cardShadow.setPosition(new Vec3(210, -105, 0)); // 稍微偏移
+            this.cardShadow.setScale(new Vec3(0.6, 0.6, 1));
+            this.cardShadow.setRotationFromEuler(new Vec3(0, 0, -15));
+            this.shadowOpacity.opacity = 0;
+        }
+
+        // 创建动画序列 - 模拟传递过来的感觉
+        tween(this.passCardPanel)
+            // 第一阶段:接近动作,带有轻微旋转
+            .to(this.animDuration * 0.7, { 
+                scale: new Vec3(1.05, 1.05, 1),
+                position: new Vec3(10, 5, 0),
+                eulerAngles: new Vec3(0, 0, 5) // 轻微旋转到反方向
+            }, {
+                easing: 'quartOut'
+            })
+            // 第二阶段:最终位置,带有轻微的回弹效果
+            .to(this.animDuration * 0.3, { 
+                scale: new Vec3(1, 1, 1),
+                position: new Vec3(0, 0, 0),
+                eulerAngles: new Vec3(0, 0, 0)
+            }, {
+                easing: 'backOut'
+            })
+            // 第三阶段:添加轻微的抖动效果,模拟卡片放在桌面上的感觉
+            .call(() => {
+                this.addShakeEffect();
+            })
+            .start();
+            
+        // 为阴影创建跟随动画
+        if (this.cardShadow) {
+            tween(this.cardShadow)
+                // 第一阶段
+                .to(this.animDuration * 0.7, { 
+                    scale: new Vec3(1.05, 1.05, 1),
+                    position: new Vec3(15, 2, 0), // 稍微偏移以产生阴影效果
+                    eulerAngles: new Vec3(0, 0, 5)
+                }, {
+                    easing: 'quartOut'
+                })
+                // 第二阶段
+                .to(this.animDuration * 0.3, { 
+                    scale: new Vec3(1, 1, 1),
+                    position: new Vec3(5, -3, 0), // 保持偏移
+                    eulerAngles: new Vec3(0, 0, 0)
+                }, {
+                    easing: 'backOut'
+                })
+                .start();
+                
+            // 阴影透明度动画
+            if (this.shadowOpacity) {
+                tween(this.shadowOpacity)
+                    .delay(this.animDuration * 0.2)
+                    .to(this.animDuration * 0.5, { opacity: 100 }) // 半透明阴影
+                    .start();
+            }
+        }
+            
+        // 单独为透明度创建动画
+        if (this.passCardOpacity) {
+            tween(this.passCardOpacity)
+                .to(this.animDuration * 0.5, { opacity: 255 })
+                .start();
+        }
+    }
+
+    /**
+     * 添加轻微的抖动效果
+     */
+    private addShakeEffect(): void {
+        // 创建一个轻微的抖动效果,模拟卡片被放置的震感
+        const originalPos = new Vec3(0, 0, 0);
+        const shakeAmount = 1.5; // 抖动幅度
+        
+        tween(this.passCardPanel)
+            .to(0.05, { position: new Vec3(shakeAmount, 0, 0) })
+            .to(0.05, { position: new Vec3(-shakeAmount, 0, 0) })
+            .to(0.05, { position: new Vec3(shakeAmount * 0.5, 0, 0) })
+            .to(0.05, { position: new Vec3(-shakeAmount * 0.5, 0, 0) })
+            .to(0.05, { position: originalPos })
+            .start();
+
+        // 同时给阴影添加抖动效果
+        if (this.cardShadow) {
+            const shadowOriginalPos = new Vec3(5, -3, 0); // 阴影的原始位置
+            
+            tween(this.cardShadow)
+                .to(0.05, { position: new Vec3(shadowOriginalPos.x + shakeAmount, shadowOriginalPos.y, 0) })
+                .to(0.05, { position: new Vec3(shadowOriginalPos.x - shakeAmount, shadowOriginalPos.y, 0) })
+                .to(0.05, { position: new Vec3(shadowOriginalPos.x + shakeAmount * 0.5, shadowOriginalPos.y, 0) })
+                .to(0.05, { position: new Vec3(shadowOriginalPos.x - shakeAmount * 0.5, shadowOriginalPos.y, 0) })
+                .to(0.05, { position: shadowOriginalPos })
+                .start();
+        }
     }
 
     /**
@@ -115,13 +298,85 @@ export class PassCardManager extends Component {
         console.log('hidePassCard 被调用');
         
         if (this.passCardPanel) {
-            this.passCardPanel.active = false;
-            console.log('通行证面板已隐藏');
+            // 播放隐藏动画
+            this.playHideAnimation(() => {
+                this.passCardPanel.active = false;
+                console.log('通行证面板已隐藏');
+            });
         } else {
             console.error('通行证面板未设置,无法隐藏');
         }
     }
 
+    /**
+     * 播放隐藏动画
+     * @param callback 动画完成回调
+     */
+    private playHideAnimation(callback?: Function): void {
+        // 创建移动和缩放动画 - 模拟被人收回的感觉
+        tween(this.passCardPanel)
+            // 首先上抬一点,像是被拿起
+            .to(this.animDuration * 0.2, {
+                position: new Vec3(0, 10, 0),
+                eulerAngles: new Vec3(0, 0, -5)
+            }, {
+                easing: 'sineOut'
+            })
+            // 然后向远处移动,同时旋转
+            .to(this.animDuration * 0.6, {
+                scale: new Vec3(0.7, 0.7, 1),
+                position: new Vec3(-180, 50, 0),
+                eulerAngles: new Vec3(0, 0, -15)
+            }, {
+                easing: 'quartIn'
+            })
+            .call(() => {
+                if (callback) callback();
+            })
+            .start();
+            
+        // 阴影的隐藏动画
+        if (this.cardShadow) {
+            tween(this.cardShadow)
+                // 首先上抬一点,跟随卡片
+                .to(this.animDuration * 0.2, {
+                    position: new Vec3(5, 7, 0), // 保持偏移
+                    eulerAngles: new Vec3(0, 0, -5)
+                }, {
+                    easing: 'sineOut'
+                })
+                // 然后向远处移动
+                .to(this.animDuration * 0.6, {
+                    scale: new Vec3(0.7, 0.7, 1),
+                    position: new Vec3(-175, 47, 0), // 保持偏移
+                    eulerAngles: new Vec3(0, 0, -15)
+                }, {
+                    easing: 'quartIn'
+                })
+                .call(() => {
+                    if (this.cardShadow) {
+                        this.cardShadow.active = false;
+                    }
+                })
+                .start();
+                
+            // 阴影透明度动画
+            if (this.shadowOpacity) {
+                tween(this.shadowOpacity)
+                    .to(this.animDuration * 0.5, { opacity: 0 })
+                    .start();
+            }
+        }
+            
+        // 单独为透明度创建动画,稍微延迟开始
+        if (this.passCardOpacity) {
+            tween(this.passCardOpacity)
+                .delay(this.animDuration * 0.1)
+                .to(this.animDuration * 0.7, { opacity: 0 })
+                .start();
+        }
+    }
+
     /**
      * 更新通行证UI
      */

+ 97 - 2
assets/scripts/PersonalInfoManager.ts

@@ -1,4 +1,4 @@
-import { _decorator, Component, Node, Button, Sprite, Label, SpriteFrame, resources } from 'cc';
+import { _decorator, Component, Node, Button, Sprite, Label, SpriteFrame, resources, tween, Vec3, UIOpacity } from 'cc';
 const { ccclass, property } = _decorator;
 
 @ccclass('PersonalInfoManager')
@@ -27,13 +27,26 @@ export class PersonalInfoManager extends Component {
     })
     infoText: Label = null;
     
+    @property({
+        tooltip: '动画时间(秒)',
+        range: [0.1, 2.0, 0.1]
+    })
+    animDuration: number = 0.5;
+    
     // 当前显示的角色ID
     private currentCharacterId: number = -1;
+    private panelOpacity: UIOpacity = null;
     
     start() {
         // 初始化隐藏面板
         if (this.personalInfoPanel) {
             this.personalInfoPanel.active = false;
+            
+            // 确保面板有UIOpacity组件
+            this.panelOpacity = this.personalInfoPanel.getComponent(UIOpacity);
+            if (!this.panelOpacity) {
+                this.panelOpacity = this.personalInfoPanel.addComponent(UIOpacity);
+            }
         }
         
         // 注册关闭按钮事件
@@ -56,6 +69,50 @@ export class PersonalInfoManager extends Component {
         if (this.personalInfoPanel) {
             this.personalInfoPanel.active = true;
             this.personalInfoPanel.setSiblingIndex(999);
+            
+            // 播放从口袋拿出来的动画
+            this.playPocketOutAnimation();
+        }
+    }
+    
+    /**
+     * 播放从口袋掏出的动画
+     */
+    private playPocketOutAnimation() {
+        // 设置初始状态 - 从右侧口袋拿出
+        this.personalInfoPanel.setScale(new Vec3(0.7, 0.2, 1)); // 扁平状态
+        this.personalInfoPanel.setPosition(new Vec3(200, -180, 0)); // 从右侧口袋位置开始
+        this.personalInfoPanel.setRotationFromEuler(new Vec3(0, 0, -20)); // 初始右倾斜角度
+        
+        if (this.panelOpacity) {
+            this.panelOpacity.opacity = 50; // 半透明开始
+        }
+        
+        // 创建动画 - 掏出口袋的感觉
+        tween(this.personalInfoPanel)
+            // 先上移一点,同时展开
+            .to(this.animDuration * 0.6, {
+                scale: new Vec3(0.9, 0.9, 1), 
+                position: new Vec3(100, -50, 0),
+                eulerAngles: new Vec3(0, 0, -10) // 轻微倾斜,像是手拿着
+            }, {
+                easing: 'quadOut'
+            })
+            // 然后放到正确位置并恢复正常大小
+            .to(this.animDuration * 0.4, {
+                scale: new Vec3(1, 1, 1),
+                position: new Vec3(0, 0, 0),
+                eulerAngles: new Vec3(0, 0, 0)
+            }, {
+                easing: 'backOut'
+            })
+            .start();
+        
+        // 透明度动画
+        if (this.panelOpacity) {
+            tween(this.panelOpacity)
+                .to(this.animDuration * 0.7, { opacity: 255 })
+                .start();
         }
     }
     
@@ -64,7 +121,45 @@ export class PersonalInfoManager extends Component {
      */
     public hidePersonalInfoPanel() {
         if (this.personalInfoPanel) {
-            this.personalInfoPanel.active = false;
+            // 播放放回口袋的动画
+            this.playPocketInAnimation(() => {
+                this.personalInfoPanel.active = false;
+            });
+        }
+    }
+    
+    /**
+     * 播放放回口袋的动画
+     */
+    private playPocketInAnimation(callback?: Function) {
+        // 创建放回口袋的动画
+        tween(this.personalInfoPanel)
+            // 先抬起并旋转
+            .to(this.animDuration * 0.3, {
+                position: new Vec3(80, -30, 0),
+                eulerAngles: new Vec3(0, 0, -10),
+                scale: new Vec3(0.95, 0.95, 1)
+            }, {
+                easing: 'sineIn'
+            })
+            // 然后放入口袋
+            .to(this.animDuration * 0.4, {
+                position: new Vec3(200, -180, 0),
+                eulerAngles: new Vec3(0, 0, -20),
+                scale: new Vec3(0.6, 0.2, 1) // 扁平化,像塞入口袋
+            }, {
+                easing: 'quadIn'
+            })
+            .call(() => {
+                if (callback) callback();
+            })
+            .start();
+        
+        // 透明度动画
+        if (this.panelOpacity) {
+            tween(this.panelOpacity)
+                .to(this.animDuration * 0.6, { opacity: 0 })
+                .start();
         }
     }
     

+ 112 - 5
assets/scripts/RosterManager.ts

@@ -1,4 +1,4 @@
-import { _decorator, Component, Node, Button, Animation, Sprite, SpriteFrame, resources, assetManager, Label, Layout, instantiate } from 'cc';
+import { _decorator, Component, Node, Button, Animation, Sprite, SpriteFrame, resources, assetManager, Label, Layout, instantiate, tween, Vec3, UIOpacity } from 'cc';
 const { ccclass, property } = _decorator;
 
 /**
@@ -35,9 +35,23 @@ export class RosterManager extends Component {
     })
     avatarResourcePath: string = "avatars/";
     
+    @property({
+        tooltip: '动画时间(秒)',
+        range: [0.1, 2.0, 0.1]
+    })
+    animDuration: number = 0.5;
+    
+    @property({
+        tooltip: '是否使用自定义动画',
+        type: Boolean
+    })
+    useCustomAnimation: boolean = true;
+    
     // 保存头像引用的字典
     private avatarMap: Map<string, SpriteFrame> = new Map();
     
+    // 用于动画控制的组件
+    private rosterOpacity: UIOpacity = null;
 
     // 头像项预制体
     @property({
@@ -50,6 +64,12 @@ export class RosterManager extends Component {
         // 初始化隐藏名单面板
         if (this.rosterPanel) {
             this.rosterPanel.active = false;
+            
+            // 确保面板有UIOpacity组件
+            this.rosterOpacity = this.rosterPanel.getComponent(UIOpacity);
+            if (!this.rosterOpacity) {
+                this.rosterOpacity = this.rosterPanel.addComponent(UIOpacity);
+            }
         }
         
         // 注册关闭按钮点击事件
@@ -86,8 +106,12 @@ export class RosterManager extends Component {
             // 确保面板在最前面
             this.rosterPanel.setSiblingIndex(999);
             
-            // 播放打开动画(如果有)
-            if (this.rosterAnimation) {
+            // 判断使用哪种动画方式
+            if (this.useCustomAnimation) {
+                // 使用自定义的口袋动画效果
+                this.playPocketOutAnimation();
+            } else if (this.rosterAnimation) {
+                // 使用预设的动画组件
                 this.rosterAnimation.play('roster_open');
             }
         } else {
@@ -95,6 +119,47 @@ export class RosterManager extends Component {
         }
     }
     
+    /**
+     * 播放从口袋掏出的动画
+     */
+    private playPocketOutAnimation() {
+        // 设置初始状态 - 名单从左侧口袋掏出
+        this.rosterPanel.setScale(new Vec3(0.4, 0.2, 1)); // 扁平状态
+        this.rosterPanel.setPosition(new Vec3(-150, -250, 0)); // 从左下角(口袋位置)开始
+        this.rosterPanel.setRotationFromEuler(new Vec3(0, 0, 25)); // 初始倾斜角度,向左倾斜
+        
+        if (this.rosterOpacity) {
+            this.rosterOpacity.opacity = 50; // 半透明开始
+        }
+        
+        // 创建动画 - 掏出口袋的感觉
+        tween(this.rosterPanel)
+            // 先抽出动作,有种抽纸张的感觉
+            .to(this.animDuration * 0.5, {
+                scale: new Vec3(0.7, 0.8, 1), 
+                position: new Vec3(-100, -100, 0),
+                eulerAngles: new Vec3(0, 0, 10) // 减少倾斜
+            }, {
+                easing: 'cubicOut'
+            })
+            // 然后放到正确位置
+            .to(this.animDuration * 0.4, {
+                scale: new Vec3(1, 1, 1),
+                position: new Vec3(0, 0, 0),
+                eulerAngles: new Vec3(0, 0, 0)
+            }, {
+                easing: 'backOut'
+            })
+            .start();
+        
+        // 透明度动画
+        if (this.rosterOpacity) {
+            tween(this.rosterOpacity)
+                .to(this.animDuration * 0.7, { opacity: 255 })
+                .start();
+        }
+    }
+    
     /**
      * 隐藏名单面板
      */
@@ -102,8 +167,15 @@ export class RosterManager extends Component {
         console.log('hideRosterPanel被调用');
         
         if (this.rosterPanel) {
-            // 播放关闭动画,并在动画结束后隐藏面板
-            if (this.rosterAnimation) {
+            // 判断使用哪种动画方式
+            if (this.useCustomAnimation) {
+                // 使用自定义的口袋动画效果
+                this.playPocketInAnimation(() => {
+                    this.rosterPanel.active = false;
+                    console.log('名单面板已隐藏(动画后)');
+                });
+            } else if (this.rosterAnimation) {
+                // 使用预设的动画组件
                 this.rosterAnimation.play('roster_close');
                 this.scheduleOnce(() => {
                     this.rosterPanel.active = false;
@@ -116,6 +188,41 @@ export class RosterManager extends Component {
         }
     }
     
+    /**
+     * 播放放回口袋的动画
+     */
+    private playPocketInAnimation(callback?: Function) {
+        // 创建放回口袋的动画
+        tween(this.rosterPanel)
+            // 先抬起并旋转准备放回
+            .to(this.animDuration * 0.3, {
+                position: new Vec3(-50, -50, 0),
+                eulerAngles: new Vec3(0, 0, 15),
+                scale: new Vec3(0.9, 0.9, 1)
+            }, {
+                easing: 'sineIn'
+            })
+            // 然后塞入口袋
+            .to(this.animDuration * 0.4, {
+                position: new Vec3(-150, -250, 0),
+                eulerAngles: new Vec3(0, 0, 25),
+                scale: new Vec3(0.4, 0.2, 1) // 扁平化,像塞入口袋
+            }, {
+                easing: 'quadIn'
+            })
+            .call(() => {
+                if (callback) callback();
+            })
+            .start();
+        
+        // 透明度动画
+        if (this.rosterOpacity) {
+            tween(this.rosterOpacity)
+                .to(this.animDuration * 0.6, { opacity: 0 })
+                .start();
+        }
+    }
+    
     /**
      * 预加载头像资源
      * @param avatarIds 头像ID数组