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
相关产品推荐
相关产品推荐

