You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpriteKit中优化多关卡秘豆UserDefaults存储方案的技术问询

嘿,这个问题我之前做SpriteKit关卡收集系统的时候也碰到过!随着关卡增加,那种重复定义变量、写一堆else if的方式确实会变得超级臃肿,完全懂你的痛苦。下面是我整理的几个更高效的实现思路,应该能帮你把代码精简不少:

方案一:用Codable结构体统一管理进度

这种方式把所有秘豆收集状态打包成一个对象,一次性存入UserDefaults,不用再为每个关卡单独设置键值对,而且逻辑封装性更强。

步骤1:定义进度结构体

先创建一个遵循Codable的结构体,封装所有收集状态和操作方法:

struct SecretBeanProgress: Codable {
    // 用字典存储「关卡号: 是否收集」的映射,支持非连续关卡号
    private var collectedLevels: [Int: Bool] = [:]
    
    // 自动计算已收集数量,不用手动维护总数(避免和实际状态不一致)
    var collectedCount: Int {
        collectedLevels.values.filter { $0 }.count
    }
    
    // 检查指定关卡是否已收集秘豆
    func hasCollectedBean(for level: Int) -> Bool {
        collectedLevels[level] ?? false
    }
    
    // 标记指定关卡的秘豆为已收集
    mutating func markBeanCollected(for level: Int) {
        collectedLevels[level] = true
    }
    
    // 重置所有收集进度
    mutating func reset() {
        collectedLevels.removeAll()
    }
}

步骤2:扩展UserDefaults实现存储

给UserDefaults加个扩展,方便读写这个结构体:

extension UserDefaults {
    private enum StorageKeys: String {
        case secretBeanProgress
    }
    
    // 自定义属性,直接读写SecretBeanProgress对象
    var secretBeanProgress: SecretBeanProgress {
        get {
            guard let data = data(forKey: StorageKeys.secretBeanProgress.rawValue) else {
                return SecretBeanProgress()
            }
            do {
                return try JSONDecoder().decode(SecretBeanProgress.self, from: data)
            } catch {
                print("解码秘豆进度失败: \(error)")
                return SecretBeanProgress()
            }
        }
        set {
            do {
                let data = try JSONEncoder().encode(newValue)
                set(data, forKey: StorageKeys.secretBeanProgress.rawValue)
            } catch {
                print("编码秘豆进度失败: \(error)")
            }
        }
    }
}

步骤3:在游戏场景中使用

现在不管哪个关卡,代码都变得异常简洁:

  • 加载关卡时移除已收集的秘豆:
    // 假设当前关卡号是levelNumber(比如1、2...,建议抽成常量或配置)
    let progress = UserDefaults.standard.secretBeanProgress
    if progress.hasCollectedBean(for: levelNumber) {
        // 用可选绑定避免强制解包崩溃
        if let secretBean = childNode(withName: "SecretBean") as? SKSpriteNode {
            secretBean.removeFromParent()
        }
    }
    
  • 收集秘豆时更新状态:
    if node.name == "SecretBean" {
        var progress = UserDefaults.standard.secretBeanProgress
        progress.markBeanCollected(for: levelNumber)
        UserDefaults.standard.secretBeanProgress = progress
        
        // 检查是否收集全所有秘豆(假设totalLevels是总关卡数)
        if progress.collectedCount == totalLevels {
            // 触发全收集奖励逻辑
        }
    }
    
  • 重置新游戏时清空进度:
    var progress = UserDefaults.standard.secretBeanProgress
    progress.reset()
    UserDefaults.standard.secretBeanProgress = progress
    

方案二:用数组简化存储(适合连续关卡)

如果你的关卡是连续递增的(1、2、3...),用数组会更简单,数组索引+1对应关卡号:

extension UserDefaults {
    private enum StorageKeys: String {
        case collectedBeanLevels
    }
    
    private var collectedBeanLevels: [Bool] {
        get { array(forKey: StorageKeys.collectedBeanLevels.rawValue) as? [Bool] ?? [] }
        set { set(newValue, forKey: StorageKeys.collectedBeanLevels.rawValue) }
    }
    
    func hasCollectedBean(for level: Int) -> Bool {
        guard level > 0, level <= collectedBeanLevels.count else {
            return false
        }
        return collectedBeanLevels[level - 1]
    }
    
    func markBeanCollected(for level: Int) {
        var levels = collectedBeanLevels
        // 确保数组长度足够容纳当前关卡
        while levels.count < level {
            levels.append(false)
        }
        levels[level - 1] = true
        collectedBeanLevels = levels
    }
    
    var collectedBeanCount: Int {
        collectedBeanLevels.filter { $0 }.count
    }
    
    func resetBeanProgress() {
        collectedBeanLevels = []
    }
}

使用方式和结构体方案类似,调用对应的方法即可,代码同样简洁。

额外优化建议

  • 把总关卡数totalLevels抽成全局常量或配置文件,避免硬编码;
  • 可以封装一个GameProgressManager单例类,把所有进度相关的逻辑都放在里面,各个场景直接调用单例方法,代码更模块化;
  • 避免强制解包childNode(withName:),用可选绑定更安全,防止节点不存在时崩溃。

这样改完之后,不管你后续加多少关卡,都只需要在对应的地方传入关卡号就行,再也不用复制粘贴那些重复的UserDefaults代码了!

内容的提问来源于stack exchange,提问作者genericguy25

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:03:18