239
社区成员




在unity游戏开发中实现游戏场景切换中,使用了C#事件调用的方法。
同时场景切换的另一重点在于切换场景时物品数据的保存与删除,在这里使用的方法是:
设置一个PersistentScene场景作为一直存在不会被卸载的场景,游戏会一直使用的部分都保存在该场景,其他剩余的场景在此基础上进行加载和卸载。
关于为什么要使用这个方法,原因在于:进行一次场景切换需要进行许多动作,EventHandler 提供了一种解耦的方式,使得代码结构更清晰、更易于维护。
如下,在运行前除了PersistentScene以外的其他场景要卸载掉,负责会出现两个场景重叠的情况(一开始那个没有被卸载的场景会一直存在)
在PersistentScene中加入SeceneManeger场景切换脚本,跟随PersistentScene一起在游戏最初加载出来。SeceneManeger场景切换脚本里的主要内容如下:
private void OnEnable()
{
EventHandler.TransitionEvent += OnTransitionEvent;
}
private void OnDisable()
{
EventHandler.TransitionEvent -= OnTransitionEvent;
}
private void OnTransitionEvent(string sceneToGo, Vector3 positionToGo)
{
if (!isFade)
{
StartCoroutine(Transition(sceneToGo, positionToGo));
Debug.Log("触发场景切换事件");
}
}
public IEnumerator Transition(string sceneName, Vector3 targetPosition)
{
yield return SceneManager.UnloadSceneAsync(SceneManager.GetActiveScene());
yield return Fade(1); // 场景效果的淡入
yield return LoadSceneSetActive(sceneName);
EventHandler.CallMoveToPosition(targetPosition);// 保证人物在新场景的位置,不写这个,人物会保持在原先位置
yield return Fade(0); // 场景效果的淡出
}
public IEnumerator LoadSceneSetActive(string sceneName)
{
yield return SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
Scene newScene = SceneManager.GetSceneAt(SceneManager.sceneCount - 1);
SceneManager.SetActiveScene(newScene);
EventHandler.CallAfterSceneLoadEvent();
}
完整代码如下:
using System;
using System.Collections;
using System.Collections.Generic;
using CodeValley.Map;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace CodeValley.transition
{
public class TransitionManager : MonoBehaviour, ISaveable
{
#if UNITY_EDITOR
[SceneName]
#endif
public string startSceneName = string.Empty;
private CanvasGroup fadeCanvasGroup;
private bool isFade;
private void Awake()
{
SceneManager.LoadScene("UI", LoadSceneMode.Additive);
}
private void OnEnable()
{
EventHandler.TransitionEvent += OnTransitionEvent;
EventHandler.StartSleepEvent += OnStartSleepEvent;
}
private void OnDisable()
{
EventHandler.TransitionEvent -= OnTransitionEvent;
EventHandler.StartSleepEvent -= OnStartSleepEvent;
}
private void Start()
{
ISaveable saveable = this;
saveable.RegisterSaveable();
fadeCanvasGroup = FindObjectOfType<CanvasGroup>();
StartCoroutine(LoadSceneSetActive(startSceneName));
}
void OnStartSleepEvent()
{
StartCoroutine(Sleep());
}
private void OnTransitionEvent(string sceneToGo, Vector3 positionToGo)
{
if (!isFade)
{
StartCoroutine(Transition(sceneToGo, positionToGo));
// 播放切换场景声音
EventHandler.CallPlaySoundEvent(SoundName.CloseDoor);
Debug.Log("播放切换场景声音");
}
}
public IEnumerator Transition(string sceneName, Vector3 targetPosition)
{
yield return SceneManager.UnloadSceneAsync(SceneManager.GetActiveScene());
Debug.Log("场景切换");
yield return Fade(1);
yield return LoadSceneSetActive(sceneName);
Debug.Log("位置转换");
EventHandler.CallMoveToPosition(targetPosition);
yield return Fade(0);
}
public IEnumerator LoadSceneSetActive(string sceneName)
{
yield return SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
Scene newScene = SceneManager.GetSceneAt(SceneManager.sceneCount - 1);
SceneManager.SetActiveScene(newScene);
CropManager.Instance.cropParent = GameObject.FindWithTag($"CropParent")?.transform;
EventHandler.CallAfterSceneLoadEvent();
}
public IEnumerator Fade(float targetAlpha)
{
Debug.Log($"开始淡化到 {targetAlpha}");
isFade = true;
fadeCanvasGroup.blocksRaycasts = true;
float speed = Mathf.Abs(fadeCanvasGroup.alpha - targetAlpha) / 1f;
while (!Mathf.Approximately(fadeCanvasGroup.alpha, targetAlpha))
{
fadeCanvasGroup.alpha = Mathf.MoveTowards(fadeCanvasGroup.alpha, targetAlpha, speed * Time.deltaTime);
yield return null;
}
// 如果目标 alpha 是 1,等待
if (targetAlpha == 1f)
{
yield return new WaitForSeconds(1f);
}
fadeCanvasGroup.blocksRaycasts = false;
isFade = false;
Debug.Log("淡化完成");
}
private IEnumerator LoadSaveDataScene(string sceneName)
{
yield return Fade(1f);
if (SceneManager.GetActiveScene().name != "PersistentScene")
{
EventHandler.CallBeforeSceneUnloadEvent();
yield return SceneManager.UnloadSceneAsync(SceneManager.GetActiveScene().buildIndex);
}
yield return LoadSceneSetActive(sceneName);
EventHandler.CallAfterSceneLoadEvent();
yield return Fade(0f);
}
/// <summary>
/// 玩家在小屋中睡着的事件
/// </summary>
/// <returns></returns>
private IEnumerator Sleep()
{
yield return Fade(1f);
EventHandler.CallEndOfDayEvent();
yield return Fade(0f);
EventHandler.CallSwitchDeviceMapEvent(ControlMap.Player);
}
public string GUID => GetComponent<DataGUID>().guid;
public GameSaveData GenerateSaveData()
{
GameSaveData saveData = new GameSaveData();
saveData.dataSceneName = SceneManager.GetActiveScene().name;
return saveData;
}
public void RestoreData(GameSaveData saveData)
{
// 加载游戏进度场景
StartCoroutine(LoadSaveDataScene(saveData.dataSceneName));
}
}
}
使用EventHandler实现的场景切换逻辑极大地提升了代码的模块化和扩展性。在最开始设置了初始场景(StartScene)很大程度地方便了之后的测试,可以直接跳到该场景运行,无需再从主场景切换进入。