import { _decorator, Component, Node, Button, Animation, Sprite, SpriteFrame, resources, Label, Layout, instantiate, tween, Vec3, UIOpacity } from 'cc'; import { TodayListItem } from './DataManager'; const { ccclass, property } = _decorator; /** * 名单管理器,控制今日名单UI的显示和隐藏,显示今日允许进入的人员名单 */ @ccclass('RosterManager') export class RosterManager extends Component { @property({ type: Node, tooltip: '名单UI面板' }) rosterPanel: Node = null; @property({ type: Button, tooltip: '关闭按钮' }) closeButton: Button = null; @property({ type: Node, tooltip: '人员头像容器' }) avatarContainer: Node = null; @property({ tooltip: '动画时间(秒)', range: [0.1, 2.0, 0.1] }) animDuration: number = 0.5; @property({ tooltip: '是否使用自定义动画', type: Boolean }) useCustomAnimation: boolean = true; // 用于动画控制的组件 private rosterOpacity: UIOpacity = null; // 头像项预制体 @property({ type: Node, tooltip: '头像项预制体' }) avatarItemPrefab: Node = null; // 当前显示的名单数据 private currentRosterItems: TodayListItem[] = []; start() { // 初始化隐藏名单面板 if (this.rosterPanel) { this.rosterPanel.active = false; // 确保面板有UIOpacity组件 this.rosterOpacity = this.rosterPanel.getComponent(UIOpacity); if (!this.rosterOpacity) { this.rosterOpacity = this.rosterPanel.addComponent(UIOpacity); } } // 注册关闭按钮点击事件 this.setupCloseButton(); } /** * 设置关闭按钮事件 */ private setupCloseButton() { if (this.closeButton) { // 移除可能已存在的事件监听 this.closeButton.node.off('click'); // 使用按钮的点击事件监听方式 this.closeButton.node.on('click', () => { console.log('名单关闭按钮被点击'); this.hideRosterPanel(); }, this); console.log('名单关闭按钮事件已注册'); } else { console.error('名单关闭按钮未设置'); } } /** * 显示名单面板 */ public showRosterPanel() { if (this.rosterPanel) { this.rosterPanel.active = true; // 确保面板在最前面 this.rosterPanel.setSiblingIndex(999); // 判断使用哪种动画方式 if (this.useCustomAnimation) { // 使用自定义的口袋动画效果 this.playPocketOutAnimation(); } } else { console.error('名单面板未设置'); } } /** * 播放从口袋掏出的动画 */ 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(); } } /** * 隐藏名单面板 */ public hideRosterPanel() { console.log('hideRosterPanel被调用'); if (this.rosterPanel) { // 判断使用哪种动画方式 if (this.useCustomAnimation) { // 使用自定义的口袋动画效果 this.playPocketInAnimation(() => { this.rosterPanel.active = false; console.log('名单面板已隐藏(动画后)'); }); } else { this.rosterPanel.active = false; console.log('名单面板已隐藏(立即)'); } } } /** * 播放放回口袋的动画 */ 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 todayListItems 由GameFlowManager提供的今日名单数据,必须符合TodayListItem接口 */ public setTodayList(todayListItems: TodayListItem[]): void { console.log("设置今日名单数据"); if (!todayListItems || todayListItems.length === 0) { console.warn('今日名单数据为空'); return; } // 保存当前名单数据 this.currentRosterItems = todayListItems; // 显示名单数据 this.displayCurrentRoster(); } /** * 显示当前保存的名单数据 */ private displayCurrentRoster(): void { if (!this.currentRosterItems || this.currentRosterItems.length === 0) { console.warn('当前没有名单数据可显示'); return; } this.displayListItems(this.currentRosterItems); } /** * 显示名单项目列表 * @param listItems 名单项目列表,必须包含name和avatarPath属性 */ private displayListItems(listItems: TodayListItem[]): void { console.log("开始显示名单项目列表..."); if (!this.avatarContainer) { console.error('无法显示名单:avatarContainer未设置'); return; } if (!listItems || listItems.length === 0) { console.error('无法显示名单:名单数据为空'); return; } // 检查预制体 if (!this.avatarItemPrefab) { console.error('头像项预制体未设置'); return; } console.log(`准备显示${listItems.length}个名单项目`); // 清空现有内容 this.avatarContainer.removeAllChildren(); // 遍历所有人员数据,创建头像项 listItems.forEach(item => { // 创建新的头像项 const avatarItem = instantiate(this.avatarItemPrefab); this.avatarContainer.addChild(avatarItem); // 设置姓名 const nameLabel = avatarItem.getComponentInChildren(Label); if (nameLabel && item.name) { nameLabel.string = item.name; } // 加载头像 if (item.avatarPath) { // 移除可能存在的.png后缀 const pathWithoutExtension = item.avatarPath.replace(/\.png$/, ''); // 确保路径以/spriteFrame结尾 const path = pathWithoutExtension.endsWith('/spriteFrame') ? pathWithoutExtension : `${pathWithoutExtension}/spriteFrame`; resources.load(path, SpriteFrame, (err, spriteFrame) => { if (err) { console.error(`加载头像失败: ${path}`, err); return; } // 查找头像显示组件 const sprite = avatarItem.getComponentInChildren(Sprite); if (sprite) { sprite.spriteFrame = spriteFrame; } }); } }); // 更新布局 const layout = this.avatarContainer.getComponent(Layout); if (layout) { this.scheduleOnce(() => { layout.updateLayout(); }, 0.1); } } onDestroy() { // 移除按钮事件监听 if (this.closeButton) { this.closeButton.node.off('click'); } } }