在spritekit中将多个userdefaults变量存储在某种数组,类或结构中?

时间:2018-05-29 14:27:26

标签: ios swift sprite-kit nsuserdefaults userdefaults

现在游戏玩法具有多个级别,并且每个级别都有一个秘密项目(一个bean),玩家每个级别只能找到一个保存文件。这个想法是,如果玩家可以找到每个级别的每个秘密豆,一旦他们到达游戏结束,他们就会获得奖励。我跟踪玩家是否通过用户默认值找到每个级别中的秘密项目时,它可以正常工作,但我的实现很难看,我需要帮助。

问题是我无法围绕每个级别执行此操作的有效方法。为了跟踪每个级别的所有这些,我一直在这样做:

//Class gameScene variable this tracks per level if the player found the bean, if so then it's set to true and the bean will not load in the level if the player revisits

 var foundBeanLevel1 = UserDefaults().bool(forKey: "FoundBeanLevel1")
 var foundBeanLevel2 = UserDefaults().bool(forKey: "FoundBeanLevel2")

这是在每个级别覆盖didMove(用X级替换1):

 if foundBeanLevel1{
    let secretBean: SKSpriteNode = childNode(withName: "SecretBean") as! SKSpriteNode
    secretBean.removeFromParent()
 }

_

//total count, if it's not the sum of the number of levels then the player didn't collect all beans
var secretBeanCount: Int = UserDefaults().integer(forKey: "SavedSecretBean") {
    didSet {
        //sets the increment to user defaults
        UserDefaults().set(secretBeanCount, forKey: "SavedSecretBean")
    }
}

//didContact
if node.name == SecretBean{
    secretBeanCount += 1
    if levelCheck == 1 {
        UserDefaults().set(true, forKey: "FoundBeanLevel1")
    } else if levelCheck == 2 {
        UserDefaults().set(true, forKey: "FoundBeanLevel2")
    }
}

然后如果他们开始新游戏:

UserDefaults().set(0, forKey: "SavedSecretBean")
UserDefaults().set(false, forKey: "FoundBeanLevel1")
UserDefaults().set(false, forKey: "FoundBeanLevel2")

这个系统按预期工作,但我知道它非常草率。如果语句变成,那么我获得的等级越大。我知道有更好的方法可以做到这一点,但我不确定我在这方面的目标是什么。任何建议都将不胜感激。

3 个答案:

答案 0 :(得分:1)

您可以考虑在Set

中存储bean级别号码
var beanLevels = Set<Int>()
beanLevels.insert(1)
beanLevels.insert(3)
beanLevels.insert(1)

一个集合确保一个元素只存储一次,所以在上面的例子中,该集合只包含1和3。

使用集合进行成员资格测试非常简单:

if beanLevels.contains(1) {
    // ...
}

此外,您不再需要SavedSecretBean。相反,您可以通过此测试集合是否包含任何bean:

if beanLevels.isEmpty {
    // ...
}

通过将集合转换为数组,您可以轻松地将其保存到UserDefaults并从那里恢复:

// Save
let beanLevelsArray = Array(beanLevels)
UserDefaults.standard.set(beanLevelsArray, forKey: "Bean Levels")

// Restore
if let beanLevelsArray = UserDefaults.standard.array(forKey: "beanLevels") as? [Int] {
    let beanLevels = Set(beanLevelsArray)
}

通过这种方式,您可以根据游戏中的关卡数量来摆脱静态配置变量的必要性。

答案 1 :(得分:1)

扩展Tom E关于使用Sets并保存到UserDefaults的聪明建议,您可能还希望将所有其他GameVariables保存在一个对象中并轻松使用它。如果你坚持使用UserDefaults我建议使用Codable,这就是实现的样子:

struct GameSceneVariables: Codable {
    let beanLevels: Set<Int>
    let savedSecretBean: Int
}

extension UserDefaults {

    /// Saves a Codable Object into UserDefaults
    ///
    /// - Parameters:
    ///   - object: The object to save that conforms to Codable
    ///   - key: The key to save and fetch
    /// - Returns: if the save was successful or failed
    func set<T:Codable>(_ object: T, key: String) -> Bool {
        guard let encodedValue = try? JSONEncoder().encode(object) else {
            return false
        }
        UserDefaults.standard.set(encodedValue, forKey: key)
        return true
    }

    /// Retrieves a Codable object saved in UserDefaults
    ///
    /// - Parameters:
    ///   - key: The key that was used to save the object into UserDefaults
    ///   - type: The type of the object so that you would not to actually cast the object so that the compiler
    ///           can know what Type of object to Return for the generic parameter T
    /// - Returns: returns the object if found, or nil if not found or if not able to decode the object
    func object<T:Codable>(forKey key: String, forObjectType type: T.Type) -> T? {
        guard let data = UserDefaults.standard.value(forKey: key) as? Data else { return nil }
        return try? JSONDecoder().decode(T.self, from: data)
    }

}

let variables = GameSceneVariables(beanLevels: [0, 1, 2], savedSecretBean: 0)
let userDefaults = UserDefaults.standard
let success = userDefaults.set(variables, key: "GameSceneVariables")
if success {
    let fetchedVariables = userDefaults.object(forKey: "GameSceneVariables", forObjectType: GameSceneVariables.self)
} else {
    // This can happen because the properties might not have been encoded properly
    // You will need to read up more on Codable to understand what might go wrong
    print("Saving was not a success")
}

我添加了关于代码功能的评论。我强烈建议您在使用此代码之前从Apple文档中了解有关Codable的更多信息。

答案 2 :(得分:1)

用户默认值旨在保存自定义用户首选项。当需要保存少量数据时,可以将其存储在此处,但如果您打算将其用于更大的数据集,我建议您查看其他地方。这是因为User Defaults充当数据库,并且其值被缓存,因此您不会多次读取和写入数据库。拥有大量数据可能会使缓存无效,导致系统从数据库中获取的次数超出了必要的范围。我建议只创建一个用于读写的特殊结构,并将其保存到文档目录中。 (请记住关闭外部应用程序对文档目录的访问权限,以便人们无法操作保存文件。我认为苹果现在默认这样做了)

将数据保存到磁盘而不依赖于用户默认值的示例:

struct MyGameData : Codable{
    enum CodingKeys: String,CodingKey{
        case beans = "beans"
    }

    var beans = [Int:Any]() //Int will reference our level, and AnyObject could be something else, like another dictionary or array if you want multiple beans per level
    static var beans : [Int:Any]{
        get{
            return instance.beans
        }
        set{
            instance.beans = newValue
        }
    }
    private static var instance = getData()
    private static var documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    private static func getData() -> MyGameData{
        let url = documentDirectory.appendingPathComponent("saveData.json", isDirectory: false)

        if !FileManager.default.fileExists(atPath: url.path) {
            return MyGameData()
        }

        if let data = FileManager.default.contents(atPath: url.path) {
            let decoder = JSONDecoder()
            do {
                let gameData = try decoder.decode(MyGameData.self, from: data)
                return gameData
            } catch {
                fatalError(error.localizedDescription)
            }
        } else {
            fatalError("No data at \(url.path)!")
        }
    }




    public static func save(){
        let url = documentDirectory.appendingPathComponent("saveData.json", isDirectory: false)

        let encoder = JSONEncoder()
        do {
            let data = try encoder.encode(instance)
            if FileManager.default.fileExists(atPath: url.path) {
                try FileManager.default.removeItem(at: url)
            }
            let _ = FileManager.default.createFile(atPath: url.path, contents: data, attributes: nil)
        } catch {
            fatalError(error.localizedDescription)
        }
    }

    func encode(to encoder: Encoder) throws
    {
        do {
            var container = encoder.container(keyedBy: CodingKeys.self)
            try container.encode(beans, forKey: .beans)
        } catch {
                fatalError(error.localizedDescription)
        }
    }

    init(from decoder: Decoder)
    {
        do {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            beans = try values.decode([Int:Any].self, forKey: .beans)
        } catch {
                fatalError(error.localizedDescription)
        }

    }
}

使用它非常简单:

MyGameData充当单身人士,所以当你第一次使用它时它将是init

let beans = MyGameData.beans

然后当您需要保存回文件时,只需调用

即可
MyGameData.save()