MatchMaster/assets/cloud_rise_home/scripts/CloudRiseNumberToImage.ts
2026-09-16 18:19:34 +08:00

83 lines
4.1 KiB
TypeScript

const { ccclass, property, executeInEditMode } = cc._decorator;
/** Bundle-local adaptation of NumberToImage: explicit frames, outlined digits and a centered coin row. */
@ccclass
@executeInEditMode
export default class CloudRiseNumberToImage extends cc.Component {
@property([cc.SpriteFrame]) digits: cc.SpriteFrame[] = [];
@property(cc.Material) glyphMaterial: cc.Material = null;
@property(cc.Node) coin: cc.Node = null;
@property(cc.Node) content: cc.Node = null;
@property value = 10000;
@property digitHeight = 62;
@property widthScale = 0.83;
@property spacing = 1;
@property coinGap = 10;
@property sourceArt = false;
@property showCoin = true;
private rendered = "";
private renderedDigits: cc.SpriteFrame[] = [];
private renderedMaterial: cc.Material = null;
setValue(value: number): void {
this.value = Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
this.update();
}
onEnable(): void { this.rendered = ""; this.update(); }
update(): void {
if (!this.content || !this.coin || this.digits.length !== 10 || this.digits.some(frame => !frame)) return;
const text = String(Math.max(0, Math.floor(this.value)));
if (text.length > 5) {
this.rendered = "";
this.content.active = false;
this.coin.active = false;
return;
}
this.content.active = true;
const key = [text, this.digitHeight, this.widthScale, this.spacing, this.coinGap, this.node.width, this.sourceArt, this.showCoin].join(":");
if (key === this.rendered && this.renderedMaterial === this.glyphMaterial
&& this.digits.every((frame, i) => frame === this.renderedDigits[i])) return;
this.rendered = key;
this.renderedDigits = this.digits.slice();
this.renderedMaterial = this.glyphMaterial;
const outlined = !!this.glyphMaterial && !this.sourceArt;
const digitMaterial = outlined ? this.glyphMaterial : cc.Material.getBuiltinMaterial("2d-sprite");
const widths = text.split("").map(char => {
const rect = this.digits[Number(char)].getRect();
return this.digitHeight * rect.width / rect.height * this.widthScale;
});
const numberWidth = widths.reduce((sum, width) => sum + width, 0) + (text.length - 1) * this.spacing + 16;
this.coin.active = this.showCoin;
const coinWidth = this.showCoin ? this.coin.width + this.coinGap : 0;
const total = coinWidth + numberWidth;
const scale = Math.min(1, this.node.width / total);
this.coin.scale = scale; this.content.scale = scale;
this.coin.x = (-total / 2 + this.coin.width / 2) * scale;
this.content.x = (-total / 2 + coinWidth + 8) * scale;
let x = 0;
this.content.children.forEach((node, index) => {
node.active = index < text.length;
if (!node.active) return;
const frame = this.digits[Number(text[index])], width = widths[index];
const sprite = node.getComponent(cc.Sprite);
sprite.spriteFrame = frame;
sprite.sizeMode = cc.Sprite.SizeMode.CUSTOM;
const padding = outlined ? 8 : 0;
node.setContentSize(width + padding * 2, this.digitHeight + padding * 2);
node.color = cc.Color.WHITE;
node.setPosition(x + width / 2, 0);
// Source artwork must also replace a previously assigned outline material.
sprite.setMaterial(0, digitMaterial);
if (outlined) {
const material = sprite.getMaterial(0);
const rect = frame.getRect(), texture = frame.getTexture();
// The bundle atlas is not packable/rotated. Pass its bounds so outlines cannot sample adjacent digits.
material.setProperty("glyphRect", new cc.Vec4(rect.x / texture.width, rect.y / texture.height,
rect.width / texture.width, rect.height / texture.height));
material.setProperty("glyphSize", cc.v2(width, this.digitHeight));
}
x += width + this.spacing;
});
}
}