import { _decorator, Component, Node, Label, Sprite, Button, resources, SpriteFrame, tween, Vec3, UIOpacity, UITransform, Color } from 'cc'; import { DataManager } from './DataManager'; const { ccclass, property } = _decorator; /** * 通行证数据接口 (与DataManager中的PassInfo保持一致) */ interface PassCardData { name: string; // 人物姓名 room: string; // 房间号 identityId: string; // 身份ID (注意:这里与之前的id字段不同) reason: string; // 原因 avatarPath: string; // 头像路径 hasStamp: boolean; // 是否有印章 } @ccclass('PassCardManager') export class PassCardManager extends Component { @property({ type: Node, tooltip: '通行证面板节点' }) passCardPanel: Node = null; @property({ type: Label, tooltip: '姓名标签' }) nameLabel: Label = null; @property({ type: Label, tooltip: '房间号标签' }) roomLabel: Label = null; @property({ type: Label, tooltip: '身份ID标签' }) idLabel: Label = null; @property({ type: Label, tooltip: '原因标签' }) reasonLabel: Label = null; @property({ type: Sprite, tooltip: '头像显示组件' }) avatarSprite: Sprite = null; @property({ type: Button, tooltip: '关闭按钮' }) closeButton: Button = null; @property({ tooltip: '通行证面板动画时长(秒)', range: [0.1, 2.0, 0.1] }) animDuration: number = 0.5; @property({ type: Node, tooltip: '卡片阴影节点(可选)' }) cardShadow: Node = null; @property({ type: DataManager, tooltip: '数据管理器引用' }) dataManager: DataManager = 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(); } } // 注册关闭按钮点击事件 if (this.closeButton) { // 先移除可能已存在的事件监听(防止重复注册) this.closeButton.node.off(Button.EventType.CLICK, this.onCloseButtonClick, this); // 重新注册点击事件 this.closeButton.node.on(Button.EventType.CLICK, this.onCloseButtonClick, this); console.log('关闭按钮事件已注册'); } else { console.error('关闭按钮未设置'); } } /** * 创建卡片阴影节点 */ 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; } /** * 关闭按钮点击事件处理 */ private onCloseButtonClick(): void { console.log('关闭按钮被点击'); this.hidePassCard(); } /** * 显示通行证 * @param passDataOrCharacterId 通行证数据或角色ID */ public showPassCard(passDataOrCharacterId: PassCardData | number): void { console.log('PassCardManager.showPassCard 被调用:', passDataOrCharacterId); if (!this.passCardPanel) { console.error('通行证面板未设置'); return; } // 根据参数类型处理数据 if (typeof passDataOrCharacterId === 'number') { // 如果传入的是角色ID,则从DataManager获取通行证数据 if (!this.dataManager) { console.error('数据管理器未设置,无法通过角色ID获取通行证数据'); return; } const characterId = passDataOrCharacterId; const passInfo = this.dataManager.getNPCPassInfo(characterId); if (!passInfo) { console.error(`无法获取角色ID ${characterId} 的通行证数据`); return; } // 保存当前数据 this.currentPassData = passInfo; } else { // 直接使用传入的通行证数据 this.currentPassData = passDataOrCharacterId; } // 更新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(); } } /** * 隐藏通行证 */ public hidePassCard(): void { console.log('hidePassCard 被调用'); if (this.passCardPanel) { // 播放隐藏动画 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 */ private updatePassCardUI(): void { console.log('更新通行证UI'); if (!this.currentPassData) { console.error('没有通行证数据'); return; } // 更新文本内容 if (this.nameLabel) { this.nameLabel.string = this.currentPassData.name || ''; console.log('设置姓名:', this.currentPassData.name); } else { console.error('姓名标签未设置'); } if (this.roomLabel) { this.roomLabel.string = this.currentPassData.room || ''; console.log('设置房间号:', this.currentPassData.room); } else { console.error('房间号标签未设置'); } if (this.idLabel) { // 注意这里使用identityId而不是之前的id this.idLabel.string = this.currentPassData.identityId || ''; console.log('设置ID:', this.currentPassData.identityId); } else { console.error('ID标签未设置'); } if (this.reasonLabel) { this.reasonLabel.string = this.currentPassData.reason || ''; console.log('设置原因:', this.currentPassData.reason); } else { console.error('原因标签未设置'); } // 加载并显示头像(使用avatarPath) this.loadAvatar(); } /** * 加载角色头像 */ private loadAvatar(): void { if (!this.avatarSprite) { console.error('头像Sprite组件未设置'); return; } if (!this.currentPassData || !this.currentPassData.avatarPath) { console.error('无效的头像路径'); return; } const avatarPath = this.currentPassData.avatarPath; console.log('尝试加载头像路径:', avatarPath); // 移除.png后缀 const pathWithoutExtension = avatarPath.replace(/\.png$/, ''); // 处理路径,确保以'/spriteFrame'结尾 const finalPath = pathWithoutExtension.endsWith('/spriteFrame') ? pathWithoutExtension : `${pathWithoutExtension}/spriteFrame`; // 加载头像资源 resources.load(finalPath, SpriteFrame, (err, spriteFrame) => { if (err) { console.error(`加载头像失败: ${finalPath}`, err); } else { // 设置头像 this.avatarSprite.spriteFrame = spriteFrame; console.log('头像加载成功: ' + finalPath); } }); } onDestroy() { // 清理事件监听 if (this.closeButton) { this.closeButton.node.off(Button.EventType.CLICK, this.onCloseButtonClick, this); } } }