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; }); } }