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

news/2025/2/3 1:56:43 标签: 游戏, 游戏引擎, unity, ui

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 event
        EventHandler.CallBeforeSceneUnloadFadeOutEvent();

        // Start fading to block and wait for it to finish before continuing.
        yield return StartCoroutine(Fade(1f));  // 变黑色

        // Set player position
        Player.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 event
        EventHandler.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 event
        EventHandler.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 finish
        yield return StartCoroutine(LoadSceneAndSetActive(startingSceneName.ToString()));

        // If this event has any subscribers, call it
        EventHandler.CallAfterSceneLoadEvent();

        // Once the scene is finished loading, start fading in
        StartCoroutine(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 edges
        PolygonCollider2D 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 cache
        cinemachineConfiner.InvalidatePathCache();
    }
}

(6)优化Player.cs脚本

修改PlayerTestInput()方法。

 private void PlayerTestInput()
 {
     // Trigger Advance Time
     if (Input.GetKeyDown(KeyCode.T))
     {
         TimeManager.Instance.TestAdvanceGameMinute();
     }

     // Trigger Advance Day
     if (Input.GetKeyDown(KeyCode.G))
     {
         TimeManager.Instance.TestAdvanceGameDay();
     }

     // Test scene unload / load
     if (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.niftyadmin.cn/n/5840419.html

相关文章

Java小白入门教程:Object

目录 一、定义 二、作用 三、使用场景 四、语法以及示例 1、创建Object类型的对象 2、使用 toString()方法 3、使用 equals()方法 4、使用 hashCode()方法 5、使用 getClass()方法 6、使用 clone()方法 7、使用 finalize()方法 一、定义 在Java中&#xff0c; object…

No.7十六届蓝桥杯备战|单目操作符|getchar|putchar(C++)

单⽬操作符 前⾯介绍的操作符都是双⽬操作符&#xff0c;即有2个操作数。除此之外还有⼀些操作符只有⼀个操作数&#xff0c;被称为单⽬操作符。如 、–、(正)、-(负) 就是单⽬操作符。 和– 是⼀种⾃增的操作符&#xff0c;⼜分为前置和后置&#xff0c;–是⼀种⾃减的操作…

手撕Vision Transformer -- Day1 -- 基础原理

手撕Vision Transformer – Day1 – 基础原理 目录 手撕Vision Transformer -- Day1 -- 基础原理Vision Transformer (ViT) 模型原理1. Vit 网络结构图2. 背景3. 模型架构3.1 图像切块&#xff08;Patch Embedding&#xff09;3.2 添加位置编码&#xff08;Positional Encoding…

【LeetCode 刷题】回溯算法-组合问题

此博客为《代码随想录》二叉树章节的学习笔记&#xff0c;主要内容为回溯算法组合问题相关的题目解析。 文章目录 77. 组合216.组合总和III17.电话号码的字母组合39. 组合总和40. 组合总和 II 77. 组合 题目链接 class Solution:def combinationSum3(self, k: int, n: int) …

todo记事本案例

此案例较为简单&#xff0c;功能需求也比较单一&#xff0c;后续会使用node.jsmysql来进行更加复杂的需求开发的。 主要分为5部分 1、列表渲染 设置好一个数组&#xff0c;使用v-for数组遍历即可 <template> <!-- 主体区域 --> <section id"app&quo…

python-leetcode-二叉搜索树迭代器

173. 二叉搜索树迭代器 - 力扣&#xff08;LeetCode&#xff09; # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right right class BST…

图书管理系统 Axios 源码 __删除图书功能

目录 代码实现&#xff08;index.js&#xff09; 代码解析 使用方法 下面是完整的删除图书功能代码&#xff0c;基于 HTML Bootstrap JavaScript Axios 开发。 代码实现&#xff08;index.js&#xff09; // 删除图书功能 document.querySelector(.list).addEventListen…

Qt常用控件 输入类控件

文章目录 1.QLineEdit1.1 常用属性1.2 常用信号1.3 例子1&#xff0c;录入用户信息1.4 例子2&#xff0c;正则验证手机号1.5 例子3&#xff0c;验证输入的密码1.6 例子4&#xff0c;显示密码 2. QTextEdit2.1 常用属性2.2 常用信号2.3 例子1&#xff0c;获取输入框的内容2.4 例…