Unity笔记(一)——生命周期函数、Inspector面板、MonoBehavior、GameObject

发布于:2025-08-07 ⋅ 阅读:(16) ⋅ 点赞:(0)

写在前面

写本系列的目的(自用)是回顾已经学过的知识、记录新学习的知识或是记录心得理解,方便自己以后快速复习,减少遗忘。这里只有部分C#相关语法知识,内容是跟着唐老师学的。

一、生命周期函数

1、帧的概念

游戏本质与帧的关系:游戏的本质就是⼀个死循环,每次循环处理游戏逻辑就会更新⼀次画面、当切换画面的速度达到⼀定时(通常24/秒),⼈眼就会认为画面是流畅的。

常见帧率:

60帧:每帧时间=1000ms / 60 ≈ 16.66ms

30帧:每帧时间=1000ms / 30 ≈ 33.33ms

⼈眼舒适帧率:人眼在放松状态下可视帧数为每秒24

Unity已经内置了死循环机制,开发者不需要自己编写while循环,通过使用Unity提供的生命周期函数来处理游戏逻辑。

2、生命周期函数

(1)Awake函数

AwakeUnity中的生命周期函数,在对象创建时自动调用。类似构造函数的存在(但不能带参数),用于对象创建时的初始化操作。⼀个对象在其生命周期中只会调用⼀次(仅与脚本实例创建有关)。

不写访问修饰符时默认是私有函数。

(2)OnEnable生命周期函数

当脚本依附的GameObject对象每次激活时自动调用。​​​​​​​且相同来源的打印信息会被折叠显示,但不同激活方式(初始创建vs手动激活)会产生不同来源的打印

用处是,当需要对象被激活时执行特定逻辑处理的场景,可以编写OnEnable生命周期函数。

(3)Start生命周期函数

从对象被创建出来后,第⼀次帧更新之前调用。⼀个对象只会调用一次。

与Awake的区别是:

Awake:对象生成时立即调用,类似构造函数

Start:在Awake之后,第⼀次帧更新之前调用

例如,​在Update中动态创建的对象会立即执行Awake,Start会等到当前帧结束后,下⼀帧开始前才执行。

用处是用于对象初始化。

(4)FixedUpdate生命周期函数

主要用于进行物理相关的处理,如碰撞检测等物理计算。如果不做物理相关的游戏开发,基本上很少使用该函数。

Unity内部实现了⼀个死循环,以固定间隔时间(物理帧间隔)执行FixedUpdate

(5)Update生命周期函数

处理游戏核心逻辑更新,如位移计算等;每帧执行,帧率可通过项目设置控制;Unity通过反射在每次游戏循环时自动调用;是编写游戏逻辑的主要接口,会频繁使用。

例如,在Update中,可以通过每帧修改位置实现平滑移动效果。

(6)LateUpdate生命周期函数

处理摄像机位置更新等渲染相关逻辑,在Update之后执行,两者之间Unity会处理动画更新,这样可以避免在Update中更新摄像机导致的渲染错误。

(7)OnDisable生命周期函数

​​​​​​​当依附的GameObject对象每次失活时调⽤,与OnEnable形成对应关系。​​​​​

对象失活后,所有生命周期函数(特别是循环更新类函数)都会停止调用。

核心作用:​​​​​​​用于在对象失活时进行特定逻辑处理,适合存放状态保存、资源释放等代码

(8)OnDestroy生命周期函数

当依附的GameObject对象被销毁(从场景中移除)时调用。

销毁时会先调用OnDisable再调⽤OnDestroy,形成完整生命周期闭环。只会调用一次。

二、Inspector窗口的变量

1、让私有的和保护的也可以被显示和编辑

在脚本中创建的变量,如果不是public的,在Inspector面板上就不会被显示。

但也可以通过特性,让私有的和保护的也可以被显示和编辑。

可以在变量前添加[SerializeField]特性,这样私有的和保护的变量都可以在面板上显示和编辑。

public class inspec : MonoBehaviour
{
    [SerializeField]
    private int a = 10;
}

面板:

2、让公共的不能被显示和编辑

在脚本中创建的变量,可以通过特性,让公共的也不可以被显示和编辑。

可以在变量前添加[HideInInspector]特性,这样公共的也不可以在面板上显示和编辑。

public class inspec : MonoBehaviour
{
    [HideInInspector]
    public string str = "HELLO";
}

3、小部分类型有显示限制

我们所熟知的各种类:数组、列表、枚举、整型、浮点型、字符串等,只要是公共的或是加了可编辑特性的,都可以在Inspector面板上显示和编辑。

但字典、自定义结构体、自定义类哪怕是公共的也不支持显示。​​​​​​​Unity默认不支持复杂数据结构的序列化显示。

但如果在自定义结构体或是自定义类前,加上特性[System.Serializable],那么自定义的结构体或是自定义类也能够显示并编辑了

[System.Serializable]
public class Myclass
{
    public string name;
    public int age;
}

public class inspec : MonoBehaviour
{
    public Myclass myclass;
}

但是字典仍然不可以。

三、MonoBehavior

1、重要成员

(1)获取依附的GameObject

通过MonoBehavior中的成员,可以获取当前脚本依附的对象的各种信息。名字、位置、将当前依附的脚本进行激活、失活。

以下是获取信息的函数。

void Start()
{
    //获取名字
    print(this.gameObject.name);
    //获取位置信息
    //获取位置
    print(this.transform.position);
    //获取角度
    print(this.transform.eulerAngles);
    //获取缩放大小
    print(this.transform.lossyScale);
}

将当前脚本激活/失活可以用this.enabled = true/false:

void Start()
{
    //激活
    this.enabled = true;
    //失活
    this.enabled = false;
}

2、重要方法

(1)获取挂载的脚本

可以利用函数GetComponent的泛型方法来获取当前对象挂载的脚本。

void Start()
{
    //根据泛型获取
    mono t = this.GetComponent<mono>();
    print(t);
}

(2)获取挂载的多个脚本

假如对象上挂载了多个相同的脚本,可以用this.GetComponents来得到挂载的所有脚本。

void Start()
{
    mono[] array = this.GetComponents<mono>();
    print(array.Length);
}

(3)获取子对象挂载的脚本 

可以使用this.GetComponentInChildren<T>()获取挂载到子对象上的脚本(它默认也会找自己身上是否挂载该脚本)

括号内可以传参数,默认不传是false。假如穿了true,那么哪怕是失活的脚本也会去找,false默认失活的不找

同样的,也可以获取子对象挂载的多个脚本,添个s即可:this.GetComponentsInChildren<T>()

void Start()
{
    mono2 t2 = this.GetComponentInChildren<mono2>(true);
    print(t2);

    mono2[] array2 = this.GetComponentsInChildren<mono2>(true);
    print(array.Length);
}

(4)获取父对象挂载的脚本

可以使用this.GetComponentInParent<T>()获取挂载到父对象上的脚本(它默认也会找自己身上是否挂载该脚本)

需要注意的是,找父对象的脚本时一般不会传入是否找失活对象。因为父对象失活时,子对象也会失活,没有意义。

同样的,也可以获取父对象挂载的多个脚本,添s即可:this.GetComponentsInParent<mono2>()

void Start()
{
    mono_f t3 = this.GetComponentInParent<mono_f>();
    print(t3);

    mono2[] array3 = this.GetComponentsInParent<mono2>();
    print(array3.Length);
}

四、最小单位GameObject

GameObject是Unity场景中的最小单位,所有脚本都依附在GameObject上运行。

1、GameObject成员变量

(1)获取名字

可以通过this.gameObject.name获取当前对象名称。也可以直接对该值赋值事实修改场景中的对象名称。

    void Start()
    {
        print(this.gameObject.name);
        this.gameObject.name = "MyCube";
        print(this.gameObject.name);
    }

执行结果:

(2)是否激活、是否是静态

判断对象是否激活可以用.activeSelf,判断是否是静态可以用.isStatic

    void Start()
    {
        print(this.gameObject.activeSelf);
        print(this.gameObject.isStatic);
    }

(3)获取层级和标签

获取层级:gameObject.layer,层级默认为0;获取标签:gameObject.tag,未设置标签时返回"Untagged"

    void Start()
    {
        print(this.gameObject.layer);
        print(this.gameObject.tag);
    }

(4)Transform

可以通过this.gameObject.transform.position获取详细的三维坐标,也可以直接通过this.transform.position获取。推荐后者:

print(this.transform.position);

结果Unity会自行四舍五入。 

2、GameObject静态方法

(1)创建自带几何体

可以通过GameObject.CreatePrimitive(PrimitiveType)静态方法创建Unity内置几何体。如下例,创建一个自带的Cube立方体并为其改名。

void Start()
{
    GameObject obj =  GameObject.CreatePrimitive(PrimitiveType.Cube);
    obj.name = "创建的立方体";
}

运行结果: 

(2)查找对象 

①可以通过按名查找和按tag查找。

按名查找:使用GameObject.Find("对象名")静态方法。缺点是效率较低,因为会遍历场景中所有对象,假如没找到,返回null

按tag查找: 有两种方法,效果一样,GameObject.FindWithTag("标签名")、GameObject.FindGameObjectWithTag("标签名")。

需要注意的是,二者都无法查找到失活的对象。 假如场景中有多个同名或是同tag对象时,查找结果是随机的,无法指定找到哪个对象。

void Start()
{
    GameObject obj2 = GameObject.Find("创建的立方体");
    if(obj2 != null)
    {
        print(obj2.name);
    }

    GameObject obj3 = GameObject.FindWithTag("Player");
    if(obj3 != null)
    {
        print(obj3.name);
    }

    GameObject obj4 = GameObject.FindGameObjectWithTag("Player");
}

②查找多个对象 

 查找多个对象只能用GameObject.FindGameObjectWithTag("tag名"),且同样只能找到激活对象

void Start()
{
    GameObject[] objs = GameObject.FindGameObjectsWithTag("Player");
    print("查找到的数量" + objs.Length);
}

 ③通过泛型查找对象

可以通过FindObjectOfType<T>():查找挂载特定脚本的对象,返回的是脚本组件实例。

void Start()
{
    GB_1 o = GameObject.FindObjectOfType<GB_1>();
    print(o.gameObject.name);
}

(3)实例化(克隆)对象 

实例化(克隆)对象是根据⼀个GameObject对象创建和它完全相同的对象。克隆的对象包含原对象的所有脚本信息和组件。

使用GameObject.Instantiate()方法实现克隆功能。克隆的对象可以选择场景上的对象,也可以是预设体对象。

public GameObject obj;
void Start()
{
    GameObject.Instantiate(obj);
}

关联预设体:

运行结果:

(4)删除对象

GameObject.Destroy()方法可以删除游戏对象或脚本组件。可以传1~2个参数,第一个参数是需要删除的对象,第二个参数是延迟多少秒删除。

public GameObject obj;

public GameObject obj2;
// Start is called before the first frame update
void Start()
{
    GameObject.Instantiate(obj);

    GameObject.Destroy(obj2);
    //延迟5s删除
    GameObject.Destroy(obj, 5);
    //删除当前脚本组件,Inspector窗口中该脚本组件会消失
    GameObject.Destroy(this);
}

GameObject.Destroy()方法实际上不会立即移除对象,只是添加了移除标识。通常会在下一帧时真正移除对象。如果需要立即删除,需要使用:DestroyImmediate()

(5)过场景不移除

默认情况下,在切换场景时,所有场景对象会被自动删除。如果不希望某个对象被删除,就可以使用:

GameObject.DontDestroyOnLoad(this.gameObject);

这样,该对象过场景就不会被移除。

3、GameObject成员方法

(1)创建空物体

创建空物体直接使用new即可,就可以在场景里创建一个空物体。可以传入参数,为空物体命名、挂载脚本。

添加脚本使用泛型方法 obj.AddComponent<T>即可,添加的脚本有返回值,返回值是添加的脚本,可以用变量接收。

 void Start()
 {
     GameObject obj = new GameObject();
     GameObject obj2 = new GameObject("空物体");
     GameObject obj3 = new GameObject("加脚本的空物体",typeof(temp));

     temp tp =  obj2.AddComponent<temp>();
}

查看加脚本的Inspector信息:

 (2)标签比较

标签比较有两种方法,第一种是使用gameObject.CompareTag(),会比较当前对象的tag名和括号中传入的tag名

第二种是直接判断gameObject.tag == “tag名”,两种方法完全一样。

void Start()
{
    if(this.gameObject.CompareTag("Player"))
    {
        print("对象的标签是 Player");
    }
    if(this.gameObject.tag == "Player")
    {
        print("对象的标签是 Player");
    }
}

(3)设置对象激活失活

设置对象激活失活可以使用obj.SetActive(),括号里填false时,对象就会失活。

void Start()
{    
    GameObject obj = new GameObject();
    GameObject obj2 = new GameObject("空物体");
    GameObject obj3 = new GameObject("加脚本的空物体",typeof(temp));

    obj.SetActive(false);
    obj2.SetActive(false);
    obj3.SetActive(false);
}

(4)广播或发送消息

了解即可,不建议使用。

SendMessage()主要是通知自己要执行什么行为。例如下例,通知自己执行函数TestFun。于是会在自己身上挂载的所有脚本中去找这个名字的函数,效率很低。

 void Start()
 {
     this.gameObject.SendMessage("TestFun");
 }

 void TestFun()
 {
     print("执行TestFun");
 }

gameObject. BroadcastMessage("函数名")是广播行为,会通知自己和自己的子对象去执行这个函数。

this.gameObject.BroadcastMessage("TestFun");

gameObject.SendMessageUpwards("函数名")也是广播行为,会通知自己和自己的父对象去执行这个函数。

this.gameObject.SendMessageUpwards("TestFun");

网站公告

今日签到

点亮在社区的每一天
去签到