RosterManager.ts 11 KB

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