2、脚本组件

脚本组件

脚本是使用 Unity 开发的所有应用程序中必不可少的组成部分。大多数应用程序都需要脚本来响应玩家的输入并安排游戏过程中应发生的事件。除此之外,脚本可用于创建图形效果,控制对象的物理行为,甚至为游戏中的角色实现自定义的 AI 系统。

我们可以在Project视图右键进行脚本创建,除了C#脚本,还有两类脚本;Testing用来做单元测试,Playables是TimeLine引入的新概念。默认C#脚本模板如下:

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

public class Move : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

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

注意

生命周期

只有继承了MonoBehavior的脚本才可挂载到游戏对象上,这样的脚本才有生命周期。脚本的生命周期与游戏对象的状态密切相关,但脚本的生命周期函数会按照固定顺序执行,即该脚本从创建到销毁的各个阶段,这就是我们说的Unity的生命周期。

游戏对象的生命周期主要有这几个状态:创建、激活、禁用和销毁。游戏对象的状态直接影响挂载在其上的脚本的生命周期和函数调用。

主要有四个阶段:初始化阶段->更新阶段->渲染阶段->销毁阶段,下面的讲述的顺序也是生命周期函数的执行顺序。

初始化阶段

Awake()

在脚本实例被加载时调用,用于初始化变量或设置对象的初始状态。这是在游戏对象启用之前调用的。注意:在脚本整个生命周期内它仅被调用一次,且每个游戏物体上的Awke以随机的顺序被调用,Awake总是在Start之前被调用。

using UnityEngine;
 
public class Test01 : MonoBehaviour
{
    private void Awake()
    {
        //在控制台打印
        Debug.Log("Awake()函数被调用!");
    }
}

OnEnable()

当脚本或对象被激活时调用,用于处理对象激活时的逻辑。

using UnityEngine;
 
public class Test01 : MonoBehaviour
{
    private void OnEnable()
    {
        Debug.Log("OnEnable()函数被调用!");
    }
}

Start()

在所有Awake()方法调用完成后并且所有游戏对象已启用时调用,用于脚本的初始设置和游戏逻辑初始化。在第一个Update发生之前调用一次。

using UnityEngine;
 
public class Test01 : MonoBehaviour
{
    private void Start()
    {
        Debug.Log("Start()函数被调用!");
    }
}

更新阶段

FixedUpdate()

每固定时间间隔调用一次,通常是0.02s,用于处理物理计算。如处理力,给游戏对象加上刚体(Rigidbody)组件后,通过刚体给物体加一个作用力时,必须在FixedUpdate里的固定帧执行,而不是Update中的帧,两者帧长不同,每帧应用一个力到刚体上。如果没有刚体(Rigidbody)组件,对象将不会受到物理引擎的影响,也就无法参与物理模拟和力的应用,所以需要给游戏对象添加刚体,但没加刚体组件的物体可以通过碰撞检测(Collider)来触发事件。

using UnityEngine;
 
public class FixedUpdateTest : MonoBehaviour
{
    public new Rigidbody rigidbody;
    
    private void FixedUpdate()
    {
        rigidbody.AddForce(Vector3.up);
    }
}

Update()

每帧调用一次,用于常规的游戏逻辑处理,如输入检测和对象移动。同理,新建一个三维物体,挂载脚本进行测试。

using UnityEngine;
 
public class UpdateTest : MonoBehaviour
{
    //Unity中脚本的成员访问权限不写,默认是private
    void Update()
    {
        transform.position += new Vector3(Time.deltaTime * 1.2f, 0,0);
    }
}

LateUpdate()

在所有Update()方法之后调用,用于在所有对象更新后处理逻辑,如当物体在Update里移动时,跟随物体的相机可以在此处实现。

using UnityEngine;
 
public class UpdateTest : MonoBehaviour
{
    //Unity中脚本的成员访问权限不写,默认是private
    void Update()
    {
        transform.position += new Vector3(Time.deltaTime * 1.2f, 0,0);
    }
 
    private void LateUpdate()
    {
        Camera.main.transform.position = transform.position+ new Vector3(0, 0.7f, -1.5f);
    }
}

渲染阶段

OnGUI()

主要用于绘制即时用户界面元素和调试信息,适用于创建和管理GUI界面,如绘制一个按钮。也可以用于自定义EditorWindow或Editor类的GUI绘制,在方法中实现自定义的界面布局和绘制逻辑,此时该方法会在Unity编辑器的GUI渲染过程中调用。

销毁阶段

OnApplicationQuit()

当应用程序退出时调用,用于清理代码、保存数据或执行其他在退出时需要完成的操作。

using UnityEngine;
 
public class Test01 : MonoBehaviour
{
    private void OnApplicationQuit()
    {
        Debug.Log("OnApplicationQuit()函数被调用!");
    }
 
}

OnDisable()

当脚本或游戏对象被禁用时调用,用于清理操作或停止处理。

using UnityEngine;
 
public class Test01 : MonoBehaviour
{
    private void OnDisable()
    {
        Debug.Log("OnDisable()函数被调用!");
    }
}

OnDestroy()

当游戏对象被销毁时调用,用于释放资源或执行清理操作。

using UnityEngine;
 
public class Test01 : MonoBehaviour
{
    private void OnDestroy()
    {
        Debug.Log("OnDestroy()函数被调用!");
    }
}

脚本执行顺序

对于同一个物体上面存在多个脚本组件的时候,如果需要限制脚本的执行顺序,可以采用如下方法

image-20240912170402468

点击脚本,在检查器中点击Execution Order

image-20240912170459143

点击+将需要管理执行顺序的脚本添加进来,然后修改数值,数值小的先执行,数值大的后执行,注意不要小于系统默认的脚本。

Inspector检查器窗口可编辑的变量

Inspector显示的可编辑内容就是脚本的成员变量,私有和保护无法显示编辑,只有声明public的变量才可以在检查器中编辑。

例如,脚本如下,其中的变量myColor为public的,所以就可以在编辑器中直接编辑

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

public class Move : MonoBehaviour
{
    public string myColor;
    
    // Start is called before the first frame update
    void Start()
    {
        Debug.Log("Start ...");
    }

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

image-20240912171219562

常用特性

[HideInInspector]:如果变量上方添加此特性,即使是public的也无法在检查器中编辑。

[SerializeField]:此特性可以将变量序列化,就是把一个对象保存到一个文件或数据库字段中

其他的辅助特性如下:

1、分组说明特性 Header 为成员分组
[Header("基础属性")]
public int age;
public bool sex;
[Header("战斗属性")]
public int atk;
public int def;

2、悬停注释 Tooltip 为变量添加说明
[Tooltip("闪避")]
public int miss;

3、间隔特性 Space 让两个字段之间出现间隔
[Space()]
public int crit;

4、修饰数值的滑条范围 Range
[Range(0,10)]
public float luck;

5、多行显示字符串,默认3行 Multiline
[Multiline()]
public string tips;

6、滚动条显示字符串,默认三行 TextArea
[TextArea()]
public string myLife;

7、为变量添加快捷方法 ContextMenuItem
参数1:显示按钮名
参数2;方法名,不能有参数
[ContextMenuItem("重置钱","Money")]
public int money;
private void Money()
{
    money = 10;
}

8、为方法添加特性能够在Inspector中执行
[ContextMenu("测试")]
private void TestFunc()
{
    print("测试方法");
    money = 20;
}

支持的类型

大部分类型都能显示编辑,但是字典Dictionary、自定义类型变量不能显示

public int[] array;
public List<int> list;
public E_TestEnum type;
public GameObject gameObj;

MonoBehaviour提供的API

基本信息

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

public class Move : MonoBehaviour
{

    // Start is called before the first frame update
    void Start()
    {
        // 获取脚本挂载的物体对象
        GameObject g = this.gameObject;

        // 获取脚本挂载的物体名称
        Debug.Log(gameObject.name);

        // 获取挂载物体的位置,也可以直接使用transform.position
        Debug.Log(gameObject.transform.position);

        // 获取挂载物体的欧拉角
        Debug.Log(transform.eulerAngles);

        // 获取挂载物体的缩放大小
        Debug.Log(transform.lossyScale);

        // 获取当前脚本是否激活
        Debug.Log(enabled);
    }

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

如果需要获取其他物体的属性,只需要拿到对应的GameObject。

获取组件

获取GameObject上挂载的其他组件

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

public class Move : MonoBehaviour
{

    // Start is called before the first frame update
    void Start()
    {
        // 通过组件名称获取
        MeshRenderer m1 = GetComponent("MeshRenderer") as MeshRenderer;

        // 通过组件类型获取
        MeshRenderer m2 = GetComponent(typeof(MeshRenderer)) as MeshRenderer;

        // 通过泛型获取
        MeshRenderer m3 = GetComponent<MeshRenderer>();
    }

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

如果GameObject上存在多个同类型的组件,可以使用如下方式获取

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

public class Move : MonoBehaviour
{

    // Start is called before the first frame update
    void Start()
    {
        // 获取多个组件,并放入数组
        MeshRenderer[] arr = GetComponents<MeshRenderer>();
      
        // 获取多个组件,并放入集合
        List<MeshRenderer> list = new List<MeshRenderer>();
        GetComponents<MeshRenderer>(list);
    }

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

获取子对象挂载的组件,默认也会找自己是否挂载该组件

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

public class Move : MonoBehaviour
{

    // Start is called before the first frame update
    void Start()
    {
        // 获取单个组件
        MeshRenderer m = GetComponentInChildren<MeshRenderer>();
        
        // 获取多个组件,并放入数组
        MeshRenderer[] arr = GetComponentsInChildren<MeshRenderer>();
        // 获取多个组件,并放入集合
        List<MeshRenderer> list = new List<MeshRenderer>();
        GetComponentsInChildren<MeshRenderer>(true,list);
    }

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

获取父对象挂载的组件,默认也会找自己是否挂载该组件

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

public class Move : MonoBehaviour
{

    // Start is called before the first frame update
    void Start()
    {
        // 获取单个组件
        MeshRenderer m = GetComponentInParent<MeshRenderer>();

        // 获取多个组件,并放入数组
        MeshRenderer[] arr = GetComponentsInParent<MeshRenderer>();
        // 获取多个组件,并放入集合
        List<MeshRenderer> list = new List<MeshRenderer>();
        GetComponentsInParent<MeshRenderer>(true,list);
    }

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

如果不确定组件是否存在,可以尝试获取组件

if (TryGetComponent<MeshRenderer>(out mr)) { }

常用API

Debug

在Unity中,想要控制台输出就不能使用Console.WriteLine了,Unity提供了Debug,包含了三个方法:LogLogWarningLogError

初次之外,还提供了在场景编辑器中画线的方法:Debug.DrawLine(),例如

Debug.DrawLine(new Vector3(0,0,0),new Vector3(5,5,5),Color.yellow);

image-20240913173232785

GameObject

Transform

脚本中直接通过transform即可获取挂载物体的Transform组件

位置

旋转

表示旋转的有欧拉角和四元数,其中欧拉角就是使用Vector3的xyz三个值表示在不同坐标轴旋转的角度(0 - 360)。

四元数使用Quaternion来表示,有四个属性:x、y、z、w

缩放

向量

方法

父子关系

在Unity中,游戏物体的父子级关系是有Transform组件控制的。

时间

Unity封装了Time

Application类

Scene、SceneManager

新增场景可以在project窗口中右键进行创建,但是创建的场景默认是不会被游戏最终打包的,需要在(文件 - 生成设置)中将场景添加进来

image-20240914151529008

SceneManager

场景管理类,使用需要导入命名空间using UnityEngine.SceneManagement;

异步加载场景并获取进度
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneTest : MonoBehaviour
{

    AsyncOperation asyncOperation;

    // Start is called before the first frame update
    void Start()
    {   
        // 开启一个携程异步加载场景
        StartCoroutine(loadScene());
    }

    // 异步加载场景
    IEnumerator loadScene(){
        asyncOperation = SceneManager.LoadSceneAsync("MyScene");
        yield return asyncOperation;
    }

    // Update is called once per frame
    void Update()
    {
        // 获取加载的进度(0 - 0.9)
        Debug.Log(asyncOperation.progress);
    }
}

Scene

场景类