Unity C#使用SceneController和ISaveable系统实现转换Scene时的物品内存管理

前言

类的介绍:

SceneControllerManager:

        用于进行Scene开启关闭以及是否使SceneActive,从而达到不同Scene之间的转换

SceneSave:

        用于存储每个Scene应该存储和读取的的SceneData

        所有应该存储/读取的数据都可以放入其中

GameObjectSave

        一个Key为SceneName,Value为SceneSave的Dictionary,用于快速读取每个场景的SceneData

ISaveable:

        一个接口,内部涵盖了GUID(Globally Unique Identifier),全局唯一标识符,便于对所有继承于ISaveable的类所构造的唯一对象的管理。

        GUID();        

        GameObjectSave();

        存储系统,包含SceneData的数据集合,此处使用Dictionary数据结构存取。

        IsaveableRegister(); 
        IsaveableDerigister();

         二者用于向SaveLoadManger的List进行注册和注销,使此ISaveable接入SaveLoadManager的SaveList之中,使其Store和Restore函数 有效/无效。

        IsaveableStore();//将该场景的数据存储入本身的GameObjectSave中。
        IsaveableRestore();//将上次存储的场景恢复,并且覆写该场景现有的数据。

  

代码:

SceneControllerManager

           Unity的SceneLoad具备两种形式,一种是Single,即LoadScene时只打开选中的Scene,另外一种是Additive,可以同时加载多个Scene,但只能有一个ActiveScene,ActiveScene就是Engine用来添加新gameobject的Scene,所以在loadScene进行替换是,还得将其设置为Active。 

下方有许多AsyncOperation 类的方法,需要使用协程方法来调用,不然编译器会发出警告。 

 总结:

在场景加载完毕前不会进行Fade0的协程,这样就确保了fade结束后场景已经完全加载,这就是采用大量异步方法来编写SceneControllerManager的原因。

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

public class SceneControllerManager : SingletonMonoBehaviour<SceneControllerManager>
{
    private bool isFading;
    [SerializeField] private Image fadeImage;
    [SerializeField] private CanvasGroup fadeImageCanvasGroup;
    [SerializeField] private float fadeDuration;
    [SerializeField] private SceneName startSceneName;

    private IEnumerator Fade(float fadeFinalAlpha)
    {
        isFading=true;
        //场景加载时禁止角色输入的点击
        fadeImageCanvasGroup.blocksRaycasts=true;
        
        float fadeSpeed=Mathf.Abs(fadeImageCanvasGroup.alpha-fadeFinalAlpha)/fadeDuration;

        while(!Mathf.Approximately(fadeImageCanvasGroup.alpha,fadeFinalAlpha))
        {
            fadeImageCanvasGroup.alpha=Mathf.MoveTowards(fadeImageCanvasGroup.alpha,fadeFinalAlpha,Time.deltaTime*fadeSpeed);
            yield return null;
        }
        fadeImageCanvasGroup.blocksRaycasts=false;
        isFading=false;
    }

    private IEnumerator FadeAndSwitchScene(string SceneName)
    {       
        EventHandler.CallBeforeSceneLoadEvent();
        yield return StartCoroutine(Fade(1f));
        // SaveLoadManager.Instance.StoreCurrentScene(SceneName);

        //Switch Scene
        //1.Unload the current scene 此处需要使用使用yield return 应为 UnloadSceneAsync 是一个 AsyncOperation,属于异步方法,需要使用yield return,不然编译器会发出警告
        yield return SceneManager.UnloadSceneAsync(SceneManager.GetActiveScene().buildIndex);
        //2.Begin the LoadingNextScene IEnumator
        StartCoroutine(LoadSceneAndSetActive(SceneName));

        // SaveLoadManager.Instance.RestoreCurrentScene(SceneName);
        yield return StartCoroutine(Fade(0f));
        EventHandler.CallAfterSceneLoadEvent();
    }

    private IEnumerator LoadSceneAndSetActive(string SceneName)
    {   
        //Additive/Single?
        yield return SceneManager.LoadSceneAsync(SceneName,LoadSceneMode.Additive);
        //How the SceneLoad in the SceneList?
        Scene newlyLoadedScene=SceneManager.GetSceneAt(SceneManager.sceneCount-1);
        //Why we should set active scene here?
        yield return SceneManager.SetActiveScene(newlyLoadedScene);
    }

    private IEnumerator Start()
    {
        fadeImage.color=new Color(0,0,0,1f);
        fadeImageCanvasGroup.alpha=1f;

        yield return LoadSceneAndSetActive(startSceneName.ToString());
        //使用委托
        EventHandler.CallAfterSceneLoadEvent();
        //覆写场景Data
        // SaveLoadManager.Instance.RestoreCurrentScene();
        
        //提供画面
        StartCoroutine(Fade(0f));

    }

    public void FadeAndLoadScene(string SceneName) 
    {    
        if(!isFading)
            StartCoroutine(FadeAndSwitchScene(SceneName));
    }
}
  SaveLoadManager:

提供四个函数接口,和两个固有访问器

public interface ISaveable 
{
    string ISavableUniqueID{get; set;}
    GameObjectSave ISavableGameObjectSave{get;set;}
    void ISaveableRegister();
    void ISaveableDerigister();
    void ISaveableRestoreScene(string sceneName);
    void ISaveableStoreScene(string sceneName);
}
SceneSave:

SceneSave中可以为任何东西,可以是地图的数据,也可以是游戏物件的数据,用数据存取StandardPrefab,也就是数据需要挂载的GameObject,就可以完成对该数据类型物件的存取与覆写。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// This class is used to contain the details that Scene should save and read
/// </summary>
public class SceneSave : MonoBehaviour
{
    public List<Item> itemList;
}
GenerateGUID

newGUID属于System的固有函数

using UnityEngine;
//ExecuteAlways使此代码在编译器运行时也可以同时运行
[ExecuteAlways]
public class GenerateGUID : MonoBehaviour
{   
    [SerializeField]
    private string _guid;
    public string GUID{
        get{
            return _guid;
        }
        set{
            _guid=value;
        }
    }
    
    private void Awake() 
    {
          //Only populate in the editor,make sure the gameobject is not playing in the scene
        if(!Application.IsPlaying(gameObject))
        {
            //Ensure the object has a guaranted unique id
            if(_guid=="")
            {
                //Asign GUID 此处调用System自带的NewGuid函数
                _guid=System.Guid.NewGuid().ToString();
            }
        }
    }

}
通过Isaveable接口构造一个SceneItemManager范例
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//使GenerateGUID成为这个Class的绝对附加类,即在挂载SceneItemManger时,会同时附加GenerateGUID
[RequireComponent(typeof(GenerateGUID))]
public class SceneItemManger : SingletonMonoBehaviour<SceneItemManger>,ISaveable
{
    [SerializeField] private Transform ItemParentTransform;

    private GameObjectSave itemGameObjectSave;
    public GameObjectSave ISavableGameObjectSave
    {
        get{
            return itemGameObjectSave;
        }
        set{
            itemGameObjectSave=value;
        }
    }
    [SerializeField]
    private string _guid;
    public string ISavableUniqueID
    {
        get{
            return _guid;
        }
        set{
            _guid=value;
        }
    }

    public void AfterSceneLoadEvent() 
    {
        ItemParentTransform=GameObject.FindGameObjectWithTag(Tags.ItemParentTransform).transform;
    }

    protected override void Awake()
    {
        base.Awake();
        _guid=this.GetComponent<GenerateGUID>().GUID;
        ISavableGameObjectSave=new GameObjectSave();
    }

    private void OnEnable() 
    {
        EventHandler.AfterSceneLoadEvent+=AfterSceneLoadEvent;
        ISaveableRegister();
    }

    private void OnDisable() 
    {
        EventHandler.AfterSceneLoadEvent-=AfterSceneLoadEvent;
        ISaveableDerigister();
    }

    public void ISaveableRegister()
    {
        SaveLoadManager.Instance.ISaveableObjectList.Add(this);
    }

    public void ISaveableDerigister()
    {
        SaveLoadManager.Instance.ISaveableObjectList.Remove(this);
    }

    public void ISaveableStoreScene(string sceneName)
    {   
           SceneSave currentSceneSave;
        if(ISavableGameObjectSave.sceneData.TryGetValue(sceneName,out currentSceneSave))
        {
            //do sth.
        }
    }   

    public void ISaveableRestoreScene(string sceneName)
    {   
        SceneSave currentSceneSave;
        if(ISavableGameObjectSave.sceneData.TryGetValue(sceneName,out currentSceneSave))
        {
            //do sth.
        }
    }

    public void DestoryAllTheItemInCurrentScene()
    {
        Item[] itemsInScene=GameObject.FindObjectsOfType<Item>();
        foreach(Item item in itemsInScene)
        {
            Destroy(item.gameObject);
        }
    }
}

最后,使用PlayerTest调用SceneChange

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

public class PlayerTest : MonoBehaviour
{   
    // [SerializeField] private GameObject ReUsePoolPrefab=null;
    private void Update() 
    {
        // if(Input.GetMouseButtonDown(1))
        // {
        //     Vector3 mouseScreenPosition=Input.mousePosition;
        //     Vector3 mouseWorldPosition=Camera.main.ScreenToWorldPoint(new Vector3(mouseScreenPosition.x,mouseScreenPosition.y,-Camera.main.transform.position.z));
        //     // PoolManger.Instance.ReUsePoolGameObject(ReUsePoolPrefab,mouseWorldPosition,Quaternion.identity);
        //     EventHandler.CallParticleUse(ReUsePoolPrefab,mouseWorldPosition,Quaternion.identity);
        // }
        
        if(Input.GetKeyDown(KeyCode.Q))
        {
            SceneControllerManager.Instance.FadeAndLoadScene(SceneName.Scene2.ToString());
        }
        else if(Input.GetKeyDown(KeyCode.E))
        {
            SceneControllerManager.Instance.FadeAndLoadScene(SceneName.Scene1.ToString());
        }
    }
}

SceneChange效果

 注:可以使用IEnumator重写Start函数

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值