ShopController.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. import { _decorator, Component, Node, Label, Button, JsonAsset, Sprite, Color, SpriteFrame, director } from 'cc';
  2. import { SaveDataManager } from '../../LevelSystem/SaveDataManager';
  3. import EventBus, { GameEvents } from '../../Core/EventBus';
  4. import { TopBarController } from '../TopBarController';
  5. import { MoneyAni } from '../../Animations/MoneyAni';
  6. import { AdManager } from '../../Ads/AdManager';
  7. const { ccclass, property } = _decorator;
  8. interface ShopConfig {
  9. dailyRewards: {
  10. money: {
  11. rewards: number[];
  12. maxClaimsPerDay: number;
  13. };
  14. diamond: {
  15. rewards: number[];
  16. maxClaimsPerDay: number;
  17. };
  18. }
  19. }
  20. interface DailyRewardData {
  21. lastResetDate: string;
  22. moneyFreeCount: number;
  23. moneyAdCount: number;
  24. diamondsFreeCount: number;
  25. diamondsAdCount: number;
  26. // 每日免费机会是否已使用
  27. moneyFreeUsed: boolean;
  28. diamondFreeUsed: boolean;
  29. }
  30. @ccclass('ShopController')
  31. export class ShopController extends Component {
  32. @property(Node)
  33. moneyRewardNode: Node = null;
  34. @property(Node)
  35. diamondRewardNode: Node = null;
  36. @property(Button)
  37. moneyButton: Button = null;
  38. @property(Button)
  39. diamondButton: Button = null;
  40. @property(Label)
  41. moneyCountLabel: Label = null;
  42. @property(Label)
  43. diamondCountLabel: Label = null;
  44. @property(Label)
  45. moneyAmountLabel: Label = null;
  46. @property(Label)
  47. diamondAmountLabel: Label = null;
  48. @property(JsonAsset)
  49. shopConfigAsset: JsonAsset = null;
  50. @property(Node)
  51. billSpriteNode: Node = null; // Canvas/ShopUI/ScrollView/view/content/bill/Sprite/Sprite
  52. @property(Node)
  53. diamondSpriteNode: Node = null; // Canvas/ShopUI/ScrollView/view/content/diamond/Sprite/Sprite
  54. @property(Node)
  55. moneyIconNode: Node = null; // Canvas/ShopUI/ScrollView/view/content/bill/Sprite/次数/antb-02
  56. @property(Node)
  57. diamondIconNode: Node = null; // Canvas/ShopUI/ScrollView/view/content/diamond/Sprite/次数/antb-02
  58. private shopConfig: ShopConfig = null;
  59. private dailyRewardData: DailyRewardData = null;
  60. private saveDataManager: SaveDataManager = null;
  61. private readonly DAILY_REWARD_KEY = 'daily_reward_data';
  62. /**
  63. * 格式化数字显示,在数字之间添加空格
  64. * @param num 要格式化的数字
  65. * @returns 格式化后的字符串,如 "50" -> "5 0"
  66. */
  67. private formatNumberWithSpaces(num: number): string {
  68. return num.toString().split('').join(' ');
  69. }
  70. onLoad() {
  71. this.saveDataManager = SaveDataManager.getInstance();
  72. this.loadShopConfig();
  73. this.loadDailyRewardData();
  74. this.setupEventListeners();
  75. }
  76. start() {
  77. this.updateUI();
  78. }
  79. private loadShopConfig() {
  80. if (this.shopConfigAsset) {
  81. this.shopConfig = this.shopConfigAsset.json as ShopConfig;
  82. console.log('[ShopController] shop.json加载成功:', this.shopConfig);
  83. this.updateUI();
  84. } else {
  85. console.error('[ShopController] shopConfigAsset未设置,请在编辑器中拖拽shop.json文件到shopConfigAsset属性');
  86. }
  87. }
  88. private loadDailyRewardData() {
  89. const savedData = localStorage.getItem(this.DAILY_REWARD_KEY);
  90. const today = this.getCurrentDateString();
  91. if (savedData) {
  92. const data = JSON.parse(savedData) as DailyRewardData;
  93. // 兼容旧数据:如果存在旧的costFreeUsed字段,迁移到新字段
  94. if (data.hasOwnProperty('costFreeUsed')) {
  95. const oldCostFreeUsed = (data as any).costFreeUsed;
  96. data.moneyFreeUsed = oldCostFreeUsed || false;
  97. data.diamondFreeUsed = oldCostFreeUsed || false;
  98. delete (data as any).costFreeUsed;
  99. console.log('[ShopController] 迁移旧的costFreeUsed数据到新字段');
  100. }
  101. // 确保新字段存在,设置默认值
  102. if (data.moneyFreeUsed === undefined) {
  103. data.moneyFreeUsed = false; // 默认为false(未使用)
  104. console.log('[ShopController] 设置moneyFreeUsed默认值: false');
  105. }
  106. if (data.diamondFreeUsed === undefined) {
  107. data.diamondFreeUsed = false; // 默认为false(未使用)
  108. console.log('[ShopController] 设置diamondFreeUsed默认值: false');
  109. }
  110. // 检查是否需要重置(新的一天)
  111. if (data.lastResetDate !== today) {
  112. console.log('[ShopController] 检测到新的一天,重置每日奖励数据');
  113. this.resetDailyRewardData(today);
  114. } else {
  115. this.dailyRewardData = data;
  116. console.log('[ShopController] 加载已有的每日奖励数据');
  117. }
  118. } else {
  119. console.log('[ShopController] 首次运行,创建新的每日奖励数据');
  120. this.resetDailyRewardData(today);
  121. }
  122. console.log('[ShopController] 每日奖励数据:', this.dailyRewardData);
  123. }
  124. private resetDailyRewardData(date: string) {
  125. this.dailyRewardData = {
  126. lastResetDate: date,
  127. moneyFreeCount: 0,
  128. moneyAdCount: 0,
  129. diamondsFreeCount: 0,
  130. diamondsAdCount: 0,
  131. moneyFreeUsed: false,
  132. diamondFreeUsed: false
  133. };
  134. this.saveDailyRewardData();
  135. }
  136. private saveDailyRewardData() {
  137. localStorage.setItem(this.DAILY_REWARD_KEY, JSON.stringify(this.dailyRewardData));
  138. }
  139. private getCurrentDateString(): string {
  140. const now = new Date();
  141. const month = (now.getMonth() + 1).toString();
  142. const day = now.getDate().toString();
  143. const paddedMonth = month.length === 1 ? '0' + month : month;
  144. const paddedDay = day.length === 1 ? '0' + day : day;
  145. return `${now.getFullYear()}-${paddedMonth}-${paddedDay}`;
  146. }
  147. private setupEventListeners() {
  148. // 监听货币变化事件,更新UI
  149. EventBus.getInstance().on(GameEvents.CURRENCY_CHANGED, this.updateUI, this);
  150. }
  151. private updateUI() {
  152. if (!this.shopConfig || !this.dailyRewardData) return;
  153. // 更新钞票奖励UI
  154. this.updateMoneyRewardUI();
  155. // 更新钻石奖励UI
  156. this.updateDiamondRewardUI();
  157. }
  158. private updateMoneyRewardUI() {
  159. if (!this.shopConfig || !this.dailyRewardData) return;
  160. const config = this.shopConfig.dailyRewards.money;
  161. const freeCount = this.dailyRewardData.moneyFreeCount;
  162. const adCount = this.dailyRewardData.moneyAdCount;
  163. const totalCount = freeCount + adCount;
  164. const maxCount = config.maxClaimsPerDay;
  165. const freeUsed = this.dailyRewardData.moneyFreeUsed;
  166. // 计算当前次数索引(免费机会未使用时为0,否则为总次数)
  167. const currentIndex = freeUsed ? totalCount : 0;
  168. const currentReward = config.rewards[currentIndex] || config.rewards[config.rewards.length - 1];
  169. // 更新金额显示
  170. if (this.moneyAmountLabel) {
  171. this.moneyAmountLabel.string = this.formatNumberWithSpaces(currentReward);
  172. }
  173. // 更新按钮状态和显示
  174. if (this.moneyButton) {
  175. const canClaim = totalCount < maxCount;
  176. this.moneyButton.interactable = canClaim;
  177. // 更新按钮文本
  178. const buttonLabel = this.moneyButton.node.getChildByName('Label')?.getComponent(Label);
  179. if (buttonLabel) {
  180. if (!canClaim) {
  181. buttonLabel.string = "今日已达上限";
  182. } else if (!freeUsed) {
  183. buttonLabel.string = "免 费";
  184. } else {
  185. buttonLabel.string = "观看广告";
  186. }
  187. }
  188. // 更新次数显示和图标
  189. if (this.moneyCountLabel) {
  190. if (!freeUsed) {
  191. this.moneyCountLabel.string = "免 费";
  192. // 免费时隐藏图标
  193. if (this.moneyIconNode) {
  194. this.moneyIconNode.active = false;
  195. }
  196. } else {
  197. this.moneyCountLabel.string = `${totalCount}/${maxCount}`;
  198. // 观看广告时显示图标
  199. if (this.moneyIconNode) {
  200. this.moneyIconNode.active = true;
  201. }
  202. }
  203. }
  204. }
  205. }
  206. private updateDiamondRewardUI() {
  207. if (!this.shopConfig || !this.dailyRewardData) return;
  208. const config = this.shopConfig.dailyRewards.diamond;
  209. const freeCount = this.dailyRewardData.diamondsFreeCount;
  210. const adCount = this.dailyRewardData.diamondsAdCount;
  211. const totalCount = freeCount + adCount;
  212. const maxCount = config.maxClaimsPerDay;
  213. const freeUsed = this.dailyRewardData.diamondFreeUsed;
  214. // 计算当前次数索引(免费机会未使用时为0,否则为总次数)
  215. const currentIndex = freeUsed ? totalCount : 0;
  216. const currentReward = config.rewards[currentIndex] || config.rewards[config.rewards.length - 1];
  217. // 更新金额显示
  218. if (this.diamondAmountLabel) {
  219. this.diamondAmountLabel.string = this.formatNumberWithSpaces(currentReward);
  220. }
  221. // 更新按钮状态和显示
  222. if (this.diamondButton) {
  223. const canClaim = totalCount < maxCount;
  224. this.diamondButton.interactable = canClaim;
  225. // 更新按钮文本
  226. const buttonLabel = this.diamondButton.node.getChildByName('Label')?.getComponent(Label);
  227. if (buttonLabel) {
  228. if (!canClaim) {
  229. buttonLabel.string = "今日已达上限";
  230. } else if (!freeUsed) {
  231. buttonLabel.string = "免 费";
  232. } else {
  233. buttonLabel.string = "观看广告";
  234. }
  235. }
  236. // 更新次数显示和图标
  237. if (this.diamondCountLabel) {
  238. if (!freeUsed) {
  239. this.diamondCountLabel.string = "免 费";
  240. // 免费时隐藏图标
  241. if (this.diamondIconNode) {
  242. this.diamondIconNode.active = false;
  243. }
  244. } else {
  245. this.diamondCountLabel.string = `${totalCount}/${maxCount}`;
  246. // 观看广告时显示图标
  247. if (this.diamondIconNode) {
  248. this.diamondIconNode.active = true;
  249. }
  250. }
  251. }
  252. }
  253. }
  254. // 钞票奖励按钮点击事件
  255. public onMoneyRewardClick() {
  256. if (!this.shopConfig || !this.dailyRewardData) return;
  257. const config = this.shopConfig.dailyRewards.money;
  258. const freeCount = this.dailyRewardData.moneyFreeCount;
  259. const adCount = this.dailyRewardData.moneyAdCount;
  260. const totalCount = freeCount + adCount;
  261. const maxCount = config.maxClaimsPerDay;
  262. const freeUsed = this.dailyRewardData.moneyFreeUsed;
  263. if (totalCount >= maxCount) {
  264. console.log('[ShopController] 钞票奖励已达每日上限');
  265. return;
  266. }
  267. // 如果免费机会未使用,直接发放奖励
  268. if (!freeUsed) {
  269. console.log('[ShopController] 使用免费机会获取钞票奖励');
  270. this.dailyRewardData.moneyFreeUsed = true;
  271. // 免费奖励不计入freeCount,只标记为已使用
  272. this.saveDailyRewardData();
  273. this.updateUI();
  274. // 播放奖励动画并添加货币
  275. const amount = config.rewards[0] || config.rewards[config.rewards.length - 1];
  276. this.saveDataManager.addMoney(amount, 'daily_free_reward');
  277. this.playMoneyRewardAnimation(amount, () => {
  278. console.log(`[ShopController] 钞票免费奖励动画播放完成: ${amount}`);
  279. });
  280. } else {
  281. // 看广告增加次数
  282. this.showAdForMoneyReward();
  283. }
  284. }
  285. // 钻石奖励按钮点击事件
  286. public onDiamondRewardClick() {
  287. if (!this.shopConfig || !this.dailyRewardData) return;
  288. const config = this.shopConfig.dailyRewards.diamond;
  289. const freeCount = this.dailyRewardData.diamondsFreeCount;
  290. const adCount = this.dailyRewardData.diamondsAdCount;
  291. const totalCount = freeCount + adCount;
  292. const maxCount = config.maxClaimsPerDay;
  293. const freeUsed = this.dailyRewardData.diamondFreeUsed;
  294. if (totalCount >= maxCount) {
  295. console.log('[ShopController] 钻石奖励已达每日上限');
  296. return;
  297. }
  298. // 如果免费机会未使用,直接发放奖励
  299. if (!freeUsed) {
  300. console.log('[ShopController] 使用免费机会获取钻石奖励');
  301. this.dailyRewardData.diamondFreeUsed = true;
  302. // 免费奖励不计入freeCount,只标记为已使用
  303. this.saveDailyRewardData();
  304. this.updateUI();
  305. // 播放奖励动画并添加货币
  306. const amount = config.rewards[0] || config.rewards[config.rewards.length - 1];
  307. this.saveDataManager.addDiamonds(amount, 'daily_free_reward');
  308. this.playDiamondRewardAnimation(amount, () => {
  309. console.log(`[ShopController] 钻石免费奖励动画播放完成: ${amount}`);
  310. });
  311. } else {
  312. // 看广告增加次数
  313. this.showAdForDiamondReward();
  314. }
  315. }
  316. private claimMoneyReward(isFromAd: boolean) {
  317. const config = this.shopConfig.dailyRewards.money;
  318. // 更新领取次数(先更新数据)
  319. if (isFromAd) {
  320. this.dailyRewardData.moneyAdCount++;
  321. } else {
  322. this.dailyRewardData.moneyFreeCount++;
  323. }
  324. // 计算当前次数索引和对应奖励
  325. const totalCount = this.dailyRewardData.moneyFreeCount + this.dailyRewardData.moneyAdCount;
  326. const currentIndex = totalCount - 1; // 减1因为已经增加了次数
  327. const amount = config.rewards[currentIndex] || config.rewards[config.rewards.length - 1];
  328. this.saveDailyRewardData();
  329. this.updateUI();
  330. console.log(`[ShopController] 开始播放钞票奖励动画: ${amount}, 来源: ${isFromAd ? '广告' : '免费'}`);
  331. // 添加货币到账户并播放奖励动画
  332. this.saveDataManager.addMoney(amount, isFromAd ? 'daily_ad_reward' : 'daily_free_reward');
  333. this.playMoneyRewardAnimation(amount, () => {
  334. console.log(`[ShopController] 钞票奖励动画播放完成: ${amount}`);
  335. });
  336. }
  337. private claimDiamondReward(isFromAd: boolean) {
  338. const config = this.shopConfig.dailyRewards.diamond;
  339. // 更新领取次数(先更新数据)
  340. if (isFromAd) {
  341. this.dailyRewardData.diamondsAdCount++;
  342. } else {
  343. this.dailyRewardData.diamondsFreeCount++;
  344. }
  345. // 计算当前次数索引和对应奖励
  346. const totalCount = this.dailyRewardData.diamondsFreeCount + this.dailyRewardData.diamondsAdCount;
  347. const currentIndex = totalCount - 1; // 减1因为已经增加了次数
  348. const amount = config.rewards[currentIndex] || config.rewards[config.rewards.length - 1];
  349. this.saveDailyRewardData();
  350. this.updateUI();
  351. console.log(`[ShopController] 开始播放钻石奖励动画: ${amount}, 来源: ${isFromAd ? '广告' : '免费'}`);
  352. // 播放奖励动画,动画完成后会自动添加钻石
  353. this.playDiamondRewardAnimation(amount, () => {
  354. console.log(`[ShopController] 钻石奖励动画播放完成: ${amount}`);
  355. });
  356. }
  357. private showAdForMoneyReward() {
  358. console.log('[ShopController] 显示钞票奖励广告');
  359. // 显示激励视频广告
  360. AdManager.getInstance().showRewardedVideoAd(
  361. () => {
  362. // 广告观看完成,发放钞票奖励
  363. console.log('[ShopController] 广告观看完成,发放钞票奖励');
  364. this.claimMoneyReward(true);
  365. },
  366. (error) => {
  367. console.error('[ShopController] 钞票奖励广告显示失败:', error);
  368. // 广告失败时不给予奖励
  369. }
  370. );
  371. }
  372. private showAdForDiamondReward() {
  373. console.log('[ShopController] 显示钻石奖励广告');
  374. // 显示激励视频广告
  375. AdManager.getInstance().showRewardedVideoAd(
  376. () => {
  377. // 广告观看完成,发放钻石奖励
  378. console.log('[ShopController] 广告观看完成,发放钻石奖励');
  379. this.claimDiamondReward(true);
  380. },
  381. (error) => {
  382. console.error('[ShopController] 钻石奖励广告显示失败:', error);
  383. // 广告失败时不给予奖励
  384. }
  385. );
  386. }
  387. // showRewardEffect方法已被MoneyAni.playReward替代,不再需要
  388. // Cost按钮相关方法已移除,功能已整合到主按钮中
  389. // 从配置JSON更新钞票数值显示
  390. private updateMoneyAmountFromConfig() {
  391. if (this.moneyAmountLabel && this.shopConfig) {
  392. // 从配置中读取下一次的钞票数值
  393. const nextMoneyAmount = this.shopConfig.dailyRewards.money.rewards[0] || 100;
  394. this.moneyAmountLabel.string = this.formatNumberWithSpaces(nextMoneyAmount);
  395. console.log('[ShopController] 从配置更新钞票数值:', nextMoneyAmount);
  396. }
  397. }
  398. // 从配置JSON更新钻石数值显示
  399. private updateDiamondAmountFromConfig() {
  400. if (this.diamondAmountLabel && this.shopConfig) {
  401. // 从配置中读取下一次的钻石数值
  402. const nextDiamondAmount = this.shopConfig.dailyRewards.diamond.rewards[0] || 10;
  403. this.diamondAmountLabel.string = this.formatNumberWithSpaces(nextDiamondAmount);
  404. console.log('[ShopController] 从配置更新钻石数值:', nextDiamondAmount);
  405. }
  406. }
  407. /**
  408. * 播放钞票奖励动画
  409. */
  410. private playMoneyRewardAnimation(amount: number, onComplete?: () => void) {
  411. // 查找场景中的MoneyAni组件
  412. const scene = director.getScene();
  413. if (!scene) {
  414. console.error('[ShopController] 无法获取当前场景');
  415. if (onComplete) onComplete();
  416. return;
  417. }
  418. const findMoneyAni = (node: Node): MoneyAni | null => {
  419. const moneyAni = node.getComponent(MoneyAni);
  420. if (moneyAni) return moneyAni;
  421. for (const child of node.children) {
  422. const result = findMoneyAni(child);
  423. if (result) return result;
  424. }
  425. return null;
  426. };
  427. const moneyAni = findMoneyAni(scene);
  428. if (!moneyAni) {
  429. console.error('[ShopController] 场景中未找到MoneyAni组件');
  430. if (onComplete) onComplete();
  431. return;
  432. }
  433. // 临时设置钞票起始位置
  434. if (this.billSpriteNode) {
  435. moneyAni.coinStartNode = this.billSpriteNode;
  436. }
  437. // 播放钞票动画
  438. moneyAni.playRewardAnimation(amount, 0, onComplete);
  439. }
  440. /**
  441. * 播放钻石奖励动画
  442. */
  443. private playDiamondRewardAnimation(amount: number, onComplete?: () => void) {
  444. // 查找场景中的MoneyAni组件
  445. const scene = director.getScene();
  446. if (!scene) {
  447. console.error('[ShopController] 无法获取当前场景');
  448. if (onComplete) onComplete();
  449. return;
  450. }
  451. const findMoneyAni = (node: Node): MoneyAni | null => {
  452. const moneyAni = node.getComponent(MoneyAni);
  453. if (moneyAni) return moneyAni;
  454. for (const child of node.children) {
  455. const result = findMoneyAni(child);
  456. if (result) return result;
  457. }
  458. return null;
  459. };
  460. const moneyAni = findMoneyAni(scene);
  461. if (!moneyAni) {
  462. console.error('[ShopController] 场景中未找到MoneyAni组件');
  463. if (onComplete) onComplete();
  464. return;
  465. }
  466. // 临时设置钻石起始位置
  467. if (this.diamondSpriteNode) {
  468. moneyAni.diamondStartNode = this.diamondSpriteNode;
  469. }
  470. // 播放钻石动画
  471. moneyAni.playRewardAnimation(0, amount, onComplete);
  472. }
  473. onDestroy() {
  474. // 移除事件监听
  475. EventBus.getInstance().off(GameEvents.CURRENCY_CHANGED, this.updateUI, this);
  476. }
  477. // 调试方法:重置每日奖励数据
  478. public resetDailyRewards() {
  479. const today = this.getCurrentDateString();
  480. this.resetDailyRewardData(today);
  481. this.updateUI();
  482. console.log('[ShopController] 每日奖励数据已重置');
  483. }
  484. // 获取当前每日奖励状态(用于调试)
  485. public getDailyRewardStatus() {
  486. return {
  487. config: this.shopConfig,
  488. data: this.dailyRewardData,
  489. today: this.getCurrentDateString()
  490. };
  491. }
  492. }