RosterManager.ts 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. import { _decorator, Component, Node, Button, Animation, Sprite, SpriteFrame, resources, Label, Layout, instantiate, tween, Vec3, UIOpacity } from 'cc';
  2. import { TodayListItem } from './DataManager';
  3. const { ccclass, property } = _decorator;
  4. /**
  5. * 名单管理器,控制今日名单UI的显示和隐藏,显示今日允许进入的人员名单
  6. */
  7. @ccclass('RosterManager')
  8. export class RosterManager extends Component {
  9. @property({
  10. type: Node,
  11. tooltip: '名单UI面板'
  12. })
  13. rosterPanel: Node = null;
  14. @property({
  15. type: Button,
  16. tooltip: '关闭按钮'
  17. })
  18. closeButton: Button = null;
  19. @property({
  20. type: Node,
  21. tooltip: '人员头像容器'
  22. })
  23. avatarContainer: Node = null;
  24. @property({
  25. tooltip: '动画时间(秒)',
  26. range: [0.1, 2.0, 0.1]
  27. })
  28. animDuration: number = 0.5;
  29. @property({
  30. tooltip: '是否使用自定义动画',
  31. type: Boolean
  32. })
  33. useCustomAnimation: boolean = true;
  34. // 用于动画控制的组件
  35. private rosterOpacity: UIOpacity = null;
  36. // 头像项预制体
  37. @property({
  38. type: Node,
  39. tooltip: '头像项预制体'
  40. })
  41. avatarItemPrefab: Node = null;
  42. // 当前显示的名单数据
  43. private currentRosterItems: TodayListItem[] = [];
  44. start() {
  45. // 初始化隐藏名单面板
  46. if (this.rosterPanel) {
  47. this.rosterPanel.active = false;
  48. // 确保面板有UIOpacity组件
  49. this.rosterOpacity = this.rosterPanel.getComponent(UIOpacity);
  50. if (!this.rosterOpacity) {
  51. this.rosterOpacity = this.rosterPanel.addComponent(UIOpacity);
  52. }
  53. }
  54. // 注册关闭按钮点击事件
  55. this.setupCloseButton();
  56. }
  57. /**
  58. * 设置关闭按钮事件
  59. */
  60. private setupCloseButton() {
  61. if (this.closeButton) {
  62. // 移除可能已存在的事件监听
  63. this.closeButton.node.off('click');
  64. // 使用按钮的点击事件监听方式
  65. this.closeButton.node.on('click', () => {
  66. console.log('名单关闭按钮被点击');
  67. this.hideRosterPanel();
  68. }, this);
  69. console.log('名单关闭按钮事件已注册');
  70. } else {
  71. console.error('名单关闭按钮未设置');
  72. }
  73. }
  74. /**
  75. * 显示名单面板
  76. */
  77. public showRosterPanel() {
  78. if (this.rosterPanel) {
  79. this.rosterPanel.active = true;
  80. // 确保面板在最前面
  81. this.rosterPanel.setSiblingIndex(999);
  82. // 判断使用哪种动画方式
  83. if (this.useCustomAnimation) {
  84. // 使用自定义的口袋动画效果
  85. this.playPocketOutAnimation();
  86. }
  87. } else {
  88. console.error('名单面板未设置');
  89. }
  90. }
  91. /**
  92. * 播放从口袋掏出的动画
  93. */
  94. private playPocketOutAnimation() {
  95. // 设置初始状态 - 名单从左侧口袋掏出
  96. this.rosterPanel.setScale(new Vec3(0.4, 0.2, 1)); // 扁平状态
  97. this.rosterPanel.setPosition(new Vec3(-150, -250, 0)); // 从左下角(口袋位置)开始
  98. this.rosterPanel.setRotationFromEuler(new Vec3(0, 0, 25)); // 初始倾斜角度,向左倾斜
  99. if (this.rosterOpacity) {
  100. this.rosterOpacity.opacity = 50; // 半透明开始
  101. }
  102. // 创建动画 - 掏出口袋的感觉
  103. tween(this.rosterPanel)
  104. // 先抽出动作,有种抽纸张的感觉
  105. .to(this.animDuration * 0.5, {
  106. scale: new Vec3(0.7, 0.8, 1),
  107. position: new Vec3(-100, -100, 0),
  108. eulerAngles: new Vec3(0, 0, 10) // 减少倾斜
  109. }, {
  110. easing: 'cubicOut'
  111. })
  112. // 然后放到正确位置
  113. .to(this.animDuration * 0.4, {
  114. scale: new Vec3(1, 1, 1),
  115. position: new Vec3(0, 0, 0),
  116. eulerAngles: new Vec3(0, 0, 0)
  117. }, {
  118. easing: 'backOut'
  119. })
  120. .start();
  121. // 透明度动画
  122. if (this.rosterOpacity) {
  123. tween(this.rosterOpacity)
  124. .to(this.animDuration * 0.7, { opacity: 255 })
  125. .start();
  126. }
  127. }
  128. /**
  129. * 隐藏名单面板
  130. */
  131. public hideRosterPanel() {
  132. console.log('hideRosterPanel被调用');
  133. if (this.rosterPanel) {
  134. // 判断使用哪种动画方式
  135. if (this.useCustomAnimation) {
  136. // 使用自定义的口袋动画效果
  137. this.playPocketInAnimation(() => {
  138. this.rosterPanel.active = false;
  139. console.log('名单面板已隐藏(动画后)');
  140. });
  141. } else {
  142. this.rosterPanel.active = false;
  143. console.log('名单面板已隐藏(立即)');
  144. }
  145. }
  146. }
  147. /**
  148. * 播放放回口袋的动画
  149. */
  150. private playPocketInAnimation(callback?: Function) {
  151. // 创建放回口袋的动画
  152. tween(this.rosterPanel)
  153. // 先抬起并旋转准备放回
  154. .to(this.animDuration * 0.3, {
  155. position: new Vec3(-50, -50, 0),
  156. eulerAngles: new Vec3(0, 0, 15),
  157. scale: new Vec3(0.9, 0.9, 1)
  158. }, {
  159. easing: 'sineIn'
  160. })
  161. // 然后塞入口袋
  162. .to(this.animDuration * 0.4, {
  163. position: new Vec3(-150, -250, 0),
  164. eulerAngles: new Vec3(0, 0, 25),
  165. scale: new Vec3(0.4, 0.2, 1) // 扁平化,像塞入口袋
  166. }, {
  167. easing: 'quadIn'
  168. })
  169. .call(() => {
  170. if (callback) callback();
  171. })
  172. .start();
  173. // 透明度动画
  174. if (this.rosterOpacity) {
  175. tween(this.rosterOpacity)
  176. .to(this.animDuration * 0.6, { opacity: 0 })
  177. .start();
  178. }
  179. }
  180. /**
  181. * 设置并显示今日名单
  182. * @param todayListItems 由GameFlowManager提供的今日名单数据
  183. */
  184. public setTodayList(todayListItems: TodayListItem[]): void {
  185. console.log("设置今日名单数据");
  186. if (!todayListItems || todayListItems.length === 0) {
  187. console.warn('今日名单数据为空');
  188. return;
  189. }
  190. // 保存当前名单数据
  191. this.currentRosterItems = todayListItems;
  192. // 显示名单数据
  193. this.displayCurrentRoster();
  194. }
  195. /**
  196. * 显示当前保存的名单数据
  197. */
  198. private displayCurrentRoster(): void {
  199. if (!this.currentRosterItems || this.currentRosterItems.length === 0) {
  200. console.warn('当前没有名单数据可显示');
  201. return;
  202. }
  203. this.displayListItems(this.currentRosterItems);
  204. }
  205. /**
  206. * 显示名单项目列表
  207. * @param listItems 名单项目列表,必须包含name和avatarPath属性
  208. */
  209. private displayListItems(listItems: TodayListItem[]): void {
  210. console.log("开始显示名单项目列表...");
  211. if (!this.avatarContainer) {
  212. console.error('无法显示名单:avatarContainer未设置');
  213. return;
  214. }
  215. if (!listItems || listItems.length === 0) {
  216. console.error('无法显示名单:名单数据为空');
  217. return;
  218. }
  219. // 检查预制体
  220. if (!this.avatarItemPrefab) {
  221. console.error('头像项预制体未设置');
  222. return;
  223. }
  224. console.log(`准备显示${listItems.length}个名单项目`);
  225. // 清空现有内容
  226. this.avatarContainer.removeAllChildren();
  227. // 遍历所有人员数据,创建头像项
  228. listItems.forEach(item => {
  229. // 创建新的头像项
  230. const avatarItem = instantiate(this.avatarItemPrefab);
  231. this.avatarContainer.addChild(avatarItem);
  232. // 设置姓名
  233. const nameLabel = avatarItem.getComponentInChildren(Label);
  234. if (nameLabel && item.name) {
  235. nameLabel.string = item.name;
  236. }
  237. // 加载头像
  238. if (item.avatarPath) {
  239. // 移除可能存在的.png后缀
  240. const pathWithoutExtension = item.avatarPath.replace(/\.png$/, '');
  241. // 确保路径以/spriteFrame结尾
  242. const path = pathWithoutExtension.endsWith('/spriteFrame')
  243. ? pathWithoutExtension
  244. : `${pathWithoutExtension}/spriteFrame`;
  245. resources.load(path, SpriteFrame, (err, spriteFrame) => {
  246. if (err) {
  247. console.error(`加载头像失败: ${path}`, err);
  248. return;
  249. }
  250. // 查找头像显示组件
  251. const sprite = avatarItem.getComponentInChildren(Sprite);
  252. if (sprite) {
  253. sprite.spriteFrame = spriteFrame;
  254. }
  255. });
  256. }
  257. });
  258. // 更新布局
  259. const layout = this.avatarContainer.getComponent(Layout);
  260. if (layout) {
  261. this.scheduleOnce(() => {
  262. layout.updateLayout();
  263. }, 0.1);
  264. }
  265. }
  266. onDestroy() {
  267. // 移除按钮事件监听
  268. if (this.closeButton) {
  269. this.closeButton.node.off('click');
  270. }
  271. }
  272. }