文章目录
- 前言
- 一、脚本模版及其拓展
- 1、脚本模版
- 2、拓展脚本模版
- 二、脚本的生命周期
- 三、脚本的执行顺序
- 四、脚本序列化
- 1、序列化数据
- 2、serializedObject
- 3、监听部分元素修改事件
- 五、定时器与间隔定时器
- 六、工作线程(多线程)
- 总结
前言
脚本在Unity的重要程度不用多说,她是大部分软件的核心组件。
我们将在此篇文章学习脚本模版及其拓展、脚本的生命周期、脚本的执行顺序、脚本序列化,下一篇为脚本编译与调试。
一、脚本模版及其拓展
1、脚本模版
如下图我们可以在Project视图右键进行脚本创建,除了C#脚本,还有两类脚本;Testing用来做单元测试,Playables是TimeLine引入的新概念。
通常来讲我们会根据项目修改脚本模版,位置在Unity安装位置的Resource文件夹内,例如:“C:\Program Files\Unity 2021.3.16f1\Editor\Data\Resources\ScriptTemplates”。除了按照项目修改模版,通常会将脚本编码改成UTF-8并在脚本模版中加入中文或中文字符。
2、拓展脚本模版
如果按上述方法修改脚本模版会十分麻烦,不说每个版本的unity都需要修改,项目里每个成员都需要修改,当模版变动难道每个人都要重新修改吗。我们想做到版本管理可以拓展脚本的方式添加模版。
二、脚本的生命周期
Unity脚本有一套完整的生命周期,无需手动调用。其中包含初始化、更新回调、渲染、销毁,我们经常使用的协程等也在其内。
官方解释:
书中解释:
三、脚本的执行顺序
在上一章中当有两个脚本标记了"[CustomEditor(typeof(Camera))]",只会执行第一个创建的脚本逻辑。
为什么呢?Unity的脚本可以预先挂载场景中,也可以在运行时动态添加,所以用Script Execution Order使用来管理脚本的执行顺序,我们可以在此添加脚本并设置顺序。
我们考虑一件事,现在有多个脚本,A脚本在Awake获取B脚本的数据,A如果比B先执行那就会报错。实际上这种时序的问题还会发生在项目越发复杂,初始化过多的情况。不仅如此,多个脚本还会产生多余消耗,假设每个脚本都有初始化(Awake、Start等)和更新(Update)的方法,几百个脚本同时运行,性能差的主机可能分分钟卡死。
解决方法:将系统分类,每个系统的初始化等功能都设置入口,功能统一调用。初学者推荐尝试单例的写法来管理小项目,再根据项目尝试工厂模式、适配器模式等设计模式,最后学习框架的思想。
四、脚本序列化
想了解脚本序列化,首先我们需要明白脚本本身并没有保存数据,而是将数据保存于文件中,也就是脚本可以通过序列化和反序列化来保存游戏。
书中举例,给场景其中一个物体加上脚本Test.cs,再找一个预制体Prefab.prefab加上脚本Test.cs。接下来我们修改场景中的脚本数据,也就是Id,那么数据将保存在Scene.unity;同理,在Project下修改预制体上的脚本Id,那么数据会保存在预制体文件夹内。
如果把预制体赋值到场景的脚本中(脚本中的public GameObject prefab;),那么数据会存在Scene.unity。大家仔细想想,平常雀氏是这样操作的,只是不明所以罢了。
public class Test : MonoBehaviour
{
public int Id;
public GameObject prefab;
}
1、序列化数据
打开上面的预制体和场景,就会发现里面存着序列化的数据,这里不做展示。
通常,公开的对象会支持序列化格式,如果不想显示可以加上[HideInInspector],私有序列化需要再代码上加上[SerializeField]。
public class Test : MonoBehaviour
{
[SerializeField]
private int Id;
[SerializeField]
private GameObject prefab;
}
假如我此时还想访问呢,直接请出serializedObject.FindProperty()访问私有属性。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class Script_04_08 : MonoBehaviour
{
[SerializeField]
private int id;
[SerializeField]
private string name;
[SerializeField]
private GameObject prefab;
}
#if UNITY_EDITOR
[CustomEditor(typeof(Script_04_08))]
public class ScriptInsector : Editor
{
public override void OnInspectorGUI()
{
//更新最新数据
serializedObject.Update();
//获取数据信息
SerializedProperty property = serializedObject.FindProperty("id");
//赋值数据
property.intValue = EditorGUILayout.IntField("主键", property.intValue);
property = serializedObject.FindProperty("prefab");
property.objectReferenceValue = EditorGUILayout.ObjectField("游戏对象",
property.objectReferenceValue,typeof(GameObject),true);
//全部保存数据
serializedObject.ApplyModifiedProperties();
}
}
#endif
2、serializedObject
serializedObject只能在Editor中使用,它专门用于获取设置的序列化信息。通常,要开发复杂的编辑组件,都需要重写OnInspectorGUI()方法,但是如果希望有些用源生的绘制结构,同时兼容有些自定义渲染的话,那该怎么办呢?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class SerializedObjectTest : MonoBehaviour
{
[SerializeField]
private int id;
[SerializeField]
private GameObject[] targets;
}
#if UNITY_EDITOR
[CustomEditor(typeof(SerializedObjectTest))]
public class ScriptInsector : Editor
{
public override void OnInspectorGUI()
{
//更新最新数据
serializedObject.Update();
//获取数据信息
SerializedProperty property = serializedObject.FindProperty("id");
//赋值数据
property.intValue = EditorGUILayout.IntField("主键", property.intValue);
//以默认样式绘制数组数据
EditorGUILayout.PropertyField(serializedObject.FindProperty("targets"), true);
//全部保存数据
serializedObject.ApplyModifiedProperties();
}
}
#endif
效果如下:
3、监听部分元素修改事件
脚本面板中,参与绘制的元素都是在OninspectorGUI中绘制的。我们想监听其中的一个元素有两种方法,第一种适用于简单的脚本,在脚本中写入Onvalidate(),当面板信息发生改变就会回调该方法;
void Onvalidate(){
Debug.log("信息改变");
}
第二种需要监听GUI的元素写在EditorGUI.BeginChangeCheck()。我们监听变化则需要在if(EditorGUI.EndChangeCheck()) 中处理。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class Test : MonoBehaviour
{
[SerializeField]
private GameObject[] targets;
}
#if UNITY_EDITOR
[CustomEditor(typeof(Test))]
public class ScriptInsector : Editor
{
public override void OnInspectorGUI()
{
//更新最新数据
serializedObject.Update();
//标记检查
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(serializedObject.FindProperty("targets"), true);
//标记检查发生变化
if(EditorGUI.EndChangeCheck()) {
Debug.Log("元素发生变化");
}
//判断面板元素是否任意发生改变
if(GUI.changed) {
}
//全部保存数据
serializedObject.ApplyModifiedProperties();
}
}
#endif
五、定时器与间隔定时器
通常我们可以用协程做定时器,缺陷是依赖于脚本,所以封装一个不依赖脚本实现的定时器是很有必要的。
详细使用请看定时器与间隔定时器。
六、工作线程(多线程)
工作线程(多线程)是指在一个程序中同时执行多个任务的能力。多线程编程可以提高程序的性能和响应性,特别是在需要同时执行多个任务的情况下。工作线程通常用于执行耗时的任务,以避免阻塞主线程,从而保持程序的流畅性。
目前很多主流游戏或软件在启动或下载等待时会使用多线程技术,应用十分广泛。
详细使用请看Unity多线程。
总结
这篇文章将介绍Unity中脚本的核心概念和重要性。我们探讨如何创建和定制脚本以满足特定需求,以及脚本的生命周期、执行顺序和序列化等内容。后面两个示例(定时器和工作线程的运用)我分别用单独的章节说明,如果需要再去学习运用;这种安排旨在强调项目驱动的学习方式,同时鼓励读者在实践中运用这些示例来更快地提升自己的技能。逐步学习并根据需求应用所学知识,将使读者能够更有效地掌握相关技能。
最后,感谢大家观看,欢迎讨论指正错误,别忘了点赞👍。