如何从GameViewController更新SKScene变量?Admob激励视频后标签不实时更新
解决Admob激励视频奖励后Lives标签不实时更新的问题
嘿,我看你遇到了个iOS开发里的常见小坑:激励视频看完后,livesLabel没法立刻刷新显示新的生命值,得切换场景回来才生效。咱们拆解下问题根源,再给你几个靠谱的解决方案:
问题出在哪?
你在MainScene里定义的lives属性,只会在初始化时从UserDefaults读取一次值,而且它的didSet回调只有当你直接给这个lives变量赋值的时候才会触发。但你在GameViewController的奖励回调里,只是更新了GameData和UserDefaults,并没有通知MainScene去读取最新值,所以标签自然不会实时变化。
下面是按推荐程度排序的解决办法:
方案1:直接更新MainScene的属性(最简单直接)
如果MainScene是当前显示的场景,直接在奖励回调里找到它的实例,给lives赋值就行——这样didSet会立刻触发,标签同步更新:
func rewardBasedVideoAd(_ rewardBasedVideoAd: GADRewardBasedVideoAd, didRewardUserWith reward: GADAdReward) { print("Reward received with currency: \(reward.type), amount \(reward.amount).") // 先更新数据 GameData.shared.lives += 1 UserDefaults.standard.set(GameData.shared.lives, forKey: "lives") UserDefaults.standard.synchronize() // 关键:找到当前的MainScene实例,直接更新lives if let skView = self.view as? SKView, let mainScene = skView.scene as? MainScene { mainScene.lives = GameData.shared.lives } }
方案2:用通知中心解耦(适合复杂场景结构)
如果你的页面跳转逻辑比较复杂,不想直接依赖MainScene的实例,用通知中心发消息是更解耦的选择:
第一步:在奖励回调里发送通知
func rewardBasedVideoAd(_ rewardBasedVideoAd: GADRewardBasedVideoAd, didRewardUserWith reward: GADAdReward) { print("Reward received with currency: \(reward.type), amount \(reward.amount).") GameData.shared.lives += 1 UserDefaults.standard.set(GameData.shared.lives, forKey: "lives") UserDefaults.standard.synchronize() // 发送生命值更新的通知 NotificationCenter.default.post(name: NSNotification.Name(rawValue: "LivesUpdated"), object: nil) }
第二步:在MainScene里监听通知
class MainScene: SKScene { var livesLabel: SKLabelNode? var lives = UserDefaults.standard.integer(forKey: "lives") { didSet{ livesLabel?.text = "\(lives)" } } override func didMove(to view: SKView) { // 注册通知监听 NotificationCenter.default.addObserver( self, selector: #selector(refreshLives), name: NSNotification.Name(rawValue: "LivesUpdated"), object: nil ) } @objc private func refreshLives() { // 读取最新值并赋值,触发didSet lives = GameData.shared.lives } deinit { // 记得移除监听,防止内存泄漏 NotificationCenter.default.removeObserver(self) } }
方案3:让GameData支持响应式更新(最优雅的长期方案)
如果你的项目里多个地方都需要监听生命值变化,把GameData改成可观察对象会更优雅,以后任何地方修改生命值,所有关联的UI都会自动更新:
修改GameData为ObservableObject
import Combine class GameData: ObservableObject { static let shared = GameData() // @Published会自动通知订阅者值变化 @Published var lives: Int { didSet { // 自动同步到UserDefaults UserDefaults.standard.set(lives, forKey: "lives") } } private init() { // 从UserDefaults初始化 lives = UserDefaults.standard.integer(forKey: "lives") } }
在MainScene里订阅变化
class MainScene: SKScene { var livesLabel: SKLabelNode? // 用来保存订阅,防止被提前释放 private var cancellables = Set<AnyCancellable>() override func didMove(to view: SKView) { // 订阅lives的变化,更新标签 GameData.shared.$lives .receive(on: DispatchQueue.main) // 确保在主线程更新UI .sink { [weak self] newLivesCount in self?.livesLabel?.text = "\(newLivesCount)" } .store(in: &cancellables) } }
用这个方案的话,你甚至可以删掉MainScene里的lives属性,直接通过订阅GameData的变化来更新标签,代码更简洁。
总结一下,你的原有代码之所以不生效,核心就是MainScene的lives变量没有被主动更新——它不会自动监听UserDefaults的变化,必须有一个主动触发的动作来让它读取最新值并刷新UI。
内容的提问来源于stack exchange,提问作者Tom B.
相关产品推荐
相关产品推荐

