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

Unity中TextMeshPro分数显示异常:场景切换后不更新求助

Unity 2D平台游戏跨场景分数显示异常问题

我正在开发一款Unity 2D平台游戏,玩家拾取道具时,跟踪分数的TextMeshPro元素每次加10,第一关功能正常。但通过LevelExit脚本的OnTriggerEnter2D切换关卡后,playerLives变量仍能正常联动TMPro元素并在死亡时更新,可score整数在第一关后不再更新。不过直接从第二关启动游戏时,分数显示正常,因此问题与场景加载逻辑相关。

我已在各处添加DebugLog,并编写ResetScore公共方法,控制台显示切换关卡后score确实重置为0,但Unity界面上无更新。尝试过强制刷新Canvas等多种更新TMPro的方法,均未解决问题。


相关脚本

ScoreKeeper Script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using TMPro;

public class ScoreKeeper : MonoBehaviour
{
    Pickups pickups;
    private int score = 0;
    public int pickupCount = 0;
    [SerializeField] int playerLives = 3;
    [SerializeField] TextMeshProUGUI livesText;
    [SerializeField] TextMeshProUGUI scoreText;

    void Awake() 
    {
        int numScoreKeepers = FindObjectsOfType<ScoreKeeper>().Length;
        if (numScoreKeepers > 1)
        {
            Destroy(gameObject);
        }
        else
        {
            DontDestroyOnLoad(gameObject);
        }     
    }

    void Start() 
    {
        score = 0;
        livesText.text = playerLives.ToString(); 
        scoreText.text = score.ToString(); 
    }

    public void ProcessPlayerDeath()
    {
        if (playerLives > 1)
        {
            TakeLife();
        }
        else
        {
            ResetGameSession();
        }
    }

    public void ResetGameSession()
    {
        FindObjectOfType<ScenePersist>().ResetScenePersist();
        SceneManager.LoadScene(0);
        Destroy(gameObject);
    }

    void TakeLife()
    {
        playerLives --;
        int currentSceneIndex = SceneManager.GetActiveScene().buildIndex;
        SceneManager.LoadScene(currentSceneIndex);  
        livesText.text = playerLives.ToString();
  
    }

    public void IncrementScore(int value)
    {
        score += value;
        scoreText.text = score.ToString(); 
        Debug.Log("scoreText.text is: " + scoreText.text);
    }

    public bool CheckIfHasPickups()
    {
        pickupCount = GameObject.FindGameObjectsWithTag("Pickups").Length;

        if (score >= pickupCount * 10)
        {
            return (true);
        }
        else 
        {
            return (false);
        }
    }

    public void ResetScore()
    {
        score = 0;
        Debug.Log("Score is " + score);
    }
}

Pickups Script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Pickups : MonoBehaviour
{
    ScoreKeeper scoreKeeper;
    Rigidbody2D myRigidbody;
    void Start()
    {
        scoreKeeper = FindObjectOfType<ScoreKeeper>();
        myRigidbody = GetComponent<Rigidbody2D>();
    }

    void Update() 
    {
        PickupInteraction();
    }

    public void PickupInteraction()
    {
        if (myRigidbody.IsTouchingLayers(LayerMask.GetMask("Player")))
        {
            scoreKeeper.IncrementScore(10);
            Destroy(gameObject);
        }
    }
}

ScenePersist Script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ScenePersist : MonoBehaviour
{
    void Awake() 
    {
        int numScenePersists = FindObjectsOfType<ScenePersist>().Length;
        if(numScenePersists > 1)
        {
            gameObject.SetActive(false);
            Destroy(gameObject);
            Debug.Log("Level Persist sees it is a copy, destroys itself");
        }
        else
        {
            DontDestroyOnLoad(gameObject);
        }
    }

    public void ResetScenePersist()
    {
        Destroy(gameObject);
    }
}

LevelExit Script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class LevelExit : MonoBehaviour
{
    ScoreKeeper scoreKeeper;

    void Start() 
    {
        scoreKeeper = FindObjectOfType<ScoreKeeper>();
    }
    
    void OnTriggerEnter2D(Collider2D other)
    {
        if (other.tag == "Player" && scoreKeeper.CheckIfHasPickups())
        {
            StartCoroutine(LoadLevel());
        }
        else
        {
            Debug.Log("Not all pickups collected!");
        }
    }
    IEnumerator LoadLevel()
    {
        yield return new WaitForSecondsRealtime(1);
        scoreKeeper.ResetScore();
        int currentSceneIndex = SceneManager.GetActiveScene().buildIndex;
        SceneManager.LoadScene(currentSceneIndex + 1);
        FindObjectOfType<ScenePersist>().ResetScenePersist();
    }
}

问题分析与修复方案

问题根源

ScoreKeeper通过DontDestroyOnLoad在场景切换时保留,但它的scoreText和livesText引用的是第一场景中的UI元素。切换到第二场景后,旧场景的UI已被销毁,新场景的TMPro元素未与保留的ScoreKeeper建立关联,导致后续修改scoreText.text时,实际操作的是已销毁对象的属性,自然无法在新场景显示。

修复步骤

1. 给ScoreKeeper添加场景加载监听,动态获取新场景UI引用

修改ScoreKeeper代码,监听场景加载完成事件,每次场景切换后自动查找当前场景的TMPro元素并更新引用:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using TMPro;

public class ScoreKeeper : MonoBehaviour
{
    Pickups pickups;
    private int score = 0;
    public int pickupCount = 0;
    [SerializeField] int playerLives = 3;
    private TextMeshProUGUI livesText;
    private TextMeshProUGUI scoreText;

    void Awake() 
    {
        int numScoreKeepers = FindObjectsOfType<ScoreKeeper>().Length;
        if (numScoreKeepers > 1)
        {
            Destroy(gameObject);
        }
        else
        {
            DontDestroyOnLoad(gameObject);
            // 注册场景加载完成监听
            SceneManager.sceneLoaded += OnSceneLoaded;
        }     
    }

    // 场景加载完成后执行
    void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    {
        // 通过标签精准查找UI(需给分数/生命UI添加对应标签)
        var livesObj = GameObject.FindGameObjectWithTag("LivesText");
        var scoreObj = GameObject.FindGameObjectWithTag("ScoreText");

        if(livesObj == null || scoreObj == null)
        {
            Debug.LogWarning("当前场景未找到LivesText/ScoreText标签的UI元素");
            return;
        }

        livesText = livesObj.GetComponent<TextMeshProUGUI>();
        scoreText = scoreObj.GetComponent<TextMeshProUGUI>();

        // 同步当前数据到UI
        livesText.text = playerLives.ToString(); 
        scoreText.text = score.ToString(); 
    }

    void Start() 
    {
        // 初始化逻辑移至OnSceneLoaded
    }

    public void ProcessPlayerDeath()
    {
        if (playerLives > 1)
        {
            TakeLife();
        }
        else
        {
            ResetGameSession();
        }
    }

    public void ResetGameSession()
    {
        // 移除监听避免内存泄漏
        SceneManager.sceneLoaded -= OnSceneLoaded;
        FindObjectOfType<ScenePersist>().ResetScenePersist();
        SceneManager.LoadScene(0);
        Destroy(gameObject);
    }

    void TakeLife()
    {
        playerLives --;
        int currentSceneIndex = SceneManager.GetActiveScene().buildIndex;
        SceneManager.LoadScene(currentSceneIndex);  
        // 场景加载后OnSceneLoaded会自动更新生命UI
    }

    public void IncrementScore(int value)
    {
        score += value;
        if(scoreText != null)
        {
            scoreText.text = score.ToString(); 
            Debug.Log("scoreText.text is: " + scoreText.text);
        }
        else
        {
            Debug.LogWarning("scoreText未初始化,无法更新分数");
        }
    }

    public bool CheckIfHasPickups()
    {
        pickupCount = GameObject.FindGameObjectsWithTag("Pickups").Length;
        return score >= pickupCount * 10;
    }

    public void ResetScore()
    {
        score = 0;
        Debug.Log("Score is " + score);
        // 重置后立即更新UI
        if(scoreText != null)
        {
            scoreText.text = score.ToString();
        }
    }

    void OnDestroy()
    {
        // 移除监听防止内存泄漏
        SceneManager.sceneLoaded -= OnSceneLoaded;
    }
}

2. 给UI元素添加标签

在Unity编辑器中,给显示生命的TextMeshProUGUI添加标签LivesText,给显示分数的TextMeshProUGUI添加标签ScoreText,确保脚本能精准找到目标UI。

3. 移除Inspector中的UI引用

因为现在通过动态查找获取UI引用,无需在Inspector中手动赋值,避免混淆。


内容的提问来源于stack exchange,提问作者lxjx

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 23:09:57