PassCardManager.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. import { _decorator, Component, Node, Label, Sprite, Button, resources, SpriteFrame, tween, Vec3, UIOpacity, UITransform, Color } from 'cc';
  2. import { DataManager } from './DataManager';
  3. const { ccclass, property } = _decorator;
  4. /**
  5. * 通行证数据接口 (与DataManager中的PassInfo保持一致)
  6. */
  7. interface PassCardData {
  8. name: string; // 人物姓名
  9. room: string; // 房间号
  10. identityId: string; // 身份ID (注意:这里与之前的id字段不同)
  11. reason: string; // 原因
  12. avatarPath: string; // 头像路径
  13. hasStamp: boolean; // 是否有印章
  14. }
  15. @ccclass('PassCardManager')
  16. export class PassCardManager extends Component {
  17. @property({
  18. type: Node,
  19. tooltip: '通行证面板节点'
  20. })
  21. passCardPanel: Node = null;
  22. @property({
  23. type: Label,
  24. tooltip: '姓名标签'
  25. })
  26. nameLabel: Label = null;
  27. @property({
  28. type: Label,
  29. tooltip: '房间号标签'
  30. })
  31. roomLabel: Label = null;
  32. @property({
  33. type: Label,
  34. tooltip: '身份ID标签'
  35. })
  36. idLabel: Label = null;
  37. @property({
  38. type: Label,
  39. tooltip: '原因标签'
  40. })
  41. reasonLabel: Label = null;
  42. @property({
  43. type: Sprite,
  44. tooltip: '头像显示组件'
  45. })
  46. avatarSprite: Sprite = null;
  47. @property({
  48. type: Button,
  49. tooltip: '关闭按钮'
  50. })
  51. closeButton: Button = null;
  52. @property({
  53. tooltip: '通行证面板动画时长(秒)',
  54. range: [0.1, 2.0, 0.1]
  55. })
  56. animDuration: number = 0.5;
  57. @property({
  58. type: Node,
  59. tooltip: '卡片阴影节点(可选)'
  60. })
  61. cardShadow: Node = null;
  62. @property({
  63. type: DataManager,
  64. tooltip: '数据管理器引用'
  65. })
  66. dataManager: DataManager = null;
  67. // 当前通行证数据
  68. private currentPassData: PassCardData = null;
  69. // 用于动画控制的UIOpacity组件
  70. private passCardOpacity: UIOpacity = null;
  71. private shadowOpacity: UIOpacity = null;
  72. start() {
  73. // 初始隐藏通行证面板
  74. if (this.passCardPanel) {
  75. this.passCardPanel.active = false;
  76. // 确保面板有UIOpacity组件
  77. this.passCardOpacity = this.passCardPanel.getComponent(UIOpacity);
  78. if (!this.passCardOpacity) {
  79. this.passCardOpacity = this.passCardPanel.addComponent(UIOpacity);
  80. }
  81. // 如果有阴影节点,初始化它
  82. if (this.cardShadow) {
  83. this.shadowOpacity = this.cardShadow.getComponent(UIOpacity);
  84. if (!this.shadowOpacity) {
  85. this.shadowOpacity = this.cardShadow.addComponent(UIOpacity);
  86. }
  87. this.cardShadow.active = false;
  88. } else {
  89. // 自动创建阴影节点
  90. this.createShadowNode();
  91. }
  92. }
  93. // 注册关闭按钮点击事件
  94. if (this.closeButton) {
  95. // 先移除可能已存在的事件监听(防止重复注册)
  96. this.closeButton.node.off(Button.EventType.CLICK, this.onCloseButtonClick, this);
  97. // 重新注册点击事件
  98. this.closeButton.node.on(Button.EventType.CLICK, this.onCloseButtonClick, this);
  99. console.log('关闭按钮事件已注册');
  100. } else {
  101. console.error('关闭按钮未设置');
  102. }
  103. }
  104. /**
  105. * 创建卡片阴影节点
  106. */
  107. private createShadowNode(): void {
  108. if (!this.passCardPanel || this.cardShadow) return;
  109. // 创建阴影节点
  110. this.cardShadow = new Node('CardShadow');
  111. this.passCardPanel.parent.insertChild(this.cardShadow, this.passCardPanel.getSiblingIndex());
  112. // 设置阴影大小
  113. const cardTransform = this.passCardPanel.getComponent(UITransform);
  114. if (cardTransform) {
  115. const shadowTransform = this.cardShadow.addComponent(UITransform);
  116. shadowTransform.width = cardTransform.width * 0.9;
  117. shadowTransform.height = cardTransform.height * 0.9;
  118. }
  119. // 添加阴影图像
  120. const shadowSprite = this.cardShadow.addComponent(Sprite);
  121. shadowSprite.color = new Color(0, 0, 0, 180);
  122. // 添加透明度控制
  123. this.shadowOpacity = this.cardShadow.addComponent(UIOpacity);
  124. this.shadowOpacity.opacity = 0;
  125. this.cardShadow.active = false;
  126. }
  127. /**
  128. * 关闭按钮点击事件处理
  129. */
  130. private onCloseButtonClick(): void {
  131. console.log('关闭按钮被点击');
  132. this.hidePassCard();
  133. }
  134. /**
  135. * 显示通行证
  136. * @param passDataOrCharacterId 通行证数据或角色ID
  137. */
  138. public showPassCard(passDataOrCharacterId: PassCardData | number): void {
  139. console.log('PassCardManager.showPassCard 被调用:', passDataOrCharacterId);
  140. if (!this.passCardPanel) {
  141. console.error('通行证面板未设置');
  142. return;
  143. }
  144. // 根据参数类型处理数据
  145. if (typeof passDataOrCharacterId === 'number') {
  146. // 如果传入的是角色ID,则从DataManager获取通行证数据
  147. if (!this.dataManager) {
  148. console.error('数据管理器未设置,无法通过角色ID获取通行证数据');
  149. return;
  150. }
  151. const characterId = passDataOrCharacterId;
  152. const passInfo = this.dataManager.getNPCPassInfo(characterId);
  153. if (!passInfo) {
  154. console.error(`无法获取角色ID ${characterId} 的通行证数据`);
  155. return;
  156. }
  157. // 保存当前数据
  158. this.currentPassData = passInfo;
  159. } else {
  160. // 直接使用传入的通行证数据
  161. this.currentPassData = passDataOrCharacterId;
  162. }
  163. // 更新UI显示
  164. this.updatePassCardUI();
  165. // 显示面板并播放动画
  166. this.passCardPanel.active = true;
  167. this.playShowAnimation();
  168. console.log('通行证面板已显示');
  169. // 确保阴影也显示
  170. if (this.cardShadow) {
  171. this.cardShadow.active = true;
  172. }
  173. }
  174. /**
  175. * 播放显示动画
  176. */
  177. private playShowAnimation(): void {
  178. // 重置面板的初始状态
  179. this.passCardPanel.setScale(new Vec3(0.6, 0.6, 1));
  180. this.passCardPanel.setPosition(new Vec3(200, -100, 0)); // 从更远的地方传递过来
  181. this.passCardPanel.setRotationFromEuler(new Vec3(0, 0, -15)); // 初始旋转角度
  182. // 设置初始透明度
  183. if (this.passCardOpacity) {
  184. this.passCardOpacity.opacity = 0;
  185. }
  186. // 设置阴影初始状态
  187. if (this.cardShadow && this.shadowOpacity) {
  188. this.cardShadow.setPosition(new Vec3(210, -105, 0)); // 稍微偏移
  189. this.cardShadow.setScale(new Vec3(0.6, 0.6, 1));
  190. this.cardShadow.setRotationFromEuler(new Vec3(0, 0, -15));
  191. this.shadowOpacity.opacity = 0;
  192. }
  193. // 创建动画序列 - 模拟传递过来的感觉
  194. tween(this.passCardPanel)
  195. // 第一阶段:接近动作,带有轻微旋转
  196. .to(this.animDuration * 0.7, {
  197. scale: new Vec3(1.05, 1.05, 1),
  198. position: new Vec3(10, 5, 0),
  199. eulerAngles: new Vec3(0, 0, 5) // 轻微旋转到反方向
  200. }, {
  201. easing: 'quartOut'
  202. })
  203. // 第二阶段:最终位置,带有轻微的回弹效果
  204. .to(this.animDuration * 0.3, {
  205. scale: new Vec3(1, 1, 1),
  206. position: new Vec3(0, 0, 0),
  207. eulerAngles: new Vec3(0, 0, 0)
  208. }, {
  209. easing: 'backOut'
  210. })
  211. // 第三阶段:添加轻微的抖动效果,模拟卡片放在桌面上的感觉
  212. .call(() => {
  213. this.addShakeEffect();
  214. })
  215. .start();
  216. // 为阴影创建跟随动画
  217. if (this.cardShadow) {
  218. tween(this.cardShadow)
  219. // 第一阶段
  220. .to(this.animDuration * 0.7, {
  221. scale: new Vec3(1.05, 1.05, 1),
  222. position: new Vec3(15, 2, 0), // 稍微偏移以产生阴影效果
  223. eulerAngles: new Vec3(0, 0, 5)
  224. }, {
  225. easing: 'quartOut'
  226. })
  227. // 第二阶段
  228. .to(this.animDuration * 0.3, {
  229. scale: new Vec3(1, 1, 1),
  230. position: new Vec3(5, -3, 0), // 保持偏移
  231. eulerAngles: new Vec3(0, 0, 0)
  232. }, {
  233. easing: 'backOut'
  234. })
  235. .start();
  236. // 阴影透明度动画
  237. if (this.shadowOpacity) {
  238. tween(this.shadowOpacity)
  239. .delay(this.animDuration * 0.2)
  240. .to(this.animDuration * 0.5, { opacity: 100 }) // 半透明阴影
  241. .start();
  242. }
  243. }
  244. // 单独为透明度创建动画
  245. if (this.passCardOpacity) {
  246. tween(this.passCardOpacity)
  247. .to(this.animDuration * 0.5, { opacity: 255 })
  248. .start();
  249. }
  250. }
  251. /**
  252. * 添加轻微的抖动效果
  253. */
  254. private addShakeEffect(): void {
  255. // 创建一个轻微的抖动效果,模拟卡片被放置的震感
  256. const originalPos = new Vec3(0, 0, 0);
  257. const shakeAmount = 1.5; // 抖动幅度
  258. tween(this.passCardPanel)
  259. .to(0.05, { position: new Vec3(shakeAmount, 0, 0) })
  260. .to(0.05, { position: new Vec3(-shakeAmount, 0, 0) })
  261. .to(0.05, { position: new Vec3(shakeAmount * 0.5, 0, 0) })
  262. .to(0.05, { position: new Vec3(-shakeAmount * 0.5, 0, 0) })
  263. .to(0.05, { position: originalPos })
  264. .start();
  265. // 同时给阴影添加抖动效果
  266. if (this.cardShadow) {
  267. const shadowOriginalPos = new Vec3(5, -3, 0); // 阴影的原始位置
  268. tween(this.cardShadow)
  269. .to(0.05, { position: new Vec3(shadowOriginalPos.x + shakeAmount, shadowOriginalPos.y, 0) })
  270. .to(0.05, { position: new Vec3(shadowOriginalPos.x - shakeAmount, shadowOriginalPos.y, 0) })
  271. .to(0.05, { position: new Vec3(shadowOriginalPos.x + shakeAmount * 0.5, shadowOriginalPos.y, 0) })
  272. .to(0.05, { position: new Vec3(shadowOriginalPos.x - shakeAmount * 0.5, shadowOriginalPos.y, 0) })
  273. .to(0.05, { position: shadowOriginalPos })
  274. .start();
  275. }
  276. }
  277. /**
  278. * 隐藏通行证
  279. */
  280. public hidePassCard(): void {
  281. console.log('hidePassCard 被调用');
  282. if (this.passCardPanel) {
  283. // 播放隐藏动画
  284. this.playHideAnimation(() => {
  285. this.passCardPanel.active = false;
  286. console.log('通行证面板已隐藏');
  287. });
  288. } else {
  289. console.error('通行证面板未设置,无法隐藏');
  290. }
  291. }
  292. /**
  293. * 播放隐藏动画
  294. * @param callback 动画完成回调
  295. */
  296. private playHideAnimation(callback?: Function): void {
  297. // 创建移动和缩放动画 - 模拟被人收回的感觉
  298. tween(this.passCardPanel)
  299. // 首先上抬一点,像是被拿起
  300. .to(this.animDuration * 0.2, {
  301. position: new Vec3(0, 10, 0),
  302. eulerAngles: new Vec3(0, 0, -5)
  303. }, {
  304. easing: 'sineOut'
  305. })
  306. // 然后向远处移动,同时旋转
  307. .to(this.animDuration * 0.6, {
  308. scale: new Vec3(0.7, 0.7, 1),
  309. position: new Vec3(-180, 50, 0),
  310. eulerAngles: new Vec3(0, 0, -15)
  311. }, {
  312. easing: 'quartIn'
  313. })
  314. .call(() => {
  315. if (callback) callback();
  316. })
  317. .start();
  318. // 阴影的隐藏动画
  319. if (this.cardShadow) {
  320. tween(this.cardShadow)
  321. // 首先上抬一点,跟随卡片
  322. .to(this.animDuration * 0.2, {
  323. position: new Vec3(5, 7, 0), // 保持偏移
  324. eulerAngles: new Vec3(0, 0, -5)
  325. }, {
  326. easing: 'sineOut'
  327. })
  328. // 然后向远处移动
  329. .to(this.animDuration * 0.6, {
  330. scale: new Vec3(0.7, 0.7, 1),
  331. position: new Vec3(-175, 47, 0), // 保持偏移
  332. eulerAngles: new Vec3(0, 0, -15)
  333. }, {
  334. easing: 'quartIn'
  335. })
  336. .call(() => {
  337. if (this.cardShadow) {
  338. this.cardShadow.active = false;
  339. }
  340. })
  341. .start();
  342. // 阴影透明度动画
  343. if (this.shadowOpacity) {
  344. tween(this.shadowOpacity)
  345. .to(this.animDuration * 0.5, { opacity: 0 })
  346. .start();
  347. }
  348. }
  349. // 单独为透明度创建动画,稍微延迟开始
  350. if (this.passCardOpacity) {
  351. tween(this.passCardOpacity)
  352. .delay(this.animDuration * 0.1)
  353. .to(this.animDuration * 0.7, { opacity: 0 })
  354. .start();
  355. }
  356. }
  357. /**
  358. * 更新通行证UI
  359. */
  360. private updatePassCardUI(): void {
  361. console.log('更新通行证UI');
  362. if (!this.currentPassData) {
  363. console.error('没有通行证数据');
  364. return;
  365. }
  366. // 更新文本内容
  367. if (this.nameLabel) {
  368. this.nameLabel.string = this.currentPassData.name || '';
  369. console.log('设置姓名:', this.currentPassData.name);
  370. } else {
  371. console.error('姓名标签未设置');
  372. }
  373. if (this.roomLabel) {
  374. this.roomLabel.string = this.currentPassData.room || '';
  375. console.log('设置房间号:', this.currentPassData.room);
  376. } else {
  377. console.error('房间号标签未设置');
  378. }
  379. if (this.idLabel) {
  380. // 注意这里使用identityId而不是之前的id
  381. this.idLabel.string = this.currentPassData.identityId || '';
  382. console.log('设置ID:', this.currentPassData.identityId);
  383. } else {
  384. console.error('ID标签未设置');
  385. }
  386. if (this.reasonLabel) {
  387. this.reasonLabel.string = this.currentPassData.reason || '';
  388. console.log('设置原因:', this.currentPassData.reason);
  389. } else {
  390. console.error('原因标签未设置');
  391. }
  392. // 加载并显示头像(使用avatarPath)
  393. this.loadAvatar();
  394. }
  395. /**
  396. * 加载角色头像
  397. */
  398. private loadAvatar(): void {
  399. if (!this.avatarSprite) {
  400. console.error('头像Sprite组件未设置');
  401. return;
  402. }
  403. if (!this.currentPassData || !this.currentPassData.avatarPath) {
  404. console.error('无效的头像路径');
  405. return;
  406. }
  407. const avatarPath = this.currentPassData.avatarPath;
  408. console.log('尝试加载头像路径:', avatarPath);
  409. // 移除.png后缀
  410. const pathWithoutExtension = avatarPath.replace(/\.png$/, '');
  411. // 处理路径,确保以'/spriteFrame'结尾
  412. const finalPath = pathWithoutExtension.endsWith('/spriteFrame') ? pathWithoutExtension : `${pathWithoutExtension}/spriteFrame`;
  413. // 加载头像资源
  414. resources.load(finalPath, SpriteFrame, (err, spriteFrame) => {
  415. if (err) {
  416. console.error(`加载头像失败: ${finalPath}`, err);
  417. } else {
  418. // 设置头像
  419. this.avatarSprite.spriteFrame = spriteFrame;
  420. console.log('头像加载成功: ' + finalPath);
  421. }
  422. });
  423. }
  424. onDestroy() {
  425. // 清理事件监听
  426. if (this.closeButton) {
  427. this.closeButton.node.off(Button.EventType.CLICK, this.onCloseButtonClick, this);
  428. }
  429. }
  430. }