简单完成一些场景相关的功能。
一:封装场景数据
其中SceneType 包含两种:主菜单场景 和 游戏场景,以实现不同逻辑。
[CreateAssetMenu(menuName = "Game Scene/GameSceneSO")]
public class GameSceneSO : ScriptableObject
{
public AssetReference sceneReference;
public SceneType sceneType;
}
二:传送点
记录了要传送的场景以及位置。在被触碰时即触发传送。
public class TransitionPoint : MonoBehaviour
{
[SerializeField] private GameSceneSO sceneToGo;
[SerializeField] private Vector3 positionToGo;
[SerializeField] private bool hideAfterTransition;
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag("Player"))
TransitionAction();
}
private void TransitionAction()
{
SceneLoader.Instance.SceneTransition(sceneToGo, positionToGo, true);
if(hideAfterTransition) gameObject.SetActive(false);
}
}
三:场景管理类
1:记录了游戏刚开始时加载的场景,同时保留了将要加载场景的数据。
2:场景加载前和加载后要做的事情使用全局事件实现。
3:具体的加载和卸载使用AssetReference下的LoadSceneAsync 和 UnLoadScene(一定要配套使用,不能一个用 Addressables.LoadSceneAsync(),另一个用 AssetReference的UnLoadScene() )。
4:利用协程完成场景的异步加载过程:(1)等待屏幕fadein (2)等待卸载当前的场景 (3)屏幕FadeOut + 新场景的加载。
[DefaultExecutionOrder(-50)]
public class SceneLoader : Singleton<SceneLoader>,ISavable
{
public GameSceneSO firstScene;
public float fadeDuration;
[SerializeField] private Transform player;
[SerializeField] private GameSceneSO restScene;
[SerializeField] private GameSceneSO currentScene;
private GameSceneSO sceneToGo;
private Vector3 posToGo;
private bool needFade;
public bool IsLoading { get; private set; }
protected override void Awake()
{
base.Awake();
sceneToGo = firstScene;
currentScene = firstScene;
LoadNewScene();
}
private void Start()
{
AudioManager.Instance.PlayBgmBySceneType(currentScene.sceneType);
MouseManager.Instance.SetMouseCursorBySceneType(currentScene.sceneType);
GlobalEvent.CallEnterMenuSceneEvent();
}
private void OnEnable()
{
(this as ISavable).RegisterSaveData();
GlobalEvent.newGameEvent += TransitionToRestScene;
}
private void OnDisable()
{
(this as ISavable).UnRegisterSaveData();
GlobalEvent.newGameEvent -= TransitionToRestScene;
}
public SceneType GetCurrentSceneType()
{
if (currentScene != null) return currentScene.sceneType;
return SceneType.Menu;
}
/// <summary>
///
/// </summary>
/// <param name="sceneToGo">前往的场景</param>
/// <param name="posToGo">新场景的初始的位置</param>
/// <param name="needFade">是否需要淡入淡出</param>
public void SceneTransition(GameSceneSO sceneToGo, Vector3 posToGo, bool needFade)
{
if (IsLoading) return;
//设定加载的状态和信息
IsLoading = true;
this.sceneToGo = sceneToGo;
this.posToGo = posToGo;
this.needFade = needFade;
GlobalEvent.CallBeforeSceneLoadEvent();
StartCoroutine(SceneTransition());
}
private IEnumerator SceneTransition()
{
if (needFade)
{
UIManager.Instance.fadeCanvas.Fadein(fadeDuration);
}
yield return new WaitForSeconds(fadeDuration);
//等待当前场景卸载完
yield return currentScene.sceneReference.UnLoadScene();
//加载新的场景
LoadNewScene();
}
private void LoadNewScene()
{
var loadingOperation=sceneToGo.sceneReference.LoadSceneAsync(LoadSceneMode.Additive, true);
loadingOperation.Completed += OnLoadCompleted;
}
private void OnLoadCompleted(AsyncOperationHandle<SceneInstance> obj)
{
if (obj.Status == AsyncOperationStatus.Succeeded)
{
currentScene = sceneToGo;
//player.gameObject.SetActive(currentScene.sceneType != SceneType.Persistent);
IsLoading = false;
GlobalEvent.CallAfterSceneLoadEvent(posToGo);
if (needFade)
{
UIManager.Instance.fadeCanvas.FadeOut(fadeDuration);
}
SceneManager.SetActiveScene(obj.Result.Scene);
if(currentScene.sceneType!=SceneType.Persistent)AudioManager.Instance.PlayBgmBySceneType(currentScene.sceneType);
MouseManager.Instance.SetMouseCursorBySceneType(currentScene.sceneType);
UIManager.Instance.SetPanelAfterLoad(currentScene.sceneType);
if (currentScene.sceneType == SceneType.Menu) GlobalEvent.CallEnterMenuSceneEvent();
else GlobalEvent.CallExitMenuSceneEvent();
}
else
{
Debug.LogError("Failed to load scene: " + obj.OperationException.Message);
}
}
private void TransitionToRestScene()
{
SceneTransition(restScene, Vector3.zero, true);
}
#region Save And Load
public string GetDataID()
{
return "Scene";
}
public void SaveData(Data data)
{
data.SaveScene(currentScene);
data.playerPos = new SerializeVector3(player.position);
}
public void LoadData(Data data)
{
IsLoading = false;
SceneTransition(data.LoadScene(), data.playerPos.ToVector3(), true);
}
#endregion
}
四:屏幕淡入淡出的实现
新建一个单独的Fade Canvas,里面放置有一张Image(黑色),利用DoTween下的DOBlendableColor函数调整Image的透明度即可。(也可以直接写个协程就行)
public class FadeCanvas : MonoBehaviour
{
public Image fadeImage;
//实现画面的渐入:即将透明度从0变成1
public void Fadein(float duration)
{
Color targetColor = fadeImage.color;
targetColor.a = 1;
fadeImage.DOBlendableColor(targetColor, duration);
}
//实现画面的渐出:即将透明度从1变成0
public void FadeOut(float duration)
{
Color targetColor = fadeImage.color;
targetColor.a = 0;
fadeImage.DOBlendableColor(targetColor, duration);
}
}
补充:如何保存场景? 保存系统基于 数据的保存与加载
在Data中额外添加以下数据:一个是场景,一个是人物的坐标位置。
public string sceneToSave;
public SerializeVector3 playerPos;
在Data中还单独实现了 保存 和 加载的方法。
注意:整个DataManager采用的是 Newtonsoft.Json ,而这里采用的是Unity自带的JsonUtility,原因在于Newtonsoft.Json在序列化GameSceneSO 后,无法正确还原(可能Newtonsoft.Json不支持Unity中的AssetReference,而JsonUtility支持)。
public void SaveScene(GameSceneSO gameScene)
{
sceneToSave = JsonUtility.ToJson(gameScene);
}
public GameSceneSO LoadScene()
{
var gameScene = ScriptableObject.CreateInstance<GameSceneSO>();
JsonUtility.FromJsonOverwrite(sceneToSave, gameScene);
return gameScene;
}
在SceneLoader完成Save 和 Load 的接口
public void SaveData(Data data)
{
data.SaveScene(currentScene);
data.playerPos = new SerializeVector3(player.position);
}
public void LoadData(Data data)
{
isLoading = false;
SceneTransition(data.LoadScene(), data.playerPos.ToVector3(), true);
}