Хранить несколько переменных userdefaults в каком-либо массиве, классе или структуре в spritekit? - PullRequest
0 голосов
/ 29 мая 2018

В данный момент игровой процесс включает в себя несколько уровней, и на каждом уровне есть секретный предмет (боб), который игрок сможет найти только один раз за уровень для каждого файла сохранения.Идея состоит в том, что если игрок может найти каждый секретный бин на каждом уровне, как только он доберется до конца игры, он получит бонус.Я отслеживаю, когда игрок обнаружил или не нашел секретный предмет на каждом уровне по умолчанию, он работает, но моя реализация ужасна, и мне нужна помощь.

Проблема в том, что я не могу обернуть своюОбдумайте эффективный способ сделать это за уровень.Чтобы отслеживать все это на уровне, я делаю это:

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

Эта система работает как задумано, но я знаю, что она очень небрежная.Кроме того, чем больше уровней я получу, тем больше будет этих утверждений.Я знаю, что есть гораздо лучший способ сделать это, но я не уверен, что я ищу в этом отношении.Любой совет будет принята с благодарностью.

Ответы [ 3 ]

0 голосов
/ 29 мая 2018

В дополнение к умному предложению Tom E об использовании наборов и сохранении в 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")
}

Я добавил комментарии о том, что делает код.Я настоятельно рекомендую перед использованием этого кода прочитать больше о Codable из документации Apple.

0 голосов
/ 30 мая 2018

Пользовательские настройки по умолчанию предназначены для хранения пользовательских настроек.Если вам необходимо сохранить небольшие объемы данных, их можно хранить здесь, но если вы планируете использовать их для больших наборов данных, я бы порекомендовал вам поискать их в другом месте.Это связано с тем, что пользовательские настройки по умолчанию действуют как база данных, а их значения кэшируются, так что вы не будете часто читать и записывать в базу данных.Наличие больших объемов данных может сделать недействительным кеш, вызывая выборку системы из базы данных чаще, чем это необходимо.Я бы порекомендовал просто создать специальную структуру, которую вы используете для чтения и записи, и сохранить ее в каталоге документов.(Не забудьте отключить доступ к каталогу документов из внешних приложений, чтобы люди не могли манипулировать файлом сохранения. Я думаю, что сейчас Apple делает это по умолчанию)

Пример сохранения данных на диск без использования пользовательских настроек по умолчанию:

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()
0 голосов
/ 29 мая 2018

Можно подумать о сохранении номеров уровня компонента в 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)
}

Таким образом, вы избавляетесь от необходимости статически настраивать переменные в зависимости от количества уровней в вашей игре.

...