创建脚本

脚本要创建在除Editor文件夹以外的任意目录或子目录下

脚本模板

脚本模板的位置:C:\Program Files\Unity\Hub\Editor\2020.3.6f1c1\Editor\Data\Resources\ScriptTemplates(视Unity安装位置而定),前面的数字代表菜单栏的顺序

image-20220104105812115

拓展脚本模板

使用这种方法,在本地工程文件内添加自己的模板,而不是去修改Unity安装目录下的模板

在工程的Editor文件夹中,新建ScriptTemplates文件夹,放入C# Script-MyNewBehaviourScript.cs.txt文件,文件的内容如下

1
2
3
4
5
6
7
8
9
10
11
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class #NAME# : MonoBehaviour
{
void MyFunction()
{

}
}

模板文件编辑完毕后,在Windows情况下,请确认行尾序列为CRLF

然后在Editor文件夹中新建ExtendingScriptTemplates脚本

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
using UnityEngine;
using UnityEditor;
using UnityEditor.ProjectWindowCallback;
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

public class ExtendingScriptTemplates
{
//脚本模板所在的目录
private const string MY_SCRIPT_DEFAULT = "Assets/Editor/ScriptTemplates/C# Script-MyNewBehaviourScript.cs.txt";

[MenuItem("Assets/Create/C# MyScript",false,80)]
public static void CreatMyScript()
{
string locationPath = GetSelectedPathOrFallback();
ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, ScriptableObject.CreateInstance<MyDoCreateScriptAsset>(), locationPath + "/MyNewBehaviourScript.cs", null, MY_SCRIPT_DEFAULT);
}

public static string GetSelectedPathOrFallback()
{
string path = "Assets";
foreach (UnityEngine.Object obj in Selection.GetFiltered(typeof(UnityEngine.Object),SelectionMode.Assets))
{
path = AssetDatabase.GetAssetPath(obj);
if(!string.IsNullOrEmpty(path) && File.Exists(path))
{
path = Path.GetDirectoryName(path);
break;
}
}
return path;
}

}

class MyDoCreateScriptAsset : EndNameEditAction
{
public override void Action(int instanceId, string pathName, string resourceFile)
{
UnityEngine.Object o = CreateScriptAssetFromTemplate(pathName, resourceFile);
ProjectWindowUtil.ShowCreatedAsset(o);
}

internal static UnityEngine.Object CreateScriptAssetFromTemplate(string pathName,string resourceFile)
{
string fullPath = Path.GetFullPath(pathName);
StreamReader streamReader = new StreamReader(resourceFile);
string text = streamReader.ReadToEnd();
streamReader.Close();
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(pathName);
//替换文件名
text = Regex.Replace(text, "#NAME#", fileNameWithoutExtension);
bool encoderShouldEmitUTF8Identifier = true;
bool throwOnInvalidBytes = false;
UTF8Encoding encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier, throwOnInvalidBytes);
bool append = false;
StreamWriter streamWriter = new StreamWriter(fullPath,append,encoding);
streamWriter.Write(text);
streamWriter.Close();
AssetDatabase.ImportAsset(pathName);
return AssetDatabase.LoadAssetAtPath(pathName, typeof(UnityEngine.Object));
}
}

右键新建自己的脚本“C# MyScript”就会按照本地模板的内容生成脚本

image-20220105133135386

脚本生命周期

Unity - Manual: Order of execution for event functions (unity3d.com)

脚本绑定事件

使用Reset方法,挂载脚本时或者在脚本上面右键点击reset,就会执行此事件,reset方法仅仅在Editor模式中生效

在Scripts文件夹中新建TestReset脚本

1
2
3
4
5
6
7
8
9
10
11
using UnityEngine;

public class TestReset : MonoBehaviour
{
#if UNITY_EDITOR
private void Reset()
{
Debug.LogFormat("GameObject:{0}绑定TestReset脚本", gameObject.name);
}
#endif
}

image-20220105141521644

脚本初始化和销毁

脚本挂载在游戏对象上,运行时立即执行Awake方法,它是一个同步方法,下一帧执行Start方法。

当挂载脚本的对象被删除或者脚本被删除,都会执行OnDestroy方法

初始化和销毁在整个脚本生命周期中只会执行一次

脚本挂载后,如果代码中有涉及运行时的代码,其左侧就会出现勾选框,可以多次设置脚本的激活和禁用,系统分别回调OnEnableOnDisable方法

在Scripts文件夹中新建TestLifeCycle脚本

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
using UnityEngine;

public class TestLifeCycle : MonoBehaviour
{
private void Awake()
{
Debug.Log("Awake用于初始化并且永远只会执行一次");
}
private void OnEnable()
{
Debug.Log("OnEnable在脚本每激活执行一次");
}
private void Start()
{
Debug.Log("Start在初始化下一帧执行,并且永远只会执行一次");
}
private void OnDisable()
{
Debug.Log("OnDisable在脚本每次反激活后,执行一次");
}
private void OnDestroy()
{
Debug.Log("OnDestroy用于脚本反初始化并且永远只会执行一次");
}
private void OnApplicationQuit()
{
Debug.Log("应用程序退出时执行一次");
}
}

脚本更新与协程任务

  • Update每帧更新

  • LateUpdateUpdate之后更新

  • FixedUpdate固定更新,在Editor——Project Settings——Time选项中设置时间间隔

    在实际开发中,以秒为单位的倒计时,可以在FixedUpdate中实现

    在下面的代码中,使用协程来每秒生成一个游戏对象

    在Scripts文件夹中新建CreateCubePerSecond脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CreateCubePerSecond : MonoBehaviour
{
void Start()
{
StartCoroutine(CreateCube());
}

IEnumerator CreateCube()
{
for (int i = 0; i < 5; i++)
{
GameObject.CreatePrimitive(PrimitiveType.Cube).transform.position = Vector3.one * i;
yield return new WaitForSeconds(1f);
}
}
}

停止协程任务

如果一个协程没有结束,如果需要重启协程,必须先关闭此协程,否则会出现错误,使用StopCoroutine或者StopAllCoroutine来关闭一个或所有协程

StartCoroutine也可以返回协程对象

在Scripts文件夹中新建StopCoroutineOnGUI脚本

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class StopCoroutineOnGUI : MonoBehaviour
{
IEnumerator CreateCube()
{
for (int i = 0; i < 10; i++)
{
GameObject.CreatePrimitive(PrimitiveType.Cube).transform.position = Vector3.one * i;
yield return new WaitForSeconds(1f);
}
}
private Coroutine m_Cotoutine = null;
private void OnGUI()
{
if (GUILayout.Button("开始协程"))
{
if(m_Cotoutine != null)
{
StopCoroutine(m_Cotoutine);
}
m_Cotoutine = StartCoroutine(CreateCube());
}

if (GUILayout.Button("结束协程"))
{
if (m_Cotoutine != null)
{
StopCoroutine(m_Cotoutine);
}
}
}
}

使用OnGUI显示FPS

在Scripts文件夹中新建DisplayFPS脚本

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
using UnityEngine;

public class DisplayFPS : MonoBehaviour
{
public float updateInterval = 0.5f;//设置多久更新一次
private float accum = 0;
private int frames = 0;
private float timeleft;
private string stringFps;

private void Start()
{
timeleft = updateInterval;
}

private void Update()
{
timeleft -= Time.deltaTime;
accum += Time.timeScale / Time.deltaTime;
++frames;
if(timeleft <= 0.0)
{
float fps = accum / frames;
string format = System.String.Format("{0:F2} FPS", fps);
stringFps = format;
timeleft = updateInterval;
accum = 0.0f;
frames = 0;
}
}
private void OnGUI()
{
GUIStyle gUIStyle = GUIStyle.none;
gUIStyle.fontSize = 30;
gUIStyle.normal.textColor = Color.red;
gUIStyle.alignment = TextAnchor.UpperLeft;
Rect rt = new Rect(40, 0, 100, 100);
GUI.Label(rt, stringFps, gUIStyle);
}
}

通过以下代码强制帧数来降低功耗

1
Application.targetFrameRate = 30f;

多脚本管理

脚本的执行顺序

可以在Project Settings——Script Execution Order里面设置脚本的初始化顺序(Awake的顺序)

image-20220108141023876

如果A脚本执行后B脚本执行,需要在A脚本的Start方法获取B脚本数据,如果使用Awake方法的话会出错,因为B脚本可能还没有初始化

多脚本优化

避免挂太多的脚本,避免在脚本中写入void Startvoid Update等需要引擎接收但是没有执行内容的空方法