SKLabelNode文本更新后SKNode未刷新,如何触发重绘?
我有一个SKNode,包含两个子节点:一个带图片的SKSpriteNode,以及位于图片上方、用于显示可用单位数量的SKLabelNode。
之前用户消耗单位后,我通过删除旧节点再添加新节点的方式更新计数器,虽然能正常工作但效率极低。现在我尝试获取现有节点直接更新文本,代码如下:
let newNode = createUnitCounter(type: type, nation: nation, count: countStr) if let existingNode = iterator.filter({ $0.name == newNode.name }).first?.children.filter({ $0 is SKLabelNode }).first as? SKLabelNode { existingNode.text = countStr } else { DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.addChild(newNode) } }
通过断点调试,控制台输入po existingNode.text显示值已经正确更新,但屏幕上始终看不到变化。其他重构后的节点都能正常刷新,只有这个节点不行。
如何触发节点重绘(比如类似existingNode.setNeedsDisplay()的方法)?
以下是创建节点的原始代码:
func createUnitCounter(type: UnitType, nation: Nation, count: String) -> SKNode { let node = SKNode() node.name = type.prefix + ElId.Nodes.counter let image = createCounterImage(mode: .offense, type: type, nation: nation) assignCounterPosition(to: image, of: type) node.addChild(image) let label = createCounterLabel(text: count, position: nil, type: type) label.verticalAlignmentMode = .bottom assignCounterPosition(to: label, of: type) node.addChild(label) return node } func createCounterLabel(text: String, position: CGPoint?, type: UnitType?) -> SKLabelNode { let node = SKLabelNode(fontNamed: Assets.font) node.text = text node.zPosition = Position.zUIElementH if type == selectedUnitType { node.fontColor = Colors.red } if let p = position { node.position = p } return node } func createCounterImage(mode: GameMode, type: UnitType = .infantry, nation: Nation = .usa) -> SKSpriteNode { ... }
SpriteKit中SKLabelNode更新text属性后,理论上会自动触发重绘,不需要手动调用setNeedsDisplay()这类方法。你的问题大概率不是重绘触发的问题,而是节点引用或层级的问题,以下是排查和解决步骤:
确认拿到的
existingNode是屏幕上显示的标签
更新文本后,打印existingNode.zPosition、existingNode.position以及existingNode.parent?.name,和新创建的标签节点对比,检查是否存在层级覆盖(比如zPosition过低被其他节点挡住)或位置偏移导致不可视。也可以临时给existingNode.fontColor设置亮绿色,验证节点是否在可视区域。强制主线程更新节点
SpriteKit的所有节点操作必须在主线程执行,你当前仅添加新节点时用了DispatchQueue.main.async,但更新现有节点的代码可能在非主线程执行,导致更新不生效。把更新文本的逻辑也移到主线程:if let existingNode = iterator.filter({ $0.name == newNode.name }).first?.children.filter({ $0 is SKLabelNode }).first as? SKLabelNode { DispatchQueue.main.async { existingNode.text = countStr // 可选:临时设置颜色验证更新 // existingNode.fontColor = .green } } else { DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.addChild(newNode) } }同步字体颜色设置
在createCounterLabel中,当type == selectedUnitType时会设置红色字体。如果更新文本时该条件发生变化,但未同步更新颜色,可能导致文本颜色与背景重叠,看起来像是未更新。更新文本时同步处理颜色:DispatchQueue.main.async { existingNode.text = countStr existingNode.fontColor = (type == selectedUnitType) ? Colors.red : .black // 替换为你的默认颜色 }简化节点查找逻辑
当前的节点查找方式容易因父节点匹配错误拿到无效标签。给标签节点单独设置唯一name,提升查找精准度:// 创建标签时添加唯一标识 func createCounterLabel(text: String, position: CGPoint?, type: UnitType?) -> SKLabelNode { let node = SKLabelNode(fontNamed: Assets.font) node.name = type != nil ? "counterLabel_\(type!.prefix)" : "counterLabel" // ... 其他原有代码 } // 查找时直接通过name定位 if let parentNode = iterator.filter({ $0.name == newNode.name }).first, let existingNode = parentNode.childNode(withName: "counterLabel_\(type.prefix)") as? SKLabelNode { DispatchQueue.main.async { existingNode.text = countStr } }
内容的提问来源于stack exchange,提问作者Mercutio

