import { SevenDayActivityInfo, SevenDayReward, SevenDayRewardItem } from "./SevenDayGiftApi"; import SevenDayGiftRuntime from "./SevenDayGiftRuntime"; import ActivityPopupAnimator, { ActivityPopupAnimationType } from "../Script/ActivityPopupAnimator"; const { ccclass, property } = cc._decorator; const TOTAL_DAYS = 7; type ClaimHandler = (callback: (success: boolean) => void) => void; type CloseHandler = () => void; @ccclass export default class SevenDayGift extends cc.Component { @property(cc.SpriteFrame) cardBlue: cc.SpriteFrame = null; @property(cc.SpriteFrame) cardYellow: cc.SpriteFrame = null; @property(cc.SpriteFrame) cardSpecial: cc.SpriteFrame = null; @property(cc.SpriteFrame) claimedOverlay: cc.SpriteFrame = null; @property(cc.SpriteFrame) claimedBadge: cc.SpriteFrame = null; @property(cc.SpriteFrame) catArt: cc.SpriteFrame = null; @property(cc.SpriteFrame) catLocked: cc.SpriteFrame = null; @property(cc.SpriteFrame) coinIcon: cc.SpriteFrame = null; @property(cc.SpriteFrame) hammerIcon: cc.SpriteFrame = null; @property(cc.SpriteFrame) freezeIcon: cc.SpriteFrame = null; @property(cc.SpriteFrame) magicWandIcon: cc.SpriteFrame = null; @property(cc.SpriteFrame) infiniteHealthIcon: cc.SpriteFrame = null; @property({ type: cc.Enum(ActivityPopupAnimationType), displayName: "弹窗动画", tooltip: "活动页面打开和关闭时使用的动画" }) popupAnimation: ActivityPopupAnimationType = ActivityPopupAnimationType.Q弹弹出; private info: SevenDayActivityInfo = null; private cards: cc.Node[] = []; private claimHandler: ClaimHandler = null; private claiming = false; private panel: cc.Node = null; private mask: cc.Node = null; private closeNode: cc.Node = null; private closeEnabled = true; private closeHandler: CloseHandler = null; private runtime: SevenDayGiftRuntime = null; onLoad() { if (!this.cacheView()) { this.node.active = false; return; } this.bindEvents(); this.runtime = new SevenDayGiftRuntime(this); } onDestroy() { if (this.runtime) this.runtime.destroy(); this.runtime = null; } public initializeRuntime(info: SevenDayActivityInfo, closeHandler: CloseHandler) { if (this.runtime) this.runtime.initialize(info, closeHandler); } public updateRuntimeInfo(info: SevenDayActivityInfo) { if (this.runtime) this.runtime.updateInfo(info); } public setData(info: SevenDayActivityInfo, claimHandler: ClaimHandler) { this.info = info; this.claimHandler = claimHandler; this.refreshView(); } public setCloseHandler(closeHandler: CloseHandler) { this.closeHandler = closeHandler; } public show(autoClaim: boolean = false) { if (!this.info) return; const shouldAutoClaim = autoClaim && !!this.info.canClaim && !this.info.todayClaimed; this.setCloseEnabled(!shouldAutoClaim); this.node.active = true; this.refreshView(); this.node.opacity = 255; this.node.scale = 1; ActivityPopupAnimator.open(this.panel, this.mask, this.popupAnimation, () => { if (shouldAutoClaim) this.requestCurrentReward(); }); } public close(event?: cc.Event) { if (event) event.stopPropagation(); if (!this.closeEnabled || this.claiming) return; ActivityPopupAnimator.close(this.panel, this.mask, this.popupAnimation, () => { this.node.active = false; if (this.closeHandler) this.closeHandler(); }); } private cacheView(): boolean { this.panel = this.node.getChildByName("panel"); this.mask = this.node.getChildByName("mask"); this.closeNode = this.panel && this.panel.getChildByName("close"); const cards = this.panel && this.panel.getChildByName("cards"); const message = this.panel && this.panel.getChildByName("message"); if (!this.panel || !this.mask || !this.closeNode || !cards) { cc.error("[SevenDayGift] prefab structure is incomplete"); return false; } if (message) message.active = false; for (let day = 1; day <= TOTAL_DAYS; day++) { const card = cards.getChildByName("giftDay" + day); if (!card) { cc.error("[SevenDayGift] missing card for day " + day); return false; } this.cards.push(card); } return true; } private bindEvents() { this.closeNode.on(cc.Node.EventType.TOUCH_END, this.close, this); this.mask.on(cc.Node.EventType.TOUCH_END, this.close, this); this.cards.forEach((card: cc.Node, index: number) => { card.on(cc.Node.EventType.TOUCH_END, (event: cc.Event) => { event.stopPropagation(); this.onCardClicked(index + 1); }, this); }); } private refreshView() { if (!this.info || this.cards.length !== TOTAL_DAYS) return; for (let day = 1; day <= TOTAL_DAYS; day++) { this.refreshCard(this.cards[day - 1], day, this.findReward(day)); } } private refreshCard(card: cc.Node, day: number, reward: SevenDayReward) { const claimed = !!(reward && reward.claimed); const available = !!this.info.canClaim && day === Number(this.info.nextRewardDay); const special = day === TOTAL_DAYS; const cardFrame = claimed && !special ? this.claimedOverlay : special ? this.cardSpecial : available ? this.cardYellow : this.cardBlue; this.setSprite(card, cardFrame); // White is the neutral tint: keep the supplied art's original colors. card.color = cc.Color.WHITE; card.opacity = 255; const dayLabel = card.getChildByName("dayLabel").getComponent(cc.Label); dayLabel.string = day === TOTAL_DAYS ? "第7天" : "第" + day + "天"; dayLabel.fontSize = 32; dayLabel.lineHeight = 38; dayLabel.node.color = cc.Color.WHITE; const dayOutline = dayLabel.node.getComponent(cc.LabelOutline); if (dayOutline) { dayOutline.color = special ? cc.color(238, 91, 126) : available ? cc.color(242, 151, 0) : cc.color(0, 170, 182); } const items = reward && Array.isArray(reward.items) ? reward.items : []; this.refreshRewardItems(card, items, special, available || claimed); let badge = card.getChildByName("claimedBadge"); if (!badge) { badge = new cc.Node("claimedBadge"); badge.addComponent(cc.Sprite); card.addChild(badge); badge.zIndex = 10; } badge.setContentSize(118, 99); badge.setPosition(0, 0); badge.active = claimed; this.setSprite(badge, this.claimedBadge); card.stopAllActions(); card.scale = 1; if (available) { cc.tween(card).repeatForever( cc.tween().to(0.65, { scale: 1.04 }).to(0.65, { scale: 1 }) ).start(); } } private onCardClicked(day: number) { const reward = this.findReward(day); if (reward && reward.claimed) { return; } if (!this.info.canClaim || day !== Number(this.info.nextRewardDay)) { return; } if (this.claiming || !this.claimHandler) return; this.requestCurrentReward(); } private requestCurrentReward() { if (this.claiming || !this.claimHandler || !this.info || !this.info.canClaim) return; this.claiming = true; this.claimHandler((success: boolean) => { this.claiming = false; this.setCloseEnabled(true); if (!success) cc.warn("[SevenDayGift] 自动领取未完成,可点击当天奖励重试"); }); } private setCloseEnabled(enabled: boolean) { this.closeEnabled = enabled; if (this.closeNode) this.closeNode.active = enabled; } private findReward(day: number): SevenDayReward { const rewards = Array.isArray(this.info.rewards) ? this.info.rewards : []; for (let i = 0; i < rewards.length; i++) { if (Number(rewards[i].day) === day) return rewards[i]; } return null; } private refreshRewardItems(card: cc.Node, items: SevenDayRewardItem[], special: boolean, unlocked: boolean) { const originalIcon = card.getChildByName("rewardIcon"); if (originalIcon) originalIcon.active = false; let container = card.getChildByName("rewardItems"); if (!container) { container = new cc.Node("rewardItems"); container.zIndex = 2; card.addChild(container); } container.children.slice().forEach(child => { child.removeFromParent(); child.destroy(); }); container.active = items.length > 0; if (!items.length) return; const count = items.length; const availableWidth = special ? 220 : 184; // 多奖励保持与单奖励相同尺寸,通过横向重叠收进卡片内部。 const iconSize = special ? 128 : 104; const step = count === 1 ? 0 : Math.min(iconSize * 0.76, (availableWidth - iconSize) / (count - 1)); const totalWidth = iconSize + step * (count - 1); const startX = -totalWidth / 2 + iconSize / 2; const centerY = special ? 8 : 0; container.setContentSize(availableWidth, special ? 210 : 130); container.setPosition(0, special ? -8 : -4); items.forEach((item, index) => { const itemNode = new cc.Node("rewardItem" + index); itemNode.setContentSize(iconSize, iconSize); itemNode.setPosition(startX + step * index, centerY); itemNode.zIndex = index; container.addChild(itemNode); const sprite = itemNode.addComponent(cc.Sprite); sprite.sizeMode = cc.Sprite.SizeMode.CUSTOM; sprite.spriteFrame = this.getRewardIcon(item, unlocked); }); } private getRewardIcon(item: SevenDayRewardItem, unlocked: boolean): cc.SpriteFrame { switch (item.type) { case "coin": return this.coinIcon; case "hammer": return this.hammerIcon; case "freeze": return this.freezeIcon; case "magic_wand": return this.magicWandIcon; case "infinite_health": return this.infiniteHealthIcon; case "cat_skin": return unlocked ? this.catArt : this.catLocked; default: return this.coinIcon; } } private setSprite(node: cc.Node, frame: cc.SpriteFrame) { if (!node || !frame) return; const sprite = node.getComponent(cc.Sprite); if (sprite) sprite.spriteFrame = frame; } }