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

SpriteKit场景初始化:Codable替代NSCoding的调用逻辑问题

解决SKScene从NSCoding转Codable时的初始化问题

你混淆了SKScene的两种初始化逻辑:init?(coder:)是用来从.sks场景文件或Storyboard加载场景实例的,而Codable的init(from:)是用来解码你自己序列化的存档数据的——这俩完全不是一回事,所以直接用场景加载方法只会触发init?(coder:),不会自动调用init(from:)。

给你两种可行的解决方案:

方案1:拆分场景加载与存档恢复(推荐)

先通过常规方式加载场景实例,再用Codable解码存档数据来填充场景的状态变量,这样既保留SKScene的可视化编辑能力,又能实现存档恢复。

步骤:

  1. 定义专门的存档状态结构体,分离场景实例和状态数据:
struct WorldSceneSaveState: Codable {
    // 父类共享变量
    let sharedVar: Int
    // WorldScene专属变量
    let worldSpecificVar: String
    // 其他需要存档的状态,比如节点位置、游戏进度等
    let playerPosition: CGPoint
}
  1. 在WorldScene中添加恢复存档的方法:
class WorldScene: GameScene {
    var worldSpecificVar: String = ""
    var playerNode: SKNode!

    // 从.sks文件加载场景时调用的初始化
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        // 这里可以初始化场景的基础元素,比如获取playerNode
        playerNode = childNode(withName: "player")
    }

    // 恢复存档状态
    func restoreFromSaveState(_ state: WorldSceneSaveState) {
        self.sharedVar = state.sharedVar
        self.worldSpecificVar = state.worldSpecificVar
        self.playerNode.position = state.playerPosition
        // 恢复其他状态
    }
}
  1. 加载场景并恢复存档的流程:
// 1. 从.sks文件加载场景实例
guard let worldScene = SKScene(fileNamed: "WorldScene") as? WorldScene else {
    fatalError("Failed to load WorldScene")
}

// 2. 读取本地存档数据并解码
if let saveData = try? Data(contentsOf: saveFileURL) {
    let decoder = JSONDecoder()
    let saveState = try decoder.decode(WorldSceneSaveState.self, from: saveData)
    // 3. 恢复状态到场景实例
    worldScene.restoreFromSaveState(saveState)
}

// 4. 展示场景
view.presentScene(worldScene)

方案2:完全用Codable初始化场景实例(不依赖.sks文件)

如果不想用.sks文件,想直接从存档数据创建场景实例,需要手动处理SKScene的父类初始化(因为SKScene必须通过init(size:)或init(coder:)初始化)。

代码示例:

class GameScene: SKScene, Codable {
    var sharedVar: Int = 0

    enum CodingKeys: String, CodingKey {
        case sharedVar, size
    }

    // Codable初始化
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        sharedVar = try container.decode(Int.self, forKey: .sharedVar)
        let size = try container.decode(CGSize.self, forKey: .size)
        // 必须调用SKScene的指定初始化器
        super.init(size: size)
        // 初始化父类的基础元素
    }

    // 兼容NSCoding的初始化(如果还需要从.sks加载)
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        sharedVar = aDecoder.decodeInteger(forKey: CodingKeys.sharedVar.rawValue)
    }

    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(sharedVar, forKey: .sharedVar)
        try container.encode(size, forKey: .size)
    }
}

class WorldScene: GameScene, Codable {
    var worldSpecificVar: String = ""

    enum CodingKeys: String, CodingKey {
        case worldSpecificVar
    }

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        worldSpecificVar = try container.decode(String.self, forKey: .worldSpecificVar)
        // 先解码子类属性,再调用父类的Codable初始化
        try super.init(from: decoder)
        // 初始化WorldScene的专属元素
        setupWorldNodes()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        worldSpecificVar = aDecoder.decodeObject(forKey: CodingKeys.worldSpecificVar.rawValue) as? String ?? ""
    }

    override func encode(to encoder: Encoder) throws {
        try super.encode(to: encoder)
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(worldSpecificVar, forKey: .worldSpecificVar)
    }

    private func setupWorldNodes() {
        // 手动创建场景节点,比如player、地形等
    }
}

使用方式:

// 从存档数据解码出WorldScene实例
if let saveData = try? Data(contentsOf: saveFileURL) {
    let decoder = JSONDecoder()
    let worldScene = try decoder.decode(WorldScene.self, from: saveData)
    view.presentScene(worldScene)
}

关键提醒

  • 不要试图让SKScene的文件加载流程自动触发init(from:),这违背了SKScene的设计逻辑——init?(coder:)是针对NSCoding体系的场景资源加载,而Codable是用于你自定义的数据序列化。
  • 如果父类GameScene有需要存档的变量,必须让父类也支持Codable,或者在子类的编码解码中手动处理父类属性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 18:55:07