首页 > 解决方案 > 在 SpriteKit 中的自定义类上使用 Codable 协议

问题描述

我正在使用 SpriteKit 构建一个应用程序,我想利用 Codable 协议将一个复杂的自定义类保存到 NSUserDefaults。当我这样做时,我收到以下错误:

Cannot automatically synthesize 'Decodable' because 'SKSpriteNode' does not conform to 'Decodable'

该类如下所示(至少具有属性的部分):

class TileNode: SKNode, Codable {

    // MARK: - Class Variables

    var height = 32
    var width  = 32
    var index  = 0
    var row    = 0
    var column = 0
    var tileType = ""

    // These cannot be global because each tile needs its own instance of them.
    var dark = SKSpriteNode()
    var light = SKSpriteNode()


    // MARK: - Init

    override init() {
        super.init()

    }

    init(index:Int, tex: String) {
        super.init()

        if tileCheck {
            print("TileNode: init: creating tile with texture: \(tex).")
        }

        self.isUserInteractionEnabled = false
        self.index = index
        self.zPosition = 10
        self.name = "Tile \(index)"
        self.tileType = tex

    }
}

所以,我的问题是,当您需要将 SKSpriteNode 之类的东西作为类变量时,如何在自定义对象上使用可编码协议?

标签: sprite-kitcodabledecodable

解决方案


很明显,这个问题是因为SKSpriteNode不符合Codable协议。因此,以下步骤使自定义类 LikeSKSpriteNode能够使用Codable协议很好地工作。

  1. 声明自定义编码键以定义要编码或解码的属性列表
  2. 实现required init(from:)方法转换DataSKSpriteNode
  3. 实现encode(to:)方法转换SKSpriteNodeData

这是示例:

class TileNode: SKNode, Codable {

    var height = 32.0
    var width  = 32.0
    var index  = 0
    var row    = 0
    var column = 0
    var tileType = ""

    // These cannot be global because each tile needs its own instance of them.
    var dark = SKSpriteNode()
    var light = SKSpriteNode()

    enum CodingKeys: String, CodingKey {
        case height
        case width
        case index
        case row
        case column
        case tileType
        case dark
        case light
    }

    init(index:Int, tex: String) {
        super.init()

        if tileCheck {
            print("TileNode: init: creating tile with texture: \(tex).")
        }

        self.isUserInteractionEnabled = false
        self.index = index
        self.zPosition = 10
        self.name = "Tile \(index)"
        self.tileType = tex
    }

    required init(from decoder: Decoder) throws {
        super.init()
        let values = try decoder.container(keyedBy: CodingKeys.self)

        height = try values.decode(Double.self, forKey: .height)
        width = try values.decode(Double.self, forKey: .width)
        index = try values.decode(Int.self, forKey: .index)
        row = try values.decode(Int.self, forKey: .row)
        column = try values.decode(Int.self, forKey: .column)
        tileType = try values.decode(String.self, forKey: .tileType)

        let darkData = try values.decode(Data.self, forKey: .dark)
        dark = NSKeyedUnarchiver.unarchiveObject(with: darkData) as? SKSpriteNode ?? SKSpriteNode()
        let lightData = try values.decode(Data.self, forKey: .light)
        light = NSKeyedUnarchiver.unarchiveObject(with: lightData) as? SKSpriteNode ?? SKSpriteNode()
    }

    required init?(coder aDecoder: NSCoder) {
        // subclass of SKNode must implement this method
        fatalError("init(coder:) has not been implemented")
    }

    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)

        let darkData = NSKeyedArchiver.archivedData(withRootObject: dark)
        try container.encode(darkData, forKey: .dark)
        let lightData = NSKeyedArchiver.archivedData(withRootObject: light)
        try container.encode(lightData, forKey: .light)
    }

}

用法

编码数据并将数据保存在 UserDefaults 中:

    let node = TileNode(index: 3, tex: "Text")
    node.height = 33.0
    let dark = SKSpriteNode()
    dark.anchorPoint = CGPoint(x: 12.3, y: 45.6)
    dark.color = UIColor.red
    node.dark = dark
    let encoder = JSONEncoder()

    do {
        let encoded = try encoder.encode(node)
        UserDefaults.standard.set(encoded, forKey: "Node")
    } catch {
        print(error.localizedDescription)
    }

从 UserDefaults 中检索数据并解码数据:

    let docoder = JSONDecoder()
    do {
        let nodeData = UserDefaults.standard.object(forKey: "Node") as! Data
        let node = try docoder.decode(TileNode.self, from: nodeData)
    } catch {
        print(error.localizedDescription)
    }

推荐阅读