SpriteKit场景初始化:Codable替代NSCoding的调用逻辑问题
解决SKScene从NSCoding转Codable时的初始化问题
你混淆了SKScene的两种初始化逻辑:init?(coder:)是用来从.sks场景文件或Storyboard加载场景实例的,而Codable的init(from:)是用来解码你自己序列化的存档数据的——这俩完全不是一回事,所以直接用场景加载方法只会触发init?(coder:),不会自动调用init(from:)。
给你两种可行的解决方案:
方案1:拆分场景加载与存档恢复(推荐)
先通过常规方式加载场景实例,再用Codable解码存档数据来填充场景的状态变量,这样既保留SKScene的可视化编辑能力,又能实现存档恢复。
步骤:
- 定义专门的存档状态结构体,分离场景实例和状态数据:
struct WorldSceneSaveState: Codable { // 父类共享变量 let sharedVar: Int // WorldScene专属变量 let worldSpecificVar: String // 其他需要存档的状态,比如节点位置、游戏进度等 let playerPosition: CGPoint }
- 在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. 从.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
相关产品推荐
相关产品推荐

