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

Swift中使用UserDefaults保存游戏分数排名的更佳方案咨询

更好的无服务器游戏分数排名存储方案(基于UserDefaults)

嘿,我之前做过好几个类似的无服务器小游戏,也用过多维数组存排名,确实踩过不少坑——比如索引搞混、扩展字段麻烦、代码可读性差这些。后来换成自定义Codable结构体+UserDefaults的方案,舒服太多了,分享给你:

为什么不推荐多维数组?

你的多维数组方案虽然能跑,但有几个明显的问题:

  • 可读性差:rankArray[0][0]到底是关卡还是分数?过俩月自己看代码都懵
  • 扩展性弱:以后要加玩家昵称、游戏模式这类字段,只能硬加数组维度,代码会越来越乱
  • 易出错:不小心把关卡和分数的索引写反,排查起来特别麻烦

推荐方案:自定义Codable结构体

Swift的结构体+Codable协议完美适配UserDefaults存储,既清晰又易维护。

1. 定义分数记录结构体

先把每个玩家的分数数据封装成结构体,属性名一目了然:

struct ScoreRecord: Codable {
    let level: Int       // 关卡数
    let score: Int       // 分数
    // 以后要加字段直接加就行,比如玩家昵称:let playerName: String
}

2. 给UserDefaults加编码解码扩展

为了方便存/取结构体数组,给UserDefaults写个简单的扩展,不用每次都写重复的JSON编码解码代码:

extension UserDefaults {
    // 编码存数据
    func encode<T: Codable>(_ value: T, forKey key: String) {
        let encoder = JSONEncoder()
        if let data = try? encoder.encode(value) {
            set(data, forKey: key)
        }
    }
    
    // 解码取数据
    func decode<T: Codable>(_ type: T.Type, forKey key: String) -> T? {
        guard let data = data(forKey: key) else { return nil }
        let decoder = JSONDecoder()
        return try? decoder.decode(type, from: data)
    }
}

3. 核心逻辑:添加新分数+排序+保存

每次玩家获得新分数时,按以下步骤处理:

// 1. 创建新的分数记录
let newPlayerRecord = ScoreRecord(level: 99, score: 9999)

// 2. 从UserDefaults取出已有的所有记录
var allRecords = UserDefaults.standard.decode([ScoreRecord].self, forKey: "gameHighScores") ?? []

// 3. (可选)如果只保留玩家的最高分数,可以先判断替换,而不是直接追加
// 比如:如果玩家之前有记录,只保留分数更高的那条(需要玩家标识的话可以给结构体加id)
// 这里先按你当前的需求,直接追加新记录

// 4. 排序:先按分数降序,分数相同则按关卡降序(和你的示例规则一致)
allRecords.sort {
    if $0.score != $1.score {
        return $0.score > $1.score
    } else {
        return $0.level > $1.level
    }
}

// 5. (可选)限制排名数量,比如只保留前100条,避免数据过大
if allRecords.count > 100 {
    allRecords = Array(allRecords.prefix(100))
}

// 6. 把排序后的记录存回UserDefaults
UserDefaults.standard.encode(allRecords, forKey: "gameHighScores")

4. 生成排名(处理并列情况)

排名不需要提前存在UserDefaults里,每次读取记录后动态生成就行,还能自动处理并列:

func getFormattedRank(at index: Int, from records: [ScoreRecord]) -> String {
    guard index < records.count else { return "" }
    
    let currentRecord = records[index]
    // 第一名直接返回1st
    if index == 0 {
        return "1st"
    }
    
    let previousRecord = records[index - 1]
    // 如果和前一名分数、关卡都相同,排名和前一名一致
    if currentRecord.score == previousRecord.score && currentRecord.level == previousRecord.level {
        return getFormattedRank(at: index - 1, from: records)
    } else {
        // 生成序数词:2nd、3rd、4th...
        let rankNumber = index + 1
        switch rankNumber {
        case 2: return "2nd"
        case 3: return "3rd"
        default: return "\(rankNumber)th"
        }
    }
}

// 使用示例:获取第3条记录的排名
let thirdRank = getFormattedRank(at: 2, from: allRecords) // 会返回"3rd"(如果和第二名分数相同则返回"2nd")

这个方案的优势

  • 可读性拉满:record.level、record.score一眼就能看懂,再也不用猜数组索引
  • 扩展性强:以后加任何字段,直接给结构体加属性就行,不用改底层存储逻辑
  • 不易出错:类型安全,编译器会帮你检查属性名,不会出现索引写反的低级错误
  • 兼容性好:Codable的JSON格式比纯数组更稳定,以后如果要做数据迁移也更方便

内容的提问来源于stack exchange,提问作者K.K.D

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 08:00:56