第九章 Unity C#脚本(下)

本章节我们说一说MonoBehaviour这个类,它的内部有很多方法用来执行不同的逻辑。Unity脚本从唤醒到销毁都有着一套比较完善的生命周期,添加任何脚本都要遵守生命周期法则!直白的讲,就是MonoBehaviour类中的方法的执行是有严格的顺序的。我们常用的方法如下所示:Awake --> OnEnable --> Start --> Update --> FixedUpdate --> LateUpdate --> OnGUI --> OnDisable --> OnDestroy

1. Awake :意为唤醒方法,该方法在所有游戏对象被实例化之后调用,可用于游戏开始前初始化。在脚本整个生命周期内它仅被调用一次。需要注意的是,Awake方法的执行与否与当前脚本的状态(启用或禁用)并没有关系,而是与当前脚本所绑定的游戏对象的状态有关。

2. OnEnable :意为激活方法,当游戏对象变为可用状态时被调用。通常也是执行一次。但是如果当前游戏对象被取消后再重新激活启用,该方法会被再次执行一次。

备注:游戏对象可以激活启用,也可以取消不启用,在Inspector检视视图中最上面的游戏对象名称的左边的勾选框来设置。默认是勾选状态,也就是启用,去掉勾选就是不可用状态,被取消的游戏对象不会在场景中被渲染出来,也不会执行任何的组件行为。游戏对象的脚本可以激活启用,也可以取消不启用。我们之前说过,脚本属于组件。每一个组件都可以激活或者取消。组件的激活或取消同样在Inspector检视视图中组件名称前的勾选框来设置。如果我们取消组件的话,该组件将不再提供它对应的功能。如果是脚本的话,则脚本里面的大部分代码将不会执行(Awake方法和OnDestroy方法仍然会执行)。


 

3. Start :意为开始方法,仅在Update方法第一次被调用前调用。Start在脚本的生命周期中只被调用一次。它和Awake的不同是Start只在脚本实例被启用时调用,且Awake先执行。

4. Update :意为正常帧更新方法。每一帧都被循环调用执行。一般情况下,游戏的交互逻辑,动画播放等等都会在这里完成,我们大部分代码都是在这里完成的。

5 . FixedUpdate :意为固定帧更新方法。该方法会按照固定时间被循环调用。这个固定时间通常是0.02秒,当然也可以通过菜单“Edit”-->“Project Setting”-->“Time”选项窗口中右侧的 “Fixed Timestep”输入框进行修改。如下图所示:

这里我们需要解释一下FixedUpdate和Update的区别。我们上面已经讲过FPS值不是一个固定的数值,也就是Update方法的调用次数也不是固定的。产生这样的原因是因为,Update主要用于游戏画面的渲染,这项工作需要消耗计算系的大量性能,而且与当前场景的有关。如果当前场景的游戏对象很多,游戏特效也多,那么渲染该场景画面就需要消耗大量的时间;相反,渲染画面的时间就非常短;这就造成了Update方法的执行时间不固定,因此我们就无法保证1秒钟内调用固定次数的Update方法了。但是,对于某些物理行为的更新,这种Update操作可能会产生误差。因为在现实世界中,任何物体的物理行为(例如移动)肯定是按照固定的时间进行改变了。例如,某人按照1米/秒的速度前进,5秒后他肯定在前方5米的位置上。如果我们在Update方法中处理该逻辑的话,就不如在固定时间的FixedUpdate方法中处理更好一些。因为调用 FixedUpdate 的频度常常超过 Update,这个会让我们的物理行为计算更加准确,对物理行为的渲染效果也更加友好,但需要更大的计算机性能开销。

6. LateUpdate :在每帧Update方法调用后被调用,可用于Update的补充。如果在 Update 内让角色移动和转向,可以在 LateUpdate 中执行所有摄像机移动和旋转计算。这样可以确保角色在摄像机跟踪其位置之前已完全移动。

7. OnGUI :在渲染和处理GUI事件时调用。该方法也是每帧执行一次。

8. OnDisable :当游戏对象不可用状态时被调用,与OnEnable方法相反。

9. OnDestroy :当游戏对象被销毁的时候被调用,与Awake方法相反。

上面的几个方法是常用的,当然还有其他一些方法。比如我们给当前游戏对象添加碰撞体 (Collider) 组件后,就可以在脚本中使用OnCollisionEnter,OnCollisionStay,OnCollisionExit方法进行碰撞检测,然后做出对应的逻辑处理。这些内容我们会在后面的章节详细介绍。

接下来,我们就来验证上面的方法的执行,同时为下一个问题做铺垫。

我们在当前工程中创建三个Cube立方体游戏对象,名称为cube1,cube2,cube3

为了能够区分不同的游戏对象,可以在Inspector检视面板游戏对象名称的最左边的下拉弹框中选择一个不同颜色的Icon用于区分,这个不影响游戏的运行效果。

然后我们创建四个脚本,分别是Test1.cs,Test2.cs,Test3.cs和Test4.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Test1 : MonoBehaviour
{
    private void Awake()
    {
        Debug.Log("Test1 Awake");
    }

    private void OnEnable()
    {
        Debug.Log("Test1 OnEnable");
    }

    void Start()
    {
        Debug.Log("Test1 Start");
    }

    // Update is called once per frame
    void Update()
    {
        Debug.Log("Test1 Update");
    }

    private void FixedUpdate()
    {
        Debug.Log("Test1 FixedUpdate");
    }

    private void LateUpdate()
    {
        Debug.Log("Test1 LateUpdate");
    }

    private void OnGUI()
    {
        Debug.Log("Test1 OnGUI");
    }

    private void OnDisable()
    {
        Debug.Log("Test1 OnDisable");
    }

    private void OnDestroy()
    {
        Debug.Log("Test1 OnDestroy");
    }
}

以上只是Test1.cs脚本的内容,我们将这个脚本附加到Cube1上面。

为了更好的展示当前脚本代码运行的效果,我们将之前的“Test.cs”脚本取消不启用。

接下来,我们就来Play运行当前工程。

从日志来看,我们大致能够看到优先执行了Awake,OnEnable和Start方法,然后就开始循环执行FixedUpdate,Update,LateUpdate,OnGUI四个方法,最后的OnDisable和OnDestroy方法是我们停止运行的时候调用的。关于单一脚本内方法执行顺序我们就不继续讨论了。

接下来,我们将Test1的代码复制到Test2, Test3和Test4中,修改日志输出的TestX名称即可。以下是“Test2.cs”脚本文件的部分代码内容。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Test2 : MonoBehaviour
{
    private void Awake()
    {
        Debug.Log("Test2 Awake");
    }

    // 省略其他代码

}

以下是“Test3.cs”脚本文件的部分代码内容。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Test3 : MonoBehaviour
{
    private void Awake()
    {
        Debug.Log("Test3 Awake");
    }

    // 省略其他代码

}

以下是“Test4.cs”脚本文件的部分代码内容。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Test4 : MonoBehaviour
{
    private void Awake()
    {
        Debug.Log("Test4 Awake");
    }

    // 省略其他代码

}

然后,我们需要依次将Test2附加到cube2上,Test3附加到cube3上,Test4也附加到cube3上。请注意,这个附加操作的顺序不能改变哦!

 

最后我们重新Play当前工程。

 

 

从上面的日志我们大致能得到两个结论。第一,Unity会执行完所有脚本的Start方法后再去执行所有脚本的Update方法(这里面比较特殊就是Awake和OnEnable方法,以及OnDisable和OnDestroy方法,由于他们都是结对出现,因此被一起执行);第二,就是所有脚本中相同方法的执行顺序,这是顺序大致是按照附加操作的先后顺序反向执行,也就是先附加的后执行,后附加的先执行。请注意,是附加到游戏对象的操作顺序,并不是Inspector检视视图上的脚本的上下顺序哦。因为我们附加的顺序是Test1.cs,Test2.cs,Test3.cs和Test4.cs。因此脚本执行的顺序则是Test4.cs > Test3.cs > Test2.cs > Test1.cs。那可能有人就会产生疑问,这个附加操作是人为产生的,每个人的操作大概率是不太一样的,所以这个代码的执行顺序会跟随开发人员的操作不同而不同。这个问题,有些时候确实会出现问题,尤其是各个脚本之间有前后依赖关系的时候。如何能够固定这些脚本的执行顺序呢?当然可以了!我们在Project工程视图中点击任意一个脚本文件(Test1.cs),然后查看其对于的Inspector检视视图。

在右上角有一个“Execution Order…”按钮,我们点击一下。

在弹出的新窗口中,右下角有一个“+”按钮,点击这个按钮会弹出一个列表,该列表中列举出了当前的C#脚本文件,我们依次选择“Test1”,“Test2”, “Test3”,“Test4”。

当我们添加完毕后,发现每一个方法后面都有一个Default Time数值,他们依次为100,200,300,400。注意Default Time值越小,对应的方法越先执行。因此,我们的数值设置就决定了脚本会按照“Test1”->“Test2”->“Test3”->“Test4”进行执行。当然,这个顺序只影响同一个方法(例如Start)不同脚本的执行先后顺序,而对于不同方法的顺序执行还是之前的(Awake --> OnEnable --> Start等等)。设置方法后点击“Apply”应用一下,就可以关闭这个窗口了。然后我们Play当前工程查看日志。

我们就拿“Start”方法来验证,发现他们的执行顺序就变成了Test1,Test2,Test3和Test4了。

最后,我们再说一个简单的问题。所有的组件都可以在Inspector检视视图中编辑其属性,脚本也不例外。脚本的属性其实就是对外公开的类变量而已。这个类变量可以是一个普通的基本数据类型,也可以是另一个组件,也可以是场景中的另一个游戏对象。这里需要注意的是,必须将变量声明为 public 才能在 Inspector 中查看该变量。接下来,我们创建“Demo.cs”的脚本文件,并在脚本中添加一个“name”的字符串变量,如下所示:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Demo : MonoBehaviour
{
    // 对外公开的属性变量
    public string name = "";

    // Start is called before the first frame update
    void Start()
    {
        Debug.Log(name);
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

为了方便我们的测试,我们之前的所有附加脚本全部取消(在Inspector检视视图中去掉脚本组件名称前面的勾选框即可),然后将我们当前的“Demo.cs”附加到摄像机上面。

当我们附加成功后,我们就会发现下面出现一个“Name”的属性输入框。该属性就是我们在脚本中定义的类变量(name),我们在输入框中输入文本“hello”,然后Play工程。

 

我们可以看到输出了我们在Inspector检视视图中输入框中的文本“hello”。同时我们还发现,虽然我们禁用了脚本组件,但是还执行了Awake方法。我们上文提到,Awake方法的执行与游戏对象的状态有关,与组件的状态无关,OnDestroy同理。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咆哮的程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值