SKScene中SKAction生成节点时UIColor深浅模式适配异常
问题:SKAction生成的SpriteKit节点无法正确适配深色/浅色模式颜色
背景
在SwiftUI View中嵌入的SKScene里,通过SKAction循环生成音符节点。节点颜色使用Assets中定义的带深色模式、通用模式变体的Color资源,但通过SKAction生成的节点始终使用通用模式颜色,直接调用生成方法的节点则能正确适配当前ColorScheme。
核心代码片段
创建节点的关键逻辑
// spawnNote() 中的核心代码 let shape = SKShapeNode(path: path.cgPath) shape.fillColor = color shape.strokeColor = color // ... 其他样式设置 let spriteNode = SKSpriteNode(texture: shape)
颜色参数的赋值方式
UIColor(Color.ColorText)
循环生成节点的SKAction代码
spawnAction = SKAction.repeatForever( SKAction.sequence([ SKAction.wait(forDuration: delay), SKAction.run { [weak self] in guard let self else { return } self.spawnNote() }, SKAction.wait(forDuration: spawnInterval) ]) ) run(spawnAction, withKey: spawnActionKey)
问题现象
- 直接调用
spawnNote()生成的节点:能根据当前系统的ColorScheme自动切换颜色(浅色用通用变体,深色用深色变体)。 - 通过SKAction生成的节点:始终使用通用模式的颜色,不会随ColorScheme变化。
补充:最小复现代码
Assets中定义了Color.colorTest:
- 通用(浅色)模式:#74FBFD(浅蓝色)
- 深色模式:#74FB50(绿色)
完整复现代码:
import SwiftUI import SpriteKit class TestScene: SKScene { override func didMove(to view: SKView) { backgroundColor = UIColor(Color.colorBackground) createNode(color: UIColor(Color.colorTest)) let spawnAction = SKAction.repeat( SKAction.sequence([ SKAction.wait(forDuration: 2), SKAction.run { [weak self] in guard let self else { return } self.createNode(color: UIColor(Color.colorTest)) self.createNode(color: UIColor(Color.colorTest).resolvedColor(with: UITraitCollection(userInterfaceStyle: .dark)) ) self.createNode(color: UIColor(Color.colorTest).resolvedColor(with: UITraitCollection(userInterfaceStyle: .light)) ) } ]), count: 1 ) run(spawnAction) } @discardableResult public func createNode(color: UIColor) -> SKSpriteNode { let radius: CGFloat = 50 let halfRadius = radius let rangeX = frame.minX + halfRadius...frame.maxX - halfRadius let rangeY = frame.minY + halfRadius...frame.maxY - halfRadius let randomX = CGFloat.random(in: rangeX) let randomY = CGFloat.random(in: rangeY) let path = UIBezierPath() path.append(UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: radius, height: radius))) let shape = SKShapeNode(path: path.cgPath) shape.fillColor = color let texture = view?.texture(from: shape)! let node = SKSpriteNode(texture: texture) node.position = CGPoint(x: randomX, y: randomY) addChild(node) return node } } struct SceneTestView: View { @State var scene: TestScene = { let scene = TestScene() scene.scaleMode = .resizeFill return scene }() var body: some View { VStack { SpriteView(scene: scene) .border(Color.yellow) HStack { Button { scene.createNode(color: UIColor(Color.colorTest) .resolvedColor(with: UITraitCollection(userInterfaceStyle: .dark)) ) scene.createNode(color: UIColor(Color.colorTest) .resolvedColor(with: UITraitCollection(userInterfaceStyle: .light)) ) } label: { Text("Add Node") } .buttonStyle(.borderedProminent) Button { scene.removeAllChildren() } label: { Text("Clean") } .buttonStyle(.borderedProminent) } } } } #Preview { SceneTestView() }
现象说明:深色模式下,直接生成的节点是绿色(正确),SKAction和按钮生成的节点是浅蓝色(错误,通用变体)。
解决方案
核心原因
SwiftUI的Color依赖环境上下文,当在非SwiftUI环境(比如SKScene的闭包、SpriteKit的回调)中直接通过UIColor(Color.xxx)转换时,无法获取当前的ColorScheme环境,会默认使用通用模式的颜色变体。
具体修复步骤
1. 在SKScene中使用当前视图的TraitCollection解析颜色
在调用createNode时,通过view?.traitCollection获取当前界面的风格,手动解析颜色:
// 修改didMove中的调用 createNode(color: UIColor(Color.colorTest).resolvedColor(with: view.traitCollection)) // 修改SKAction闭包中的调用 self.createNode(color: UIColor(Color.colorTest).resolvedColor(with: self.view?.traitCollection ?? UITraitCollection()))
2. 在SwiftUI按钮回调中利用环境变量获取正确颜色
在SceneTestView中监听当前的ColorScheme,生成对应颜色后传递给SKScene:
struct SceneTestView: View { @State var scene: TestScene = { let scene = TestScene() scene.scaleMode = .resizeFill return scene }() // 监听当前ColorScheme @Environment(\.colorScheme) var colorScheme var body: some View { VStack { SpriteView(scene: scene) .border(Color.yellow) HStack { Button { // 根据当前ColorScheme生成对应颜色 let currentColor = UIColor(Color.colorTest).resolvedColor(with: UITraitCollection(userInterfaceStyle: colorScheme == .dark ? .dark : .light)) scene.createNode(color: currentColor) } label: { Text("Add Node") } .buttonStyle(.borderedProminent) Button { scene.removeAllChildren() } label: { Text("Clean") } .buttonStyle(.borderedProminent) } } } }
3. 适配系统ColorScheme动态切换(可选)
如果需要在系统切换深色/浅色模式时,自动更新已生成节点的颜色,可在TestScene中重写traitCollectionDidChange方法,重新生成节点或更新纹理:
class TestScene: SKScene { // 存储所有生成的节点 private var nodes: [SKSpriteNode] = [] override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) guard let traitCollection = view?.traitCollection else { return } let currentColor = UIColor(Color.colorTest).resolvedColor(with: traitCollection) // 重新生成每个节点的纹理 for node in nodes { let radius = node.size.width let path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: radius, height: radius)) let shape = SKShapeNode(path: path.cgPath) shape.fillColor = currentColor if let texture = view?.texture(from: shape) { node.texture = texture } } } // 修改createNode方法,把节点加入数组 @discardableResult public func createNode(color: UIColor) -> SKSpriteNode { let node = SKSpriteNode(/* ... 原有逻辑 ... */) nodes.append(node) addChild(node) return node } }
内容的提问来源于stack exchange,提问作者Mario
相关产品推荐
相关产品推荐

