迷失岛2学习记录

实现场景切换

1、所需使用知识点

1.1单列模式

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问(方便全局访问),不需要实例化该类的对象。

优点:在内存里面只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类只关心内部逻辑情况,而不关心外面怎么样来实例化。

注意

1.单例类只能有一个实例;

2.单例类必须自己创建自己的唯一实例;

3.单例类必须给所有其它对象提供这一实例。

泛型单例模式模板,可以直接套用。

using UnityEngine;

public class MySingleton<T> : MonoBehaviour where T: MySingleton<T> 
{
    private static T instance;

    public static T GetInstance
    {
        get { return instance; }
    }
    protected virtual void Awake()
    {
        if(instance != null)
        {
            Destroy(gameObject);
        }
        else
        {
            instance = (T)this;
        }
    
    }
    //判断当前单例模式下的Manager是否已经初始化生成了
    public static bool IsInitialized
    {
        get { return instance != null; }//如果不为空,就是当前Manager已经生成了,返回True
    }
    //如果一个场景中有多个单例模式,需要将他销毁
    protected virtual void OnDestroy()
    {
        if(instance == this)
        {
            instance = null;
        }
    }
}

这样一来,场景中需要单例化的脚本,只需要简单继承这个类就可以了

public class Manager : MySingleton<Manager>
{
public string testText = "hello Sinngleton!";
}

那么我们就不用实例化对象来调用Manager中一些方法了:

public class SingletonTest : MonoBehaviour {
        
        void Start () {
                Debug.Log(Manager.GetInstance.testText);
        }
}

使用单例模式的情景:

在游戏中一定会有很多场景,比如每个场景都有背景音乐,我们就可以场景一个AudioManager脚本来管理所有音乐资源。如果想播放音乐,在其它脚本中我们只需要写AudioManager.GetInstance.PlayerSound();

如果不使用单例模式,在其它脚本中调用方法,我们需要先声明AudioManager,会加大运算时间。

1.2协程使用

协程在Unity中是一个很重要的概念,我们知道,在使用Unity进行游戏开发时,一般(注意是一般)不考虑多线程。

协程与线程区别?
  • 对于协程而言,同一时间只能执行一个协程,而线程它是并发的,可以同时有多个线程运行。
  • 两者在内存上使用是相同的,共享堆,不共享栈。
什么是协程?

协程协程字面意思就是帮助程序运行,在主任务运行的同时,需要一些分支任务来配合工作完成来达到最终效果。

稍微形象的解释一下,想象一下,在进行主任务的过程中我们需要一个对资源消耗极大的操作时候,如果在一帧中实现这样的操作,游戏就会变得十分卡顿,这个时候,我们就可以通过协程,在一定帧内完成该工作的处理,同时不影响主任务的进行。

协程原理

协程不是线程,但是协程仍然在主线程中运行。

协程是通过迭代器来实现功能的,通过关键字IEnumerator来定义一个迭代方法,注意使用的是IEnumerator,而不是IEnumerable

两者之间的区别:

  • IEnumerator:是一个实现迭代器功能的接口
  • IEnumerable:是在IEnumerator基础上的一个封装接口,有一个GetEnumerator()方法返回IEnumerator

在迭代器中呢,最关键的是yield的使用,这是我们实现协程的主要途径。通过该关键方法,可以实现协程的运行暂停,记录下一次运行的时间和位置等。

协程在代码中运用

首先通过迭代器关键字IEnumerator来定义一个方法,然后在对应的程序中通过StartCoroutine来开启协程方法。

在正式代码之前先了解StartCoroutine的重载方式:

  • StartCoroutine(协程的方法名):这种是没有参数的情况,直接通过方法名(字符串形式)来开启协程-
  • StartCoroutine(IEnumerator routine):通过方法形式调用
  • StartCoroutine(协程的方法名,参数):带参数的通过方法名进行调用

协程开启示例代码:

 	//通过迭代器定义一个方法
 	IEnumerator Demo(int i)
    {
        //代码块
        yield return 0; 
		//代码块   
    }
    //在程序种调用协程
    public void Test()
    {
        //第一种与第二种调用方式,通过方法名与参数调用
        StartCoroutine("Demo", 1);

        //第三种调用方式, 通过调用方法直接调用
        StartCoroutine(Demo(1));
    }

协程有开启的方法,同样有协程结束的方法。StopCoroutine与StopAllCoroutines两种方式,但是需要注意的是,两者的使用需要遵循一定的规则,在介绍规则之前,同样介绍一下关于StopCoroutine重载:

  • StopCoroutine(string methodName):通过方法名(字符串)来进行
  • StopCoroutine(IEnumerator routine):通过方法形式来调用
  • StopCoroutine(Coroutine routine):通过指定的协程来关闭

刚刚我们说到他们的使用是有一定的规则的,那么规则是什么呢,答案是前两种结束协程方法的使用上,如果我们是使用StartCoroutine(string methodName)来开启一个协程的,那么结束协程就只能使用StopCoroutin(string methodName)和StopCoroutine(Coroutine routine)来结束协程。

关于yield

yield在Unity生命周期中的一些执行方法,不同的yield方法处于生命周期不同的位置。

在这里插入图片描述

由图可以看出yield的一些方法存在于Update和LateUpdate之间。

首先解释一下位于Update与LateUpdate之间这些yield 的含义:

yield return null; 暂停协程等待下一帧继续执行

yield return 0或其他数字; 暂停协程等待下一帧继续执行

yield return new WairForSeconds(时间); 等待规定时间后继续执行

yield return StartCoroutine(“协程方法名”);开启一个协程(嵌套协程)

通过代码来理解yield一些方法执行的顺序:

 void Update()
    {
        Debug.Log("第一次");
        StartCoroutine(myyield());

    }
    private void LateUpdate()
    {
        Debug.Log("第四次");
    }
    private IEnumerator myyield()
    {
        Debug.Log("第二次");
        yield return 0;
        Debug.Log("已经在下一帧执行");
    }

运行结果:在这里插入图片描述

可以很清晰的看出,协程虽然是在Update中开启,但是关于yield return null后面的代码会在下一帧运行,并且是在Update执行完之后才开始执行,但是会在LateUpdate之前执行。

2、代码逻辑思路

2.1新建一个Teleport类

新建一个teleportToscene方法

public class Teleport : MonoBehaviour
{
    [SceneName]public string sceneForm;
    [SceneName]public string sceneTo;

    public void teleportToscene()
    {
        TransitonManager.GetInstance.Transiton(sceneForm, sceneTo);
    }
}

2.2具体场景转换方法

而实现场景转换的具体方法我们设置为一个单例类,以便后续我们直接访问调用。

public class TransitonManager : MySingleton<TransitonManager>
{
    private bool IsFade;
    public CanvasGroup FadeCanvasGroup;
    public float fadeDuration;
    public void Transiton(string from,string to)
    {
        StartCoroutine(TransitionToScene(from, to));
    }

注:通过自己写的单例模板,MySingleton<自己的类名>就能直接变成单列类。

同时引入切换场景的命名空间:

using UnityEngine.SceneManagement;

2.3协程的运用

   //协程
    private IEnumerator TransitionToScene(string from,string to)
    {
        yield return Fade(1);
        yield return SceneManager.UnloadSceneAsync(from);
        yield return SceneManager.LoadSceneAsync(to, LoadSceneMode.Additive);
        //设置新场景为激活场景
        Scene newScene = SceneManager.GetSceneAt(SceneManager.sceneCount - 1);
        SceneManager.SetActiveScene(newScene);

        yield return Fade(0);
    }

关于场景的API官方文档:SceneManagement.SceneManager - Unity 脚本 API

2.4实现场景切换的动态遮罩

通过控制画布的Alpha值来淡入淡出整个窗口

    private IEnumerator Fade(float targetAlpha)
    {
        IsFade = true;
        FadeCanvasGroup.blocksRaycasts = true;
        float speed = Mathf.Abs(FadeCanvasGroup.alpha - targetAlpha) / fadeDuration;
        while (!Mathf.Approximately(FadeCanvasGroup.alpha, targetAlpha)){
            FadeCanvasGroup.alpha = Mathf.MoveTowards(FadeCanvasGroup.alpha, targetAlpha, speed * Time.deltaTime);
            yield return null;
        }
        FadeCanvasGroup.blocksRaycasts = false;
        IsFade = false;
    }

在切换场景的协程方法中再引用这这个方法 yield return Fade(1):实现黑色遮罩的淡入;yield return Fade(0):实现黑色遮罩的淡出。

本次数学函数官方文档:UnityEngine.Mathf - Unity 脚本 API

2.5实现鼠标点击切换

创建一个鼠标点击类CursorManager类,因为此游戏主要是鼠标点击交互,要判断点击的游戏对象就专门写一个类来实现。

public class CursorManager: MonoBehaviour
{
    private Vector3 mouseWorldPos => Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0));
    private bool canClick;
    private void Update()
    {
        canClick = ObjectAtMousePosition();
        if(canClick && Input.GetMouseButtonDown(0))
        {
            //检测鼠标互动
            clickAction(ObjectAtMousePosition().gameObject);
        }
        Debug.Log(canClick);
    }
    private void clickAction(GameObject clickObject)
    {
        switch (clickObject.tag)
        {
            case "Teleport":
                var teleport = clickObject.GetComponent<Teleport>();
                teleport?.teleportToscene();
                break;
        }
    }
    private Collider2D ObjectAtMousePosition()
    {
        //返回一个与鼠标坐标点重叠的碰撞体
        return Physics2D.OverlapPoint(mouseWorldPos);
    }
}
  • 给被点击物体挂载上Collider2D组件,并且勾选上Is Trigger;
  • 将鼠标的屏幕坐标转换成游戏世界坐标,并创建一个返回Collider2D的方法,OverlapPoint返回一个与鼠标坐标重合的碰撞体;
  • 给不同碰撞体类划分不同Tag标签,在clickAction方法中检测点击的物体以此来调用不同的方法;

声明:本文档只用于本人学习记录,文中若出现错误望各位指正。更多详情关注B站麦扣老师:《迷失岛2》游戏框架开发00:项目介绍|Unity教程_哔哩哔哩_bilibili

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值