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
相关产品推荐
相关产品推荐

