AgentSkillsCN

scene-management-patterns

Pirate Tile Clash的场景管理模式、场景组,以及多场景架构。适用于场景搭建、场景组创建,或场景切换的管理与优化。

SKILL.md
--- frontmatter
name: scene-management-patterns
description: Scene management patterns, Scene Groups, and multi-scene architecture for Pirate Tile Clash. Use when setting up scenes, creating Scene Groups, or managing scene transitions.

Scene Management Patterns

Scene Group Setup

Create Scene Group Asset

  1. Right-click in Project: Create > Scriptable Objects > Scene Management > Scene Group
  2. Add scenes to the Scenes list
  3. Assign correct SceneType for each scene
  4. Configure Persistent flag appropriately

Scene Type Rules

  • Boot: Not persistent, loaded once
  • PersistentSystems: Persistent, always loaded
  • AppShell: Persistent, main app root
  • GameplayBase: Not persistent, loaded with gameplay
  • World: Not persistent, level geometry
  • Overlay: Not persistent, temporary UI

Scene Group Configuration

Core Group (SG_Core)

csharp
// Scene Group contains:
// - SC_Boot (Boot, Persistent: false)
// - SC_PersistentSystems (PersistentSystems, Persistent: true)
// - SC_AppShell (AppShell, Persistent: true)

Gameplay Group (SG_Gameplay)

csharp
// Scene Group contains:
// - SC_GameplayBase (GameplayBase, Persistent: false)
// - SC_World_Gameplay (World, Persistent: false)

Overlay Group (SG_Overlays)

csharp
// Scene Group contains:
// - SC_Overlay_HUD (Overlay, Persistent: false)
// - SC_Overlay_Popups (Overlay, Persistent: false)

Loading Scenes

Load Scene Group

csharp
public async UniTask LoadSceneGroupAsync(SceneGroup sceneGroup)
{
    try
    {
        await SceneGroupManager.Instance.LoadSceneGroupAsync(sceneGroup);
        Debug.Log($"[SceneLoader] Loaded scene group: {sceneGroup.name}");
    }
    catch (Exception e)
    {
        Debug.LogError($"[SceneLoader] Failed to load scene group: {e.Message}");
    }
}

Startup Flow

csharp
// 1. Load Core Group
await LoadSceneGroupAsync(SG_Core);

// 2. Wait for systems to initialize
await WaitForSystemsReady();

// 3. Unload Boot scene
await UnloadSceneAsync(SC_Boot);

Scene Lifecycle

Scene Loaded

csharp
public class GameplaySceneController : MonoBehaviour
{
    void OnEnable()
    {
        SceneManager.sceneLoaded += OnSceneLoaded;
    }
    
    void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    {
        if (scene.name == "SC_GameplayBase")
        {
            InitializeGameplay();
        }
    }
}

Scene Unloaded

csharp
void OnDisable()
{
    SceneManager.sceneUnloaded += OnSceneUnloaded;
}

void OnSceneUnloaded(Scene scene)
{
    if (scene.name == "SC_World_Gameplay")
    {
        CleanupGameplay();
    }
}

Persistent Scenes

Managers in PersistentSystems

csharp
public class DataManager : MonoBehaviour
{
    public static DataManager Instance { get; private set; }
    
    void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }
        
        Instance = this;
        DontDestroyOnLoad(gameObject); // Persists across scenes
    }
}

UI in AppShell

csharp
public class UIManager : MonoBehaviour
{
    // Persists across gameplay
    // Main menu always accessible
    // Returns here after match ends
}

Scene Transitions

To Gameplay

csharp
public async UniTask StartMatchAsync()
{
    // Load gameplay scenes
    await SceneGroupManager.Instance.LoadSceneGroupAsync(SG_Gameplay);
    await SceneGroupManager.Instance.LoadSceneGroupAsync(SG_Overlays);
    
    // Initialize match
    await MatchManager.Instance.StartMatchAsync();
}

From Gameplay

csharp
public async UniTask EndMatchAsync()
{
    // Show end screen
    UIManager.Instance.ShowMatchEndScreen();
    
    // Unload gameplay scenes
    await SceneGroupManager.Instance.UnloadSceneGroupAsync(SG_Gameplay);
    await SceneGroupManager.Instance.UnloadSceneGroupAsync(SG_Overlays);
    
    // Return to AppShell (already loaded)
}

Validation Rules

Scene Group Validation

  • Core group must have exactly one Boot, PersistentSystems, AppShell
  • Gameplay group must have exactly one GameplayBase, World
  • Overlay scenes must not be persistent
  • Boot scene must not be persistent

Runtime Validation

csharp
public void ValidateSceneGroup(SceneGroup group)
{
    foreach (var scene in group.Scenes)
    {
        // Check persistence rules
        if (scene.Type == SceneType.Boot && scene.Persistent)
        {
            Debug.LogError("Boot scene cannot be persistent!");
        }
        
        if (scene.Type == SceneType.AppShell && !scene.Persistent)
        {
            Debug.LogError("AppShell must be persistent!");
        }
    }
}

Common Patterns

Wait for Scene Loaded

csharp
public async UniTask WaitForSceneLoadedAsync(string sceneName)
{
    while (!IsSceneLoaded(sceneName))
    {
        await UniTask.Yield();
    }
}

bool IsSceneLoaded(string sceneName)
{
    for (int i = 0; i < SceneManager.sceneCount; i++)
    {
        if (SceneManager.GetSceneAt(i).name == sceneName)
        {
            return true;
        }
    }
    return false;
}

Find Objects in Scene

csharp
public T FindObjectInScene<T>(string sceneName) where T : Component
{
    var scene = SceneManager.GetSceneByName(sceneName);
    if (!scene.IsValid()) return null;
    
    var rootObjects = scene.GetRootGameObjects();
    foreach (var obj in rootObjects)
    {
        var component = obj.GetComponentInChildren<T>();
        if (component != null)
            return component;
    }
    
    return null;
}

Best Practices

Scene Organization

  • Keep related systems in same scene
  • Use additive loading for overlays
  • Unload scenes when not needed
  • Never load Boot scene after startup

Performance

  • Load scenes asynchronously
  • Show loading progress
  • Preload frequently used scenes
  • Unload unused scenes to free memory

Error Handling

  • Validate scene exists before loading
  • Handle load failures gracefully
  • Provide user feedback during loading
  • Fallback to safe state on error