在 spritekit 中的某种数组、类或结构中存储多个用户默认值变量?



现在游戏玩法具有多个关卡,每个关卡都有一个秘密物品(豆子(,玩家每个关卡只能找到一次,每个保存文件。这个想法是,如果玩家可以在每个级别找到每个秘密豆子,一旦他们到达游戏结束,他们就会获得奖金。我通过用户默认值跟踪玩家何时在每个关卡中找到或没有找到秘密物品,它可以工作,但我的实现很丑陋,我需要帮助。

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

//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(将1替换为X级别(:

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")

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

扩展 Tom E 关于使用 Set 并保存到 UserDefaults 的明智建议,您可能还希望将所有其他游戏变量保存在一个对象中并轻松使用它。如果您坚持使用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的更多信息。

您可以考虑将 Bean 级别数字存储在Set中:

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)
}

通过这种方式,您就不必根据游戏中的关卡数量静态配置变量。

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

在不采用用户默认值的情况下将数据保存到磁盘的示例:

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充当单例,因此在您第一次使用它时,它将是初始化

的。
let beans = MyGameData.beans

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

MyGameData.save()

最新更新