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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 05:25:54