Files
MatchMaster/assets/Script/ThreeWinsPanel.ts
T
2026-10-16 16:24:28 +08:00

162 lines
6.2 KiB
TypeScript

import NumberToImage from "./NumberToImage";
const { ccclass } = cc._decorator;
export type ThreeWinsProp = "hourglass" | "rocket";
export interface ThreeWinsSelection {
hourglass: boolean;
rocket: boolean;
}
export interface ThreeWinsPanelData {
level: number;
threeWinStreak: number;
tenWinStreak: number;
hourglassAmount: number;
rocketAmount: number;
infiniteHourglass?: boolean;
infiniteRocket?: boolean;
}
export interface ThreeWinsPanelCallbacks {
onProp: (prop: ThreeWinsProp, selected: boolean) => void;
onStart: (selection: ThreeWinsSelection) => void;
}
/** 只管理预制体显示和选择;库存检查、购买及开局由 SceneManager 处理。 */
@ccclass
export default class ThreeWinsPanel extends cc.Component {
private atlas: cc.SpriteAtlas = null;
private data: ThreeWinsPanelData = null;
private callbacks: ThreeWinsPanelCallbacks = null;
private selected: ThreeWinsSelection = { hourglass: false, rocket: false };
private bound = false;
private entryScale: cc.Vec2 = null;
init(atlas: cc.SpriteAtlas, data: ThreeWinsPanelData, callbacks: ThreeWinsPanelCallbacks) {
this.atlas = atlas;
this.callbacks = callbacks;
this.selected = { hourglass: false, rocket: false };
if (!this.bound) {
this.node.getChildByName("hourglass").on("click", this.onHourglass, this);
this.node.getChildByName("rocket").on("click", this.onRocket, this);
this.node.getChildByName("begin").on("click", this.onStart, this);
this.bound = true;
}
this.refresh(data);
this.entryScale = cc.v2(this.node.scaleX, this.node.scaleY);
this.node.setScale(0);
}
playEntry() {
if (!this.entryScale) return;
const scale = this.entryScale;
this.entryScale = null;
cc.tween(this.node)
.to(0.28, { scaleX: scale.x * 1.06, scaleY: scale.y * 1.06 }, { easing: "cubicOut" })
.to(0.10, { scaleX: scale.x * 0.97, scaleY: scale.y * 0.97 }, { easing: "sineInOut" })
.to(0.12, { scaleX: scale.x, scaleY: scale.y }, { easing: "sineOut" })
.start();
}
refresh(data: ThreeWinsPanelData) {
this.data = data;
const tier = Math.min(3, Math.max(0, Math.floor(data.threeWinStreak || 0)));
const tenWins = Math.min(10, Math.max(0, Math.floor(data.tenWinStreak || 0)));
const box = this.node.getChildByName("box");
for (let i = 0; i <= 3; i++) box.getChildByName("box" + i).active = i === tier;
const three = this.node.getChildByName("three");
three.getChildByName("progress").getComponent(cc.Sprite).spriteFrame = this.atlas.getSpriteFrame("progress_" + tier);
for (let i = 1; i <= 3; i++) three.getChildByName("progress" + i).active = i <= tier;
this.node.getChildByName("progress").getComponent(cc.Sprite).fillRange = tenWins / 10;
this.renderNumber(this.node.getChildByName("custom"), data.level, "custom_", 44);
this.renderNumber(this.node.getChildByName("rainbowNumber"), tenWins, "rainbow", 34, true);
this.renderProp("hourglass");
this.renderProp("rocket");
}
refreshInventory(data: ThreeWinsPanelData) {
const previous = this.data;
this.data = data;
if (!previous || previous.hourglassAmount !== data.hourglassAmount || previous.infiniteHourglass !== data.infiniteHourglass) {
this.renderProp("hourglass");
}
if (!previous || previous.rocketAmount !== data.rocketAmount || previous.infiniteRocket !== data.infiniteRocket) {
this.renderProp("rocket");
}
}
setSelected(prop: ThreeWinsProp, selected: boolean) {
this.selected[prop] = selected;
this.renderProp(prop);
}
private onHourglass() {
if (this.callbacks) this.callbacks.onProp("hourglass", !this.selected.hourglass);
}
private onRocket() {
if (this.callbacks) this.callbacks.onProp("rocket", !this.selected.rocket);
}
private onStart() {
if (this.callbacks) this.callbacks.onStart({ ...this.selected });
}
private renderProp(prop: ThreeWinsProp) {
const node = this.node.getChildByName(prop);
const count = prop === "hourglass" ? this.data.hourglassAmount : this.data.rocketAmount;
const infinite = prop === "hourglass" ? this.data.infiniteHourglass : this.data.infiniteRocket;
const available = count > 0 || !!infinite;
if (!available) this.selected[prop] = false;
const selected = this.selected[prop];
node.getChildByName("add").active = !available;
node.getChildByName("select").active = selected;
const remaining = node.getChildByName("remaining");
remaining.active = available && !selected;
const amount = remaining.getChildByName("number");
if (infinite) {
this.clearNumber(amount);
this.addCountSymbol(amount, "∞", 0);
return;
}
this.renderNumber(amount, count, "prop_", 30);
}
private renderNumber(target: cc.Node, value: number, prefix: string, spacing: number, right = false) {
this.clearNumber(target);
const number = Math.max(0, Math.floor(value || 0));
NumberToImage.numberToImageNodesPublic(this.atlas, number, spacing, 0, prefix, target, false);
const offset = right ? (target.childrenCount - 1) * spacing : (target.childrenCount - 1) * spacing / 2;
for (const digit of target.children) digit.x -= offset;
}
private clearNumber(target: cc.Node) {
for (const child of target.children.slice()) child.destroy();
target.removeAllChildren();
}
private addCountSymbol(target: cc.Node, text: string, x: number) {
const node = new cc.Node("symbol");
node.parent = target;
node.x = x;
const label = node.addComponent(cc.Label);
label.string = text;
label.fontSize = 36;
label.lineHeight = 44;
label.enableBold = true;
const outline = node.addComponent(cc.LabelOutline);
outline.color = cc.color(126, 44, 20);
outline.width = 2;
}
onDestroy() {
this.callbacks = null;
}
}