SpriteKit自定义类含SKSpriteNode时如何使用Codable协议?
解决自定义Codable类包含SKSpriteNode属性的问题
这个问题很典型——SpriteKit的节点类(比如SKSpriteNode)并没有实现Codable协议,所以当你的自定义类包含这类属性时,Swift无法自动生成Decodable/Encodable的实现,就会抛出你看到的错误。
解决的核心思路是:不要直接编码SKSpriteNode实例,而是编码重建它所需要的关键信息,解码时再用这些信息重新创建节点。下面给你两种实用的方案,根据你的需求选择:
方案1:保存SpriteNode的动态状态(适合节点状态会变化的场景)
如果你的dark和light节点可能在运行时改变位置、隐藏状态或其他属性,需要完整保存这些状态,那么可以手动实现Codable的编码和解码逻辑,提取并存储节点的关键可序列化属性。
修改后的TileNode代码示例:
class TileNode: SKNode, Codable { // MARK: - Class Variables var height = 32 var width = 32 var index = 0 var row = 0 var column = 0 var tileType = "" // SpriteNode属性(无法自动编码) var dark = SKSpriteNode() var light = SKSpriteNode() // MARK: - Init override init() { super.init() } init(index:Int, tex: String) { super.init() self.isUserInteractionEnabled = false self.index = index self.zPosition = 10 self.name = "Tile \(index)" self.tileType = tex // 初始化dark和light节点(示例逻辑,可根据你的实际代码调整) dark = SKSpriteNode(imageNamed: "\(tex)_dark") dark.position = CGPoint(x: width/2, y: height/2) dark.zPosition = 1 addChild(dark) light = SKSpriteNode(imageNamed: "\(tex)_light") light.position = CGPoint(x: width/2, y: height/2) light.zPosition = 2 light.isHidden = true addChild(light) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - 手动实现Codable enum CodingKeys: String, CodingKey { // 基本属性 case height, width, index, row, column, tileType // dark节点的关键状态 case darkTextureName, darkPosX, darkPosY, darkZPos, darkIsHidden // light节点的关键状态 case lightTextureName, lightPosX, lightPosY, lightZPos, lightIsHidden } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) // 编码基本属性 try container.encode(height, forKey: .height) try container.encode(width, forKey: .width) try container.encode(index, forKey: .index) try container.encode(row, forKey: .row) try container.encode(column, forKey: .column) try container.encode(tileType, forKey: .tileType) // 编码dark节点的状态 if let textureName = dark.texture?.imageName { try container.encode(textureName, forKey: .darkTextureName) } try container.encode(dark.position.x, forKey: .darkPosX) try container.encode(dark.position.y, forKey: .darkPosY) try container.encode(dark.zPosition, forKey: .darkZPos) try container.encode(dark.isHidden, forKey: .darkIsHidden) // 编码light节点的状态 if let textureName = light.texture?.imageName { try container.encode(textureName, forKey: .lightTextureName) } try container.encode(light.position.x, forKey: .lightPosX) try container.encode(light.position.y, forKey: .lightPosY) try container.encode(light.zPosition, forKey: .lightZPos) try container.encode(light.isHidden, forKey: .lightIsHidden) } required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) // 解码基本属性 height = try container.decode(Int.self, forKey: .height) width = try container.decode(Int.self, forKey: .width) index = try container.decode(Int.self, forKey: .index) row = try container.decode(Int.self, forKey: .row) column = try container.decode(Int.self, forKey: .column) tileType = try container.decode(String.self, forKey: .tileType) // 初始化父类 super.init() // 重建dark节点 let darkTexName = try container.decode(String.self, forKey: .darkTextureName) dark = SKSpriteNode(imageNamed: darkTexName) dark.position = CGPoint( x: try container.decode(CGFloat.self, forKey: .darkPosX), y: try container.decode(CGFloat.self, forKey: .darkPosY) ) dark.zPosition = try container.decode(CGFloat.self, forKey: .darkZPos) dark.isHidden = try container.decode(Bool.self, forKey: .darkIsHidden) addChild(dark) // 重建light节点 let lightTexName = try container.decode(String.self, forKey: .lightTextureName) light = SKSpriteNode(imageNamed: lightTexName) light.position = CGPoint( x: try container.decode(CGFloat.self, forKey: .lightPosX), y: try container.decode(CGFloat.self, forKey: .lightPosY) ) light.zPosition = try container.decode(CGFloat.self, forKey: .lightZPos) light.isHidden = try container.decode(Bool.self, forKey: .lightIsHidden) addChild(light) // 恢复TileNode自身的基础配置 self.isUserInteractionEnabled = false self.zPosition = 10 self.name = "Tile \(index)" } }
方案2:仅保存生成SpriteNode的源数据(适合节点状态固定的场景)
如果dark和light节点的状态完全由tileType等基础属性决定,不会在运行时改变,那么可以简化逻辑:只编码基础属性,解码时通过初始化方法重新创建节点。
修改后的TileNode代码示例:
class TileNode: SKNode, Codable { // MARK: - Class Variables var height = 32 var width = 32 var index = 0 var row = 0 var column = 0 var tileType = "" // SpriteNode属性(无需编码) var dark = SKSpriteNode() var light = SKSpriteNode() // MARK: - Init override init() { super.init() setupTileNodes() } init(index:Int, tex: String) { super.init() self.isUserInteractionEnabled = false self.index = index self.zPosition = 10 self.name = "Tile \(index)" self.tileType = tex setupTileNodes() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } // 提取节点创建逻辑,方便复用 private func setupTileNodes() { dark = SKSpriteNode(imageNamed: "\(tileType)_dark") dark.position = CGPoint(x: width/2, y: height/2) dark.zPosition = 1 addChild(dark) light = SKSpriteNode(imageNamed: "\(tileType)_light") light.position = CGPoint(x: width/2, y: height/2) light.zPosition = 2 light.isHidden = true addChild(light) } // MARK: - Codable实现 enum CodingKeys: String, CodingKey { // 只编码基础属性,排除SpriteNode case height, width, index, row, column, tileType } required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) // 解码基础属性 height = try container.decode(Int.self, forKey: .height) width = try container.decode(Int.self, forKey: .width) index = try container.decode(Int.self, forKey: .index) row = try container.decode(Int.self, forKey: .row) column = try container.decode(Int.self, forKey: .column) tileType = try container.decode(String.self, forKey: .tileType) super.init() // 调用复用逻辑创建节点 self.isUserInteractionEnabled = false self.zPosition = 10 self.name = "Tile \(index)" setupTileNodes() } // encode方法可自动合成,也可手动实现(效果一致) func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(height, forKey: .height) try container.encode(width, forKey: .width) try container.encode(index, forKey: .index) try container.encode(row, forKey: .row) try container.encode(column, forKey: .column) try container.encode(tileType, forKey: .tileType) } }
关键注意事项
- 永远不要尝试直接编码
SKNode或其子类实例:这类对象包含大量渲染相关的内部状态和循环引用,无法被序列化。 - 编码的原则是只保存重建对象所需的最小数据:避免存储冗余信息,既减少存储体积,也降低解码出错的概率。
内容的提问来源于stack exchange,提问作者zeeple
相关产品推荐
相关产品推荐

