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

如何从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.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 09:41:38