• Unity中巧用协程和游戏对象的生命周期处理游戏重启的问题


    主要用到协程(Coroutines)和游戏对象的生命周期(GameObject Lifecycle)基础知识,巧妙解决了游戏重启的问题。

    关于协程,这里有篇文章我觉得写的非常好,理解起来也很容易。推荐先看这篇文章:对Unity中Coroutines的理解>>

    协程简单来看分三部分:

    1)启动,常用方法:StartCoroutine(IEnumerator routine) | StartCoroutine(string methodName)

    2)执行,执行的函数其返回值必须为IEnumerator(迭代器)

    3)停止,常用方法:StopCoroutine(string methodName) | StopCoroutine(IEnumerator routine) | StopCoroutine(Coroutine routine) | StopAllCoroutines

    启动、停止基本上都是对应的方法,有Start就有Stop(StopAllCoroutines比较特殊一点,没有对应的Start)。

    那么重点就在执行的函数上,只要用到协程,就不得不提关键词 yield,先看它的语法:

    yield return <expression>;  
    yield break;

    (来源:https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/yield)

    怎么理解它呢?以yield return null;为例,当执行了协程函数时,遇到yield return <expression>后,本帧就暂停执行后面的语句,然后下一帧再check表达式的返回值,如果为return则继续执行,如果不为return则到下一帧继续检测 —— 这里的前提是下一帧会执行,如果对象被销毁了,下一帧就不执行了,上面说法也就不成立了。

    来验证一下上面的说法:

    1、如果真的是一帧执行一次,那我就设定Unity的帧率为1(即1秒只执行一次Update);

    2、在Start()函数之后,启动一个协程,里面跑一个死循环 while (true),然后输出时间,从控制台上观察其输出;

    设定帧率:

    1、关闭系统的帧率设定,菜单 Edit –> Project Settings –> Quality

    image

    在场景的主相机上挂一个脚本,在 Start()函数中修改帧率

    // 修改当前的FPS
    Application.targetFrameRate = 1;

    上面的代码也可以放在场景加载之前的函数里,比如:OnBeforeSceneLoadRuntimeMethod

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
        static void OnBeforeSceneLoadRuntimeMethod()
        {
             // todo…        
        }

    2、随便找一个GameObject,在其挂载脚本的Start()函数中启动协程

    image

    运行之后,在控制台上可以看到如下的输出,是符合预期的。

    image

    注:前三行的输出先忽略,下面会进行说明,关键看后面的输出。

    Unity内置了一些yield指令(YieldInstruction):

    -)WaitForSeconds  

    等待指定的游戏时间(游戏流逝时间可用Time.scale调整)

    -)WaitForSecondsRealtime

    等待指定的真实时间(现实时间不受Time.scale影响)

    -)WaitForFixedUpdate

    等待下一次的FixedUpdate后再执行

    -)WaitForEndOfFrame

    等待这一帧(Frame)绘制完但还没有显示的时间点再执行,可以用来取得绘制结果做一些事情

    -)WaitUntil

    待到传入的 delegate 满足条件返回 true 后再执行

    -)WaitWhile

    跟WaitUntil差不多,只是传入的 delegate 满足条件返回 false 后再执行,与WaitUntil返回值正好相反

    开发游戏,通常我们都需要支持热更新,而腾讯开源的XLua无疑是目前最好的一个选择。既然是热更新,游戏重启我们肯定不希望像安卓那样暴力:

    1、先杀掉进程;

    2、再重启启动进程;

    不是不可以,只是在iOS环境下这个方案就行不通了。那怎么办呢?最好当然是游戏自己实现,像启动App进程那样,先走销毁(Destroy)流程再走初始化流程(Init)。

    因为C#的脚本通常都会挂载到一个场景(Scene)或者一个游戏对象(GameObject)上,而重启就势必会销毁场景,这样脚本里的函数在Destroy就不会再被执行了。这就需要一个独立的一个C#脚本,它独立于游戏的加载、销毁之外,我第一次看到这个方案时有点惊讶,绝对是奇技淫巧。

    我在示例中使用的是点击按钮时,调用函数的重启方法(Restart)

    Restart方法只干了一件事,new一个GameObject对象出来,当这个GameObject被实例化时,该类的Start函数会被调用,然后在Start函数中执行:销毁 –> 间隔一段时间(用上面的yield return null或者Unity提供的延时处理指令) –> 初始化 –> 销毁 new出来的GameObject对象。

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class Startup : MonoBehaviour {
    
        static GameObject m_GameObject;
    
        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
        static void OnBeforeSceneLoadRuntimeMethod()
        {
            // 在场景加载之前,可以做一些与游戏无关的一些初始化工作:Lua虚拟机的启动、SDK初始化、Bugly的初始化、资源管理的初始化...
    
            Debug.Log("Startup....... Before first scene loaded");
        }
    
    
        // Use this for initialization
        private IEnumerator Start()
        {
            Debug.Log("Start ----------------");
    
            // 加载一个空的场景,设置其主相机的Clear Flag为Don't Clear,然后停一帧
            yield return null;
    
            // 这里可以调用销毁的相关函数,如:关闭Lua虚拟机、相关资源的释放等,然后再停一帧
    
            yield return null;        
    
            // 重新调用初始化方法
            OnBeforeSceneLoadRuntimeMethod();
    
            // 加载游戏的主场景
    
            Destroy(m_GameObject);
            m_GameObject = null;
        }
    
    
        public static void Restart()
        {
            if (m_GameObject == null)
            {
                m_GameObject = new GameObject("Startup", typeof(Startup));
                DontDestroyOnLoad(m_GameObject);
            }
        }
    
    }

    参考资料:

    [1] Unity Coroutine 使用筆記

    [2] 对Unity中Coroutines的理解

    [3] Unity 协程运行时的监控和优化

  • 相关阅读:
    575. Distribute Candies
    242. Valid Anagram
    349. Intersection of Two Arrays
    290. Word Pattern
    389. Find the Difference
    httpModules 与 httpHandlers
    JS获取浏览器信息及屏幕分辨率
    asp.net 页面编码 设置的几种方法
    IIS是如何处理ASP.NET请求的
    VS2010常用插件介绍之Javascript插件(一)
  • 原文地址:https://www.cnblogs.com/meteoric_cry/p/7373673.html
Copyright © 2020-2023  润新知