ShopController.ts 21 KB

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