将多个 userdefaults 变量存储在某种数组中,class,或 spritekit 中的结构?
Store multiple userdefaults variables in some kind of array, class, or struct in 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(用 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")
这个系统按预期工作,但我知道它很草率。此外,我获得的级别越多,这些 else if 语句就会变得越大。我知道有更好的方法可以做到这一点,但我不确定在这方面我在寻找什么。任何建议将不胜感激。
您可以考虑将 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)
}
这样您就无需根据游戏中的关卡数量静态配置变量。
扩展 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 的更多信息。
用户默认值旨在保存自定义用户首选项。当需要保存少量数据时,存放在这里很好,但如果您打算将其用于更大的数据集,我建议您看看其他地方。这是因为 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 是一个单例,所以它会在您第一次使用它时被初始化
let beans = MyGameData.beans
然后需要存回文件的时候调用
MyGameData.save()
现在游戏玩法有多个级别,每个级别都有一个秘密物品(豆子),玩家在每个级别、每个保存文件中只能找到一次。这个想法是,如果玩家可以在每个级别找到每个秘密豆,一旦他们到达游戏结束,他们就会获得奖励。我通过用户默认值跟踪玩家何时在每个级别找到或没有找到秘密项目,它有效,但我的实现很丑陋,我需要帮助。
问题是我想不出一种有效的方法来按级别执行此操作。为了跟踪每个级别的所有这些,我一直在这样做:
//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")
这个系统按预期工作,但我知道它很草率。此外,我获得的级别越多,这些 else if 语句就会变得越大。我知道有更好的方法可以做到这一点,但我不确定在这方面我在寻找什么。任何建议将不胜感激。
您可以考虑将 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)
}
这样您就无需根据游戏中的关卡数量静态配置变量。
扩展 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 的更多信息。
用户默认值旨在保存自定义用户首选项。当需要保存少量数据时,存放在这里很好,但如果您打算将其用于更大的数据集,我建议您看看其他地方。这是因为 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 是一个单例,所以它会在您第一次使用它时被初始化
let beans = MyGameData.beans
然后需要存回文件的时候调用
MyGameData.save()