Unity3D仿星露谷物语开发26之创建场景控制管理器

ops/2025/2/6 1:01:51/

1、目标

创建场景控制管理器,来加载和卸载场景,以实现场景之间的切换。

2、思路

Fade To Back是黑色的过渡场景,透明度逐渐变为1。

Fade To Transparent To Show Scene:黑色消失的过渡场景,透明度逐渐变为0.

事件触发机制:

3、代码编写

(1)优化Enum.cs脚本

创建Scene的枚举类

public enum SceneName
{Scene1_Farm,Scene2_Field,Scene3_Cabin
}

(2)优化EventHandler.cs脚本

添加4个阶段的事件:

// Scene Load Events - in the order they happen
// Before Scene Unload Fade Out Event
public static event Action BeforeSceneUnloadFadeOutEvent;public static void CallBeforeSceneUnloadFadeOutEvent()
{if(BeforeSceneUnloadFadeOutEvent != null){BeforeSceneUnloadFadeOutEvent();}
}// Before Scene Unload Event
public static event Action BeforeSceneUnloadEvent;public static void CallBeforeSceneUnloadEvent()
{if( BeforeSceneUnloadEvent != null){BeforeSceneUnloadEvent();}
}// After Scene Loaded Event
public static event Action AfterSceneLoadEvent;public static void CallAfterSceneLoadEvent()
{if(AfterSceneLoadEvent != null){AfterSceneLoadEvent();}
}// After Scene Load Fade In Event
public static event Action AfterSceneLoadFadeInEvent;public static void CallAfterSceneLoadFadeInEvent()
{if(AfterSceneLoadFadeInEvent != null){AfterSceneLoadFadeInEvent();}
}

(3)创建SceneControllerManager.cs脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;public class SceneControllerManager : SingletonMonobehaviour<SceneControllerManager>
{private bool isFading;[SerializeField] private float fadeDuration = 1f;[SerializeField] private CanvasGroup faderCanvasGroup = null;[SerializeField] private Image faderImage = null;public SceneName startingSceneName;// This is the main external point of contact and influence from the rest of the project.// This will be called when the player wants to switch scenes.// sceneName:目标场景名称// spawnPosition: 主角出现的位置public void FadeAndLoadScene(string sceneName, Vector3 spawnPosition){// If a fade isn't happening then start fading and switching scenes.if (!isFading){StartCoroutine(FadeAndSwitchScenes(sceneName, spawnPosition));}}// This is the coroutine where the 'building blocks' of the script are put together.private IEnumerator FadeAndSwitchScenes(string sceneName, Vector3 spawnPosition){// Call before scene unload fade out eventEventHandler.CallBeforeSceneUnloadFadeOutEvent();// Start fading to block and wait for it to finish before continuing.yield return StartCoroutine(Fade(1f));  // 变黑色// Set player positionPlayer.Instance.gameObject.transform.position = spawnPosition;// Call before scene unload event.EventHandler.CallBeforeSceneUnloadEvent();// Unload the current active scene.yield return SceneManager.UnloadSceneAsync(SceneManager.GetActiveScene().buildIndex);// Start loading the given scene and wait for it to finish.yield return StartCoroutine(LoadSceneAndSetActive(sceneName));// Call after scene load eventEventHandler.CallAfterSceneLoadEvent();// Start fading back in and wait for it to finish before exiting the function.yield return StartCoroutine(Fade(0f)); // 变白色// Call after scene load fade in eventEventHandler.CallAfterSceneLoadFadeInEvent();}private IEnumerator Fade(float finalAlpha){// Set the fading flag to true so the FadeAndSwitchScenes coroutine won't be called again.isFading = true;// Make sure the CanvasGroup blocks raycasts into the scene so no more input can be accepted.faderCanvasGroup.blocksRaycasts = true;// Calculate how fast the CanvasGroup should fade based on it's current alpha,// it's final alpha and how long it has to change between the two.float fadeSpeed = Mathf.Abs(faderCanvasGroup.alpha - finalAlpha) / fadeDuration;// while the CanvasGroup hasn't reached the final alpha yet...while( !Mathf.Approximately(faderCanvasGroup.alpha, finalAlpha)){// ... move the alpha towards it's target alpha.faderCanvasGroup.alpha = Mathf.MoveTowards(faderCanvasGroup.alpha, finalAlpha,fadeSpeed * Time.deltaTime);// Wait for a frame then continue.yield return null;}// Set the flag to false since the fade has finished.isFading = false;// Stop the CanvasGroup from blocking raycasts so input is no longer ignored.faderCanvasGroup.blocksRaycasts = false;}private IEnumerator LoadSceneAndSetActive(string sceneName){// Allow the given scene to load over serval frames and add it to the already// loaded scenes (just the Persistent scene at this point).yield return SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);// Find the scene that was most recently loaded (the one at the last index of the loaded scenes).Scene newlyLoadedScene = SceneManager.GetSceneAt(SceneManager.sceneCount - 1);// Set the newly loaded scene as the active scene(this marks it as the one to be unloaded next).SceneManager.SetActiveScene(newlyLoadedScene);}private IEnumerator Start(){// Set the initial alpha to start off with a block screen.faderImage.color = new Color(0f, 0f, 0f, 1f);faderCanvasGroup.alpha = 1f;// Start the first scene loading and wait for it to finishyield return StartCoroutine(LoadSceneAndSetActive(startingSceneName.ToString()));// If this event has any subscribers, call itEventHandler.CallAfterSceneLoadEvent();// Once the scene is finished loading, start fading inStartCoroutine(Fade(0f));}}
  • CanvasGroup的alpha值,其子元素的alpha是子元素alpha和对应CanvasGroup中alpha的乘积。
  • 周期函数中的Start方法可以直接当成协程用,使用方法也很简单,直接将返回值void改成IEnumerator即可。Update,LateUpdate,FixedUpdate,Awake,OnEnable等都不能这么用

(4)优化UIInventorySlot.cs脚本

对于如下的代码:

parentItem = GameObject.FindGameObjectWithTag(Tags.ItemsParentTransform).transform;、

在切换场景的过程中,我们并不能总是找到他们。因为场景加载需要时间,可能场景还没加载完成就在取值了。

所以需要订阅加载完成的事件,在事件中处理元素的获取。

添加如下代码:

private void OnDisable()
{EventHandler.AfterSceneLoadEvent -= SceneLoaded;
}private void OnEnable()
{EventHandler.AfterSceneLoadEvent += SceneLoaded;
}public void SceneLoaded()
{parentItem = GameObject.FindGameObjectWithTag(Tags.ItemsParentTransform).transform;
}

同时注释掉Start()中的元素获取方法。

(5)优化SwitchConfineBoundingShape.cs脚本

元素获取基于同样的理由,需要在场景确认加载完毕后才能去获取。

using UnityEngine;
using Cinemachine;public class SwitchConfine : MonoBehaviour
{//private void Start()//{//    SwitchBoundingShape();//}private void OnEnable(){EventHandler.AfterSceneLoadEvent += SwitchBoundingShape;}private void OnDisable(){EventHandler.AfterSceneLoadEvent -= SwitchBoundingShape;}/// <summary>/// Switch the collider that cinemachine uses to define edges of the screen/// </summary>private void SwitchBoundingShape(){// Get the polygon collider on the 'boundsconfiner' gameobject which is used by Cinemachine to prevent the camera going beyond the screen edgesPolygonCollider2D polygonCollider2D = GameObject.FindGameObjectWithTag(Tags.BoundsConfiner).GetComponent<PolygonCollider2D>();CinemachineConfiner cinemachineConfiner = GetComponent<CinemachineConfiner>();  cinemachineConfiner.m_BoundingShape2D = polygonCollider2D;// since the confiner bounds have changed need to call this to clear the cachecinemachineConfiner.InvalidatePathCache();}
}

(6)优化Player.cs脚本

修改PlayerTestInput()方法。

 private void PlayerTestInput(){// Trigger Advance Timeif (Input.GetKeyDown(KeyCode.T)){TimeManager.Instance.TestAdvanceGameMinute();}// Trigger Advance Dayif (Input.GetKeyDown(KeyCode.G)){TimeManager.Instance.TestAdvanceGameDay();}// Test scene unload / loadif (Input.GetKeyDown(KeyCode.L)){SceneControllerManager.Instance.FadeAndLoadScene(SceneName.Scene1_Farm.ToString(), transform.position);}}

4、创建对象

 (1)FadeImage对象

在PersistentScene -> UI -> MainGameUICanvas -> UICanvasGroup下新建空对象命名为FadeImage。

作用:让屏幕变黑再变透明。

创建完之后,stretch让其铺满屏幕。

添加Image组件。

将颜色调整为黑色,并且将A置为0。

添加Canvas Group组件,并且勾选 Ignore Parent Groups选项,忽略父物体的管理。

(2)SceneControllerManager对象

在PersistentScene下新建空对象命名为SceneControllerManager。

添加SceneControllerManager组件,并且给元素赋值。

5、改变脚本的执行顺序

在多个脚本中,我们无法确定哪个脚本的Start()方法先被执行。

现在,在其他游戏对象开始访问第一个场景之前,我们需要确保SceneControllerManager对象已经在其Start()方法中加载了第一个场景。

Edit -> Project Settings -> Script Execution Order 

在Default Time之前保证SceneControllerManager的脚本被执行。

6、卸载场景

卸载已有的Scene1_Farm场景,

点击该场景后选择 Unload Scene。

因为SceneControllerManager的Start方法会加载当前场景一次。

执行程序后,按"L"键后效果如下:


http://www.ppmy.cn/ops/156012.html

相关文章

使用 Docker 部署 pSQL 服务器 的教程

如何使用 Edu 邮箱申请 Azure 订阅并开通免费 VPS 使用 Edu 邮箱不仅可以申请 Azure 的免费订阅来开通 VPS&#xff0c;还可以免费使用 Adobe 和 Notion 等软件&#xff0c;极大地提高学习和工作的效率。如果您还没有 Edu 邮箱&#xff0c;可以参考在线笔记s3.tebi.io/notes-i…

Linux ifstat 命令使用详解

简介 Linux 中的 ifstat 命令用于显示网络接口统计信息&#xff0c;显示系统中每个网络接口的网络流量信息&#xff08;如发送和接收的字节数或包数&#xff09;。它提供了一种实时监视网络接口活动的方法&#xff0c;帮助系统管理员和用户诊断与网络相关的问题。 安装 Debi…

Windows图形界面(GUI)-QT-C/C++ - QT MDI Area

公开视频 -> 链接点击跳转公开课程博客首页 -> ​​​链接点击跳转博客主页 目录 一、概述 二、使用场景 1. 多文档编辑器 2. 多窗口应用程序 3. 多视图应用程序 三、常见样式 1. 子窗口管理 2. 布局管理 四、属性设置 1. 添加子窗口 2. 移除子窗口 3. 设置…

ASP.NET Core Filter

目录 什么是Filter&#xff1f; Exception Filter 实现 注意 ActionFilter 注意 案例&#xff1a;自动启用事务的筛选器 事务的使用 TransactionScopeFilter的使用 什么是Filter&#xff1f; 切面编程机制&#xff0c;在ASP.NET Core特定的位置执行我们自定义的代码。…

Mac电脑上好用的免费截图软件

在Mac电脑上&#xff0c;有许多免费且功能强大的截图软件可供选择。以下是几款备受推荐的免费截图工具&#xff1a; iShot 功能&#xff1a;iShot是一款免费的截图工具&#xff0c;支持多种截图方式&#xff0c;包括长截图、延时截图、滚动截图、窗口截图、区域截图等。此外&am…

Python爬虫:1药城店铺爬虫(完整代码)

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ &#x1f434;作者&#xff1a;秋无之地 &#x1f434;简介&#xff1a;CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作&#xff0c;主要擅长领域有&#xff1a;爬虫、后端、大数据…

5. 【Vue实战--孢子记账--Web 版开发】-- 主页UI

我们在实现个人中心的时候简单的搭建了一个主页UI&#xff0c;但是这个主页并不是我们需要的&#xff0c;在这一节我们将一起实现主页UI的搭建。 一、功能 主页UI的原型如下: 首页UI原型包括左侧菜单和顶部header&#xff0c;左侧菜单包含多个功能模块的链接&#xff1a;首页…

框架与代码的形状

​ 作为一个代码的设计者&#xff0c;我之前讨论过代码的形状&#xff0c;从“名字”出发&#xff0c;进行讨论。代码的形状&#xff1a;重构的方向-CSDN博客 从比喻的角度来看&#xff0c;名字似代码的血和肉&#xff0c;而框架则似代码的骨架。 猎豹和大象 在大自然中&…