主要用到协程(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

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

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

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

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

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

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

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

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 协程运行时的监控和优化

Unity中巧用协程和游戏对象的生命周期处理游戏重启的问题的更多相关文章

  1. Unity脚本编程之——协程(Coroutine)

    本文翻译自Unity官方文档:https://docs.unity3d.com/Manual/Coroutines.html 专有名词: Coroutine 协程 Alpha 不透明度 当你调用一个函 ...

  2. Unity之"诡异"的协程

    为什么说是诡异的协程呢?首先从一个案例说起吧,示例如下: 游戏目标:让小车进入到对应颜色屋子里,即可获得一分.(转弯的道路可控)   为了让小车能够平滑转弯,小车的前进方向需要和车子的位置与圆心组成的 ...

  3. Unity 3D Framework Designing(3)——构建View和ViewModel的生命周期

    > 对于一个View而言,本质上是一个MonoBehaviour.它本身就具备生命周期这个概念,比如,Awake,Start,Update,OnDestory等.这些是非常好的方法,可以让开发者 ...

  4. Hibernate中Java对象的生命周期

    一个对象的出生源于我们的一个new操作,当我们使用new语句创建一个对象,这个对象的生命周期就开始了,当我们不在有任何引用变量引用它,这个对象就的生命就此结束,它占用的内存就可以被JVM的垃圾回收器回 ...

  5. java之hibernate之session中对象的生命周期

    1. session是用来执行对象的crud操作,并且session是对象事务工厂.session是线程级别的,所以生命周期比较短. 2.session中对象的生命周期图: 3.session中对象的 ...

  6. yield学习续:yield return迭代块在Unity3D中的应用——协程

    必读好文推荐: Unity协程(Coroutine)原理深入剖析 Unity协程(Coroutine)原理深入剖析再续 上面的文章说得太透彻,所以这里就记一下自己的学习笔记了. 首先要说明的是,协程并 ...

  7. python中线程 进程 协程

    多线程:#线程的并发是利用cpu上下文的切换(是并发,不是并行)#多线程执行的顺序是无序的#多线程共享全局变量#线程是继承在进程里的,没有进程就没有线程#GIL全局解释器锁#只要在进行耗时的IO操作的 ...

  8. 038_go语言中的状态协程

    代码演示: package main import ( "fmt" "math/rand" "sync/atomic" "time ...

  9. Android中的Coroutine协程原理详解

    前言 协程是一个并发方案.也是一种思想. 传统意义上的协程是单线程的,面对io密集型任务他的内存消耗更少,进而效率高.但是面对计算密集型的任务不如多线程并行运算效率高. 不同的语言对于协程都有不同的实 ...

随机推荐

  1. centos7忘记登录密码修改

    很多时候我们都会忘记Linux root 用户的口令,下面就教大家如果忘记root口令怎么办 第1步:开机后在内核上按“e”.截图如下 按e以后会进入内核启动页面,如下图 第2步:在linux16这行 ...

  2. Python-HTML 最强标签分类

    编程: 使用(展示)数据 存储数据 处理数据 前端 1. 前端是做什么的? 2. 我们为什么要学前端? 3. 前端都有哪些内容? 1. HTML 2. CSS 3. JavaScript 4.jQue ...

  3. 使用navicat premium将数据库从Oracle迁移到SQL Server,或从Oracle迁移到MySQL

    有时候我们有迁移数据库的需求,例如从Oracle迁移到SQL Server,或者从MySQL迁移到Oracle. 很多江湖好汉一时不知如何手工操作,所幸的是Navicat提供了迁移的自动化操作界面. ...

  4. how to detect circles and rectangle?

    opencv中对圆检测的函数为:HoughCircles(src_gray,circles,CV_HOUGHT_GRADIENT,1,src_gray.cols/8,200,100,0,0) circ ...

  5. 前端工程化-webpack-cli(官方开发中。。。)

  6. Windows Mac地址伪装步骤

    本文介绍Windows上Mac地址修改方法,适用于网络环境绑定了Mac地址需要修改上网的情况. 工具/原料 PC电脑一台 Windows系统 方法/步骤 点击右下角图标. 点击打开网络和共享中心. 点 ...

  7. LeetCode(7):颠倒整数

    Easy! 题目描述:给定一个范围为 32 位 int 的整数,将其颠倒. 例1: 输入:132 输出:321 例2: 输入:-123 输出:-321 例3: 输入:120 输出:21 注意:假设我们 ...

  8. hdu1556 树状数组区间更新单点查询板子

    就是裸的区间更新: 相对于直观的线段树的区间更新,树状数组的区间更新原理不太相同:由于数组中的一个结点控制的是一块区间,当遇到更新[l,r]时,先将所有能控制到 l 的结点给更新了,这样一来就是一下子 ...

  9. 性能测试十九:jmeter参数优化+排错

    一:参数优化 1,控制台取样间隔的设置,在jmeter/bin/jmeter.properties文件中修改 summariser.interval=10,默认为30s,最低可修改为6s 2,Jvm参 ...

  10. this和判断的位置对赋值的要求

    package charter01; public class Person { private String name; private int age; public String getName ...