个人技术博客——基于C# EventHandler的游戏场景切换

222200104邓彦茜 2024-12-20 22:17:09

目录

  • 技术概括
  • 技术详述
  • 总结
  • 参考文献

技术概括

  • 在unity游戏开发中实现游戏场景切换中,使用了C#事件调用的方法。

  • 同时场景切换的另一重点在于切换场景时物品数据的保存与删除,在这里使用的方法是:

  • 设置一个PersistentScene场景作为一直存在不会被卸载的场景,游戏会一直使用的部分都保存在该场景,其他剩余的场景在此基础上进行加载和卸载。

  • 关于为什么要使用这个方法,原因在于:进行一次场景切换需要进行许多动作,EventHandler 提供了一种解耦的方式,使得代码结构更清晰、更易于维护。

    技术详述

  • 如下,在运行前除了PersistentScene以外的其他场景要卸载掉,负责会出现两个场景重叠的情况(一开始那个没有被卸载的场景会一直存在)

    在这里插入图片描述

  • 在PersistentScene中加入SeceneManeger场景切换脚本,跟随PersistentScene一起在游戏最初加载出来。SeceneManeger场景切换脚本里的主要内容如下:

  1. 场景切换事件监听:通过 OnEnable 和 OnDisable 订阅和取消订阅 TransitionEvent,避免重复订阅或内存泄漏。
private void OnEnable()
{
    EventHandler.TransitionEvent += OnTransitionEvent;
}

private void OnDisable()
{
    EventHandler.TransitionEvent -= OnTransitionEvent;
}
  1. 触发场景切换,当事件触发时,调用 Transition 方法实现场景切换逻辑
private void OnTransitionEvent(string sceneToGo, Vector3 positionToGo)
{
    if (!isFade)
    {
        StartCoroutine(Transition(sceneToGo, positionToGo));
        Debug.Log("触发场景切换事件");
    }
}
  • Transition方法的具体实现如下,其中包括了一些效果的实现,比如在切换中实现黑屏效果
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); // 场景效果的淡出
}
  1. 场景加载的方法实现,加载场景并设置为活动场景
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)很大程度地方便了之后的测试,可以直接跳到该场景运行,无需再从主场景切换进入。

参考文献

C#事件,如何在子类(派生类)中调用父类定义的事件
b站教程

...全文
78 回复 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

239

社区成员

发帖
与我相关
我的任务
社区管理员
  • FZU_SE_teacherW
  • 助教赖晋松
  • D's Honey
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧