You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpriteKit中SKLabelNode切换等位数数字时来回跳动问题求解

SpriteKit右对齐数字宽度偏移问题解决方案

问题说明

在SpriteKit中使用SKLabelNode右对齐显示数字时,默认字体(如HelveticaNeue-UltraLight)的数字1宽度明显窄于其他数字,导致末位为1的数字出现整体向右偏移的问题。即使为NSAttributedString设置fixedAdvance字体属性,该问题仍然存在,但相同字体在AppKit中无此异常。

SpriteKit问题复现代码

class GameScene: SKScene {
    
    override func didMove(to view: SKView) {
        let label = SKLabelNode(text: "")
        view.scene!.addChild(label)
        label.horizontalAlignmentMode = .right
        var i = 11
        Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
            label.text = "\(i)"
            // 尝试过的无效方案:
            // let font = NSFont(descriptor: NSFontDescriptor(fontAttributes: [.name: "HelveticaNeue-UltraLight", .fixedAdvance: 20]), size: 30)!
            // let paragraphStyle = NSMutableParagraphStyle()
            // paragraphStyle.alignment = .right
            // label.attributedText = NSAttributedString(string: "\(i)", attributes: [.font: font, .foregroundColor: SKColor.labelColor, .paragraphStyle: paragraphStyle])
            i += 5
        }
    }
    
}

AppKit对比代码(无问题)

class ViewController: NSViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let font = NSFont(descriptor: NSFontDescriptor(fontAttributes: [.name: "HelveticaNeue-UltraLight"]), size: 30)!
        let textField = NSTextField(labelWithString: "")
        textField.font = font
        textField.alignment = .right
        textField.frame = CGRect(x: 100, y: 100, width: 100, height: 100)
        view.addSubview(textField)
        var i = 11
        Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
            textField.stringValue = "\(i)"
            i += 5
        }
    }

}

可行解决方案

1. 切换为等宽字体

使用等宽字体(如Menlo、Courier New、Consolas),这类字体的所有字符宽度一致,从根源避免宽度差异问题:

override func didMove(to view: SKView) {
    let label = SKLabelNode(text: "")
    label.fontName = "Menlo" // 替换为等宽字体
    label.fontSize = 30
    label.horizontalAlignmentMode = .right
    view.scene!.addChild(label)
    
    var i = 11
    Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
        label.text = "\(i)"
        i += 5
    }
}

2. 固定标签的最大宽度

计算预期显示的最大数字的宽度,将SKLabelNode的preferredMaxLayoutWidth设置为该值,确保右对齐时整体位置稳定:

override func didMove(to view: SKView) {
    let label = SKLabelNode(text: "")
    label.fontName = "HelveticaNeue-UltraLight"
    label.fontSize = 30
    label.horizontalAlignmentMode = .right
    view.scene!.addChild(label)
    
    // 假设最大显示数字为999,计算其宽度
    let maxText = "999"
    let font = NSFont(name: "HelveticaNeue-UltraLight", size: 30)!
    let maxWidth = maxText.size(withAttributes: [.font: font]).width
    label.preferredMaxLayoutWidth = maxWidth
    
    var i = 11
    Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
        label.text = "\(i)"
        i += 5
    }
}

3. 自定义固定宽度数字节点

如果必须使用非等宽字体,可以自定义SKNode,将每个数字作为独立子节点排列,强制每个数字占用相同宽度:

class FixedWidthNumberLabel: SKNode {
    private let digitWidth: CGFloat
    private let fontName: String
    private let fontSize: CGFloat
    
    init(digitWidth: CGFloat, fontName: String, fontSize: CGFloat) {
        self.digitWidth = digitWidth
        self.fontName = fontName
        self.fontSize = fontSize
        super.init()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setNumber(_ number: Int) {
        // 清除旧节点
        children.forEach { $0.removeFromParent() }
        
        let digits = String(number).map(String.init)
        var xOffset: CGFloat = 0
        
        // 从右往左排列数字,保证整体右对齐
        for digit in digits.reversed() {
            let digitLabel = SKLabelNode(text: digit)
            digitLabel.fontName = fontName
            digitLabel.fontSize = fontSize
            digitLabel.horizontalAlignmentMode = .center
            digitLabel.position = CGPoint(x: xOffset - digitWidth/2, y: 0)
            addChild(digitLabel)
            xOffset -= digitWidth
        }
    }
}

// 使用示例
override func didMove(to view: SKView) {
    let numberLabel = FixedWidthNumberLabel(
        digitWidth: 20, 
        fontName: "HelveticaNeue-UltraLight", 
        fontSize: 30
    )
    numberLabel.position = CGPoint(x: view.frame.width - 20, y: view.frame.midY) // 右对齐定位
    view.scene!.addChild(numberLabel)
    
    var i = 11
    Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
        numberLabel.setNumber(i)
        i += 5
    }
}

内容的提问来源于stack exchange,提问作者Nickkk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 04:47:22