单例脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 文件名:SingleScript.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 单例脚本
public class SingleScript : MonoBehaviour
{
public static SingleScript instance;

// 静态构造函数
static SingleScript()
{
GameObject go = new GameObject("#Global#");
DontDestroyOnLoad(go);
instance = go.AddComponent<SingleScript>();
Debug.Log("static construct function has finished");
}

// Start is called before the first frame update
void Start()
{
Debug.Log("Global script start");
}

void Awake()
{
Debug.Log("Global script Awake");
}

public void Dosomething()
{
Debug.Log("Global script do something");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 文件名:SingleTestMain.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SingleTestMain : MonoBehaviour
{
private void OnGUI()
{
if (GUILayout.Button("执行单例脚本中的方法"))
{
SingleScript.instance.Dosomething();
}
}
}

运行过程及说明:
SingleTestMain.cs 需要挂载在物体身上,因为要用它来模拟调用 Global 脚本中的方法。SingleScript.cs 不需要在编辑器模式下就挂载在物体身上,但是依然需要继承自 Monobehaviour,因为在点击 “执行单例脚本中的方法” 的 Button 时,会在场景中创建一个 “#Global#” 的 GameObject,并添加一个 SingleScript 的组件,并将 instance 指向该组件。输出如下:

  • Global script Awake
  • static construct function has finished
  • Global script do something
  • Global script start

可以看到,在没有点 Button 之前是没有任何输出的,并且官网上也提到 静态构造函数 的调用时机不受我们控制。暂时只需要记住静态构造函数用于初始化任何静态数据,或执行仅需执行一次的特定操作。 将在创建第一个实例或引用任何静态成员之前自动调用静态构造函数。
还需要注意一下 4 个函数的执行顺序,Awake 是在 go.AddComponent<SingleScript>() 时执行的,然后静态构造函数执行完,再执行成员方法,最后才是 Start 方法。

定时器

UnityAction

public delegate void UnityAction();
类似于 c# 中的 Action ,是一个没有返回值,没有参数的委托。

协程

注意:禁用 MonoBehaviour 时,不会停止协程,仅在明确销毁 MonoBehaviour 时才会停止协程。可以使用 MonoBehaviour.StopCoroutine 和 MonoBehaviour.StopAllCoroutines 来停止协程。销毁 MonoBehaviour 时,也会停止协程。

定时器实现

定时器可以使用协程来完成,但是这样就必须依赖于脚本,但是我们可以使用一个继承自 MonoBehaviour 的空的内部类来封装一个不依赖于脚本的定时器。
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 文件名: Script_04_18.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events; // UnityAction

// 利用继承自 Monobehaviour 的内部类做一个不依赖于脚本的定时器
// 类名可以换成 WaitTimeManager 之类的
public class Script_04_18
{
private static TaskBehaviour m_task;

// 用编写单例脚本的方法完成新建一个全局的 WaitTimeManager,他是一个物体,但主要功能是在其他代码中方便的使用定时器。
static Script_04_18()
{
GameObject go = new GameObject("#WaitTimeManager#");
GameObject.DontDestroyOnLoad(go);
TaskBehaviour t = go.AddComponent<TaskBehaviour>();
m_task = t;
}

// 供外部调用的开启定时器任务函数
public static Coroutine WaitTime(float time,UnityAction callback)
{
return m_task.StartCoroutine(Coroutine(time, callback));
}

// 供外部调用的停止定时器任务的函数,这里使用 ref 是因为函数内需要将传过来的协程置空
public static void StopCoroutine(ref Coroutine coroutine)
{
if (coroutine != null)
{
m_task.StopCoroutine(coroutine);
coroutine = null;
}
}

private static IEnumerator Coroutine(float time,UnityAction callback)
{
yield return new WaitForSeconds(time);
if(callback != null)
{
callback();
}
}

// 工具人哈哈哈
class TaskBehaviour:MonoBehaviour
{}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 文件名:Script_04_18_main.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public class Script_04_18_main : MonoBehaviour
{
private Coroutine coroutine;
private void OnGUI()
{
if (GUILayout.Button("开启定时任务"))
{
Debug.Log("10 秒之后打印一段 Rap");

// 第二个参数是 UnityAction(委托) ,但是可以直接传递函数
coroutine = Script_04_18.WaitTime(10, PrintRap);
}

if (GUILayout.Button("停止定时任务"))
{
Script_04_18.StopCoroutine(ref coroutine);
}
}

static void PrintRap()
{
Debug.Log("一段 Rap");
}
}

运行效果:
在点击了 “开启定时任务” 十秒之后,会打印出

  • 10 秒之后打印一段 Rap
  • 一段 Rap

如果在十秒之前,点击了 “停止定时任务”,那么不会有 “一段 Rap” 的输出