SwiftUI中如何让随机关卡视图实例固定且可复现
问题描述
我正在开发一款游戏,主视图可启动不同关卡,所有关卡复用同一个视图。该视图初始化时会生成随机内容,但要求每个关卡的随机内容在整个游戏过程中保持不变:每个关卡仅生成一次,通过初始化参数区分,即便关卡内子视图刷新,关卡本身也不会重新生成。目前遇到两个问题:
- 不设置随机种子时,部分场景下关卡会在游戏中途更新;
- 设置种子后,所有子视图又会使用相同的种子初始化。
请问如何实现:每个关卡视图实例仅创建一次,关闭重开也保持不变,同时具备可复现的随机性(通过种子初始化)?
解决方案
核心思路
将关卡的随机生成逻辑从视图层剥离到全局数据缓存层,通过单例缓存存储已生成的关卡数据,确保同一关卡(对应唯一种子)的随机内容只生成一次;同时为每个关卡分配唯一种子,保证不同关卡的随机内容独立且可复现。
具体实现步骤
1. 新增全局关卡数据缓存类
创建单例缓存,存储已生成的关卡数据,避免重复生成:
import Foundation // 全局关卡数据缓存,确保同一种子的关卡只生成一次 class LevelDataCache { static let shared = LevelDataCache() private var cache: [Int: (original: [Int], masked: [Int?])] = [:] private init() {} // 单例模式,禁止外部实例化 func getSequence(for seed: Int) -> (original: [Int], masked: [Int?]) { // 优先返回缓存数据 if let cachedData = cache[seed] { return cachedData } // 无缓存则生成新数据并存入缓存 let sequenceGenerator = NumberSequence(seed: seed) let newSequence = sequenceGenerator.generateSequence() cache[seed] = newSequence return newSequence } }
2. 修改关卡视图的初始化逻辑
让NumberSequenceView从缓存获取数据,而非每次初始化都重新生成:
struct NumberSequenceView: View { var original: [Int] var masked: [Int?] @State var guesses: [Int?] @Binding var checkState: CheckState let seed: Int init(checkState: Binding<CheckState>, seed: Int) { self.seed = seed // 从缓存获取关卡数据,避免重复生成 let sequence = LevelDataCache.shared.getSequence(for: seed) original = sequence.original masked = sequence.masked guesses = sequence.masked self._checkState = checkState } // 其余视图代码保持不变... }
3. 确保每个关卡使用唯一种子
在GameBoardView中,直接用关卡编号作为唯一种子,保证不同关卡的随机性独立:
private func destinationView(for level: Int) -> some View { // 用关卡编号作为唯一种子,确保每个关卡生成独立的随机内容 return AnyView(NumberSequenceView(checkState: $checkState, seed: level)) }
4. 修正随机生成器的种子作用逻辑
确保NumberSequence中所有随机操作都使用传入的种子生成器:
class NumberSequence { let hiddenNumbers = 1 let listLength: Int = 10 var generator: RandomNumberGeneratorWithSeed init(seed: Int) { generator = RandomNumberGeneratorWithSeed(seed: seed) } func generateSequence() -> (original: [Int], masked: [Int?]) { var original: [Int] = [] while true { original = [] // 所有随机操作都绑定到当前种子的生成器 let sign1 = Sign.allCases.randomElement(using: &generator)! let val1 = Int.random(in: 1..<2, using: &generator) let startValue = Int.random(in: 1..<50, using: &generator) var newValue = startValue for _ in 0..<listLength { original.append(newValue) newValue = sign1.op()(newValue, val1) } if (original.max()! < 150) && (original.min()! > 0) { break } } var randomIndexes = Set<Int>() while randomIndexes.count < hiddenNumbers { randomIndexes.insert(Int.random(in: 0..<listLength, using: &generator)) } var masked = [Int?]() for i in 0..<listLength { masked.append(randomIndexes.contains(i) ? nil : original[i]) } return (original: original, masked: masked) } }
方案说明
- 缓存机制:同一关卡(对应同一种子)的随机数据仅生成一次,后续无论视图如何初始化/刷新,都直接读取缓存,彻底解决关卡内容中途更新的问题;
- 种子唯一性:每个关卡使用唯一种子(关卡编号),保证不同关卡的随机内容独立不重复;
- 数据与视图分离:将随机生成逻辑从视图层剥离到缓存类,视图仅负责展示数据,避免因视图生命周期导致的重复生成。
内容的提问来源于stack exchange,提问作者charelf
相关产品推荐
相关产品推荐

