Unity跨场景控制组件启禁报错:ArgumentException:场景无效
First, let's break down why you're hitting this error: You're trying to access root GameObjects from "First Level" before the scene is loaded into memory. When you call SceneManager.GetSceneByName("First Level") prior to LoadScene, Unity returns an invalid scene instance because that scene hasn't been loaded yet—hence the "invalid scene" exception.
Here are two solid, practical solutions to fix this and achieve your goal of toggling components based on the button clicked:
Solution 1: Use Scene Load Callback with Mode Tracking
This approach uses a persistent flag (via PlayerPrefs) to store the selected game mode, then adjusts components automatically once the target scene finishes loading.
First, register a scene load event in your main menu script:
private void Start() { // Hook into the scene loaded event to run post-load logic SceneManager.sceneLoaded += OnTargetSceneLoaded; } private void OnTargetSceneLoaded(Scene loadedScene, LoadSceneMode loadMode) { // Only process the scene we care about if (loadedScene.name != "First Level") return; // Retrieve the stored game mode (1 = PVP, 0 = VS CPU) bool isPlayerVsPlayer = PlayerPrefs.GetInt("SelectedGameMode", 0) == 1; // Find the gameplay root object and adjust component states foreach (GameObject rootObj in loadedScene.GetRootGameObjects()) { if (rootObj.CompareTag("--Gameplay")) { rootObj.GetComponentInChildren<StaticPlayerCtr_p2>().enabled = isPlayerVsPlayer; rootObj.GetComponentInChildren<AI>().enabled = !isPlayerVsPlayer; rootObj.GetComponentInChildren<UnityEngine.AI.NavMeshAgent>().enabled = !isPlayerVsPlayer; } } } // Clean up the event listener to prevent memory leaks private void OnDestroy() { SceneManager.sceneLoaded -= OnTargetSceneLoaded; }
Then update your button click methods to just set the mode and trigger loading:
public void PlayerVsPlayer() { // Store that we're entering PVP mode PlayerPrefs.SetInt("SelectedGameMode", 1); SceneManager.LoadScene("First Level"); } public void VsCPU() { // Store that we're entering VS CPU mode PlayerPrefs.SetInt("SelectedGameMode", 0); SceneManager.LoadScene("First Level"); } public void QuitGame() { Application.Quit(); }
Solution 2: Async Scene Load with Post-Load Processing
If you prefer not to use PlayerPrefs, you can load the scene asynchronously and modify components immediately after it finishes loading:
using System.Threading.Tasks; // Don't forget this namespace for async/await public async void PlayerVsPlayer() { // Start loading the scene in the background AsyncOperation loadOperation = SceneManager.LoadSceneAsync("First Level"); // Wait until the scene is fully loaded into memory while (!loadOperation.isDone) { await Task.Yield(); } // Now the scene is valid—access its root objects Scene targetScene = SceneManager.GetSceneByName("First Level"); foreach (GameObject rootObj in targetScene.GetRootGameObjects()) { if (rootObj.CompareTag("--Gameplay")) { rootObj.GetComponentInChildren<StaticPlayerCtr_p2>().enabled = true; rootObj.GetComponentInChildren<AI>().enabled = false; rootObj.GetComponentInChildren<UnityEngine.AI.NavMeshAgent>().enabled = false; } } } // Repeat the pattern for the VsCPU button public async void VsCPU() { AsyncOperation loadOperation = SceneManager.LoadSceneAsync("First Level"); while (!loadOperation.isDone) { await Task.Yield(); } Scene targetScene = SceneManager.GetSceneByName("First Level"); foreach (GameObject rootObj in targetScene.GetRootGameObjects()) { if (rootObj.CompareTag("--Gameplay")) { rootObj.GetComponentInChildren<StaticPlayerCtr_p2>().enabled = false; rootObj.GetComponentInChildren<AI>().enabled = true; rootObj.GetComponentInChildren<UnityEngine.AI.NavMeshAgent>().enabled = true; } } } public void QuitGame() { Application.Quit(); }
Quick Optimization Tips
- Replace hardcoded scene names and tags with constants (e.g.,
private const string TargetScene = "First Level";) to avoid typos and make updates easier. - Instead of using
GetComponentInChildrenevery time, consider adding direct references to these components in a script attached to the--Gameplayobject—this is faster and more reliable. - If you're using
PlayerPrefs, addPlayerPrefs.Save()if you want the mode selection to persist between game sessions (not strictly necessary for in-session switching).
内容的提问来源于stack exchange,提问作者Andrea

