315 lines
9.4 KiB
TypeScript
315 lines
9.4 KiB
TypeScript
import JungleAnimator from "./JungleAnimator";
|
||
import { createJungleMockConfig } from "./JungleConfig";
|
||
import JungleProgress from "./JungleProgress";
|
||
import JungleRewardService from "./JungleRewardService";
|
||
import {
|
||
JungleActivityConfig,
|
||
JungleRenderState,
|
||
JungleTierConfig,
|
||
JungleViewAssets,
|
||
} from "./JungleTypes";
|
||
import JungleView from "./JungleView";
|
||
|
||
const { ccclass, property } = cc._decorator;
|
||
|
||
/**
|
||
* Jungle Treasure 流程控制器。
|
||
* 只管理活动状态和模块协作;UI、动画、配置、存档与发奖均由独立模块负责。
|
||
*/
|
||
@ccclass
|
||
export default class JungleManager extends cc.Component {
|
||
@property(cc.SpriteFrame)
|
||
cardFrame: cc.SpriteFrame = null;
|
||
|
||
@property(cc.SpriteFrame)
|
||
trackFrame: cc.SpriteFrame = null;
|
||
|
||
@property(cc.SpriteFrame)
|
||
titleFrame: cc.SpriteFrame = null;
|
||
|
||
@property(cc.SpriteFrame)
|
||
buttonFrame: cc.SpriteFrame = null;
|
||
|
||
@property(cc.SpriteFrame)
|
||
lockFrame: cc.SpriteFrame = null;
|
||
|
||
@property(cc.SpriteFrame)
|
||
timerFrame: cc.SpriteFrame = null;
|
||
|
||
@property(cc.SpriteFrame)
|
||
closeFrame: cc.SpriteFrame = null;
|
||
|
||
@property(cc.SpriteFrame)
|
||
coinFrame: cc.SpriteFrame = null;
|
||
|
||
@property(cc.SpriteFrame)
|
||
freezeFrame: cc.SpriteFrame = null;
|
||
|
||
@property(cc.SpriteFrame)
|
||
hammerFrame: cc.SpriteFrame = null;
|
||
|
||
@property(cc.SpriteFrame)
|
||
magicWandFrame: cc.SpriteFrame = null;
|
||
|
||
@property(cc.SpriteFrame)
|
||
infiniteHealthFrame: cc.SpriteFrame = null;
|
||
|
||
@property({ tooltip: "服务器支付尚未接入时,点击付费档位自动模拟支付成功" })
|
||
useMockPayment: boolean = true;
|
||
|
||
@property({ tooltip: "只预览领取和轨道动画,不发道具、不支付、不保存进度" })
|
||
previewAnimationOnly: boolean = true;
|
||
|
||
private activityConfig: JungleActivityConfig = null;
|
||
private currentTierIndex: number = 0;
|
||
private cards: cc.Node[] = [];
|
||
private slotPositions: cc.Vec2[] = [];
|
||
private isMoving: boolean = false;
|
||
private isPurchasing: boolean = false;
|
||
|
||
private view: JungleView = null;
|
||
private animator: JungleAnimator = null;
|
||
private progress: JungleProgress = null;
|
||
private rewardService: JungleRewardService = null;
|
||
|
||
protected onLoad(): void {
|
||
this.progress = new JungleProgress();
|
||
this.rewardService = new JungleRewardService();
|
||
this.activityConfig = createJungleMockConfig();
|
||
this.currentTierIndex = this.progress.read(this.activityConfig, this.previewAnimationOnly);
|
||
|
||
this.view = new JungleView(
|
||
this.node,
|
||
this.getViewAssets(),
|
||
this,
|
||
this.closeActivity,
|
||
this.onCardClicked,
|
||
);
|
||
const refs = this.view.build();
|
||
this.cards = refs.cards;
|
||
this.slotPositions = refs.slotPositions;
|
||
this.animator = new JungleAnimator(this, refs.contentNode, this.cardFrame);
|
||
|
||
this.renderAllCards();
|
||
this.updateTimer();
|
||
this.schedule(this.updateTimer, 1);
|
||
}
|
||
|
||
protected onDestroy(): void {
|
||
this.unschedule(this.updateTimer);
|
||
}
|
||
|
||
/** 后续服务器配置到达后调用此方法即可替换模拟表。 */
|
||
public setServerConfig(config: JungleActivityConfig, claimedTierIndex?: number): void {
|
||
if (!config || !config.tiers || config.tiers.length === 0) {
|
||
cc.warn("[JungleTreasure] ignored empty server config");
|
||
return;
|
||
}
|
||
|
||
this.activityConfig = config;
|
||
this.currentTierIndex = typeof claimedTierIndex === "number"
|
||
? Math.max(0, Math.min(claimedTierIndex, config.tiers.length))
|
||
: this.progress.read(config, this.previewAnimationOnly);
|
||
this.renderAllCards();
|
||
this.updateTimer();
|
||
}
|
||
|
||
/** 正式支付层完成后回调。支付失败或取消不会移动轨道。 */
|
||
public completePurchase(success: boolean): void {
|
||
if (!this.isPurchasing) {
|
||
return;
|
||
}
|
||
this.isPurchasing = false;
|
||
if (success) {
|
||
this.completeCurrentTier();
|
||
} else {
|
||
this.renderAllCards();
|
||
}
|
||
}
|
||
|
||
public openActivity(): void {
|
||
this.node.active = true;
|
||
this.renderAllCards();
|
||
this.updateTimer();
|
||
}
|
||
|
||
public closeActivity(): void {
|
||
if (this.isMoving || this.isPurchasing) {
|
||
return;
|
||
}
|
||
this.node.emit("jungle-close");
|
||
this.node.active = false;
|
||
}
|
||
|
||
/** 仅用于当前模拟配置的联调。 */
|
||
public resetMockProgress(): void {
|
||
if (!this.activityConfig) {
|
||
return;
|
||
}
|
||
this.progress.reset(this.activityConfig);
|
||
this.currentTierIndex = 0;
|
||
this.renderAllCards();
|
||
}
|
||
|
||
public getCurrentTierIndex(): number {
|
||
return this.currentTierIndex;
|
||
}
|
||
|
||
private onCardClicked(buttonComponent: cc.Button): void {
|
||
if (this.isMoving || this.isPurchasing || !this.activityConfig) {
|
||
return;
|
||
}
|
||
|
||
const button = buttonComponent && buttonComponent.node;
|
||
if (!button) {
|
||
return;
|
||
}
|
||
const card = button.parent;
|
||
if (card !== this.cards[0]) {
|
||
this.animator.playLockedFeedback(card);
|
||
return;
|
||
}
|
||
|
||
const tier = this.activityConfig.tiers[this.currentTierIndex];
|
||
if (!tier || Date.now() >= this.activityConfig.endAt) {
|
||
return;
|
||
}
|
||
|
||
if (this.previewAnimationOnly) {
|
||
this.completeCurrentTier();
|
||
} else if (tier.price > 0) {
|
||
this.requestPurchase(tier);
|
||
} else {
|
||
this.completeCurrentTier();
|
||
}
|
||
}
|
||
|
||
private requestPurchase(tier: JungleTierConfig): void {
|
||
this.isPurchasing = true;
|
||
this.renderAllCards();
|
||
|
||
if (this.useMockPayment) {
|
||
this.scheduleOnce(() => this.completePurchase(true), 0.45);
|
||
return;
|
||
}
|
||
|
||
this.node.emit("jungle-purchase-request", {
|
||
tierId: tier.id,
|
||
price: tier.price,
|
||
productId: tier.productId || "",
|
||
rewards: tier.rewards,
|
||
});
|
||
}
|
||
|
||
private completeCurrentTier(): void {
|
||
const tier = this.activityConfig.tiers[this.currentTierIndex];
|
||
if (!tier || this.isMoving) {
|
||
return;
|
||
}
|
||
|
||
this.isMoving = true;
|
||
if (!this.previewAnimationOnly) {
|
||
this.rewardService.grant(this.node, tier.rewards);
|
||
}
|
||
|
||
const leavingCard = this.cards[0];
|
||
this.animator.playClaimEffect(
|
||
leavingCard,
|
||
() => this.animator.playCardExit(leavingCard, () => this.shiftCards()),
|
||
);
|
||
}
|
||
|
||
private shiftCards(): void {
|
||
const oldCards = this.cards.slice();
|
||
this.currentTierIndex++;
|
||
if (!this.previewAnimationOnly) {
|
||
this.progress.save(this.activityConfig, this.currentTierIndex);
|
||
}
|
||
|
||
const recycled = oldCards[0];
|
||
recycled.stopAllActions();
|
||
recycled.active = false;
|
||
recycled.scale = 1;
|
||
recycled.opacity = 0;
|
||
recycled.setAnchorPoint(0.5, 0.5);
|
||
|
||
this.animator.moveCardsWithOverlap(oldCards, this.slotPositions, 1, () => {
|
||
this.cards.splice(
|
||
0,
|
||
this.cards.length,
|
||
oldCards[1],
|
||
oldCards[2],
|
||
oldCards[3],
|
||
oldCards[4],
|
||
oldCards[5],
|
||
recycled,
|
||
);
|
||
this.showNewSixthCard(recycled);
|
||
});
|
||
}
|
||
|
||
private showNewSixthCard(card: cc.Node): void {
|
||
const sixthTierIndex = this.currentTierIndex + 5;
|
||
const targetCenter = this.slotPositions[5];
|
||
this.view.renderCard(card, this.activityConfig, this.getRenderState(), sixthTierIndex, 5);
|
||
|
||
if (sixthTierIndex >= this.activityConfig.tiers.length) {
|
||
card.setAnchorPoint(0.5, 0.5);
|
||
card.setPosition(targetCenter);
|
||
this.finishShift();
|
||
return;
|
||
}
|
||
|
||
this.animator.showNewCard(card, targetCenter, () => this.finishShift());
|
||
}
|
||
|
||
private finishShift(): void {
|
||
this.isMoving = false;
|
||
this.renderAllCards();
|
||
if (this.currentTierIndex >= this.activityConfig.tiers.length) {
|
||
this.node.emit("jungle-complete");
|
||
}
|
||
}
|
||
|
||
private renderAllCards(): void {
|
||
if (this.view) {
|
||
this.view.renderAllCards(this.activityConfig, this.getRenderState());
|
||
}
|
||
}
|
||
|
||
private updateTimer(): void {
|
||
if (!this.view || !this.activityConfig) {
|
||
return;
|
||
}
|
||
if (this.view.updateTimer(this.activityConfig) === 0) {
|
||
this.renderAllCards();
|
||
this.node.emit("jungle-expired");
|
||
}
|
||
}
|
||
|
||
private getRenderState(): JungleRenderState {
|
||
return {
|
||
currentTierIndex: this.currentTierIndex,
|
||
isMoving: this.isMoving,
|
||
isPurchasing: this.isPurchasing,
|
||
};
|
||
}
|
||
|
||
private getViewAssets(): JungleViewAssets {
|
||
return {
|
||
cardFrame: this.cardFrame,
|
||
trackFrame: this.trackFrame,
|
||
titleFrame: this.titleFrame,
|
||
buttonFrame: this.buttonFrame,
|
||
lockFrame: this.lockFrame,
|
||
timerFrame: this.timerFrame,
|
||
closeFrame: this.closeFrame,
|
||
coinFrame: this.coinFrame,
|
||
freezeFrame: this.freezeFrame,
|
||
hammerFrame: this.hammerFrame,
|
||
magicWandFrame: this.magicWandFrame,
|
||
infiniteHealthFrame: this.infiniteHealthFrame,
|
||
};
|
||
}
|
||
}
|