0%

Unity中的C#脚本

脚本概述

脚本概述

unity中的脚本定义了对象运行的逻辑,包括用户输入、游戏事件等。脚本的本质是新创建组件,在unity内置组件无法完成应实现的功能时,脚本文件为其提供补充。通常所说的“unity编程”或者说“C#编程”,其实本质就是编写脚本。

创建脚本时,系统会自动在脚本中创建一个由MonoBehaviour的内置类派生而来的类,此类包含一些用于链接unity内部架构的函数,此外,类名和文件名必须相同才能时脚本附加到游戏对象中

新建的脚本默认文件如下:

using UnityEngine;
using System.Collections;

public class NewBehaviourScript : MonoBehaviour {

// Use this for initialization
void Start () {

}

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

}

}

其中,Start()函数就类似于构造函数,用于对对象的各种变量进行初始化。

Update()用于放置具体的实现代码,用于游戏对象的帧更新,包括移动等。

Start()函数在组件激活时调用,所以适用于初始化,Update()每一帧都会调用,所以适用于帧更新

此外,还包括Awake()最早调用函数,FixedUpdate()固定频率调用函数,OnDestroy()销毁时调用函数等等等等,不过初学者可以先只关注Start于Update

如果有没用到update的情况,那么最好及时删除,因为空调用update也会造成一定开销

以上函数,统称为脚本的“生命周期方法”

 

Debug命令

可以在start函数中添加以下代码:

void Start () 
{
    Debug.Log("Hello world!");
}

Debug.Log()用于将消息输出到Unity控制台,作用是检测对象是否正确初始化,若成功,则在窗口底部打印Hello world

此外

Debug.Lob(String str)也用于标准输出

Debug.LogWarning(object obj)用于输出警告信息

Debug.logWarning(object obj)用于输出错误信息

//等等,unity中的Debug命令还有很多很多。。。。//

 

变量

类似C++中的调试,Inspector作为一个变量查看器,只能查看声明为public的变量

Unity 实际上允许您在游戏运行时更改脚本变量的值。此功能很有用,无需停止和重新启动即可直接查看更改的效果。当游戏运行过程结束时,变量的值将重置为按下 Play 之前所处的任何值。这样可确保自由调整对象的设置,而不必担心会造成任何永久性损坏。

 

脚本执行顺序

单个脚本中存在一个隐含的生命周期函数执行顺序Awake()->OnEnable()->Start()->Update()

OnEnable()是一个每次激活脚本都会调用一次的方法

理论上每个脚本会严格按着这个顺序调用方法,所以在执行简单的脚本程序时,初始化的语句放在Awake()或者Start()中都是可行的。不过当一个对象中运行多个脚本时,具体的调用顺序就会变成逐个执行每个脚本的优先方法,所以说如果有非常紧急的事务,可以直接放在最优先的Awake()方法

此外,可以在Unity中自行设置全部脚本执行顺序方法

 

脚本组件与对象

在官方的Unity文档中,对象的数据更改定义了有两种方式

  • 一是通过Inspector来更改属性,这种方式相当于为脚本提供一个更方便的接口,Inspector窗口直接放在脚本内容下,游戏窗口旁边,在调试过程中动动手就能更改,十分方便:

详细解析如下

在具体的脚本代码中,public的变量都会自动显示在Inspector中并随时提供更改,如果要显示private变量,那么就在前面增加一行[SerializeField]

[SerializeField] private LayerMask _groundLayer;
[SerializeField] private int _detectorCount = 3;
[SerializeField] private float _detectionRayLength = 0.1f;

如上对应图片中COLLSION项以下部分变量,而如果要增加像图片中的小标题,则用以下格式:

[Header("COLLISION")]

拖动条的代码如下(其实就是增加了Range关键字):

[SerializeField] [Range(0.1f, 0.3f)] private float _rayBuffer = 0.1f;

此外,还有[Space]关键字用于隔行,[HideInInspector]关键字用于隐藏不想显示在面版中的变量

 

  • 二是通过脚本自身定义方法来对游戏对象进行数据操控,这种方式最为常用,因为这种方式可以在游戏运行时进行,并能响应用户输入,以用于完成例如游戏对象摧毁等任务

 

游戏对象内组件的访问

游戏对象内的多个组件往往需要相互交流,例如经典格斗游戏中跳跃时与站立时同一个招数是不同效果的。

组件就是类的实例,所以第一步是获取所需要组件的实例引用,使用GetComponent函数来完成

void Start () 
{
    Rigidbody rb = GetComponent<Rigidbody>();
}

这个例子中,用Rigidbody声明的变量rb来储存实例引用游戏对象刚体组件,尖括号意思是引用该类型的所有引用

根据官方文档提示,带有类型的的访问是最优于性能的(这里先排除一些某类型用不了的情况),所以这里先只使用这种方式,其他重载仅限了解即可:

  • GetComponent()
  • GetComponent(typeof(T))
  • GetComponent(string)

引用过后,我们就可以通过这种方式来访问相应变量辣

 rb.mass = 10f;

另外,还可以直接调用引用实例的函数,这是Inspector中所没有的

若未找到相应引用,则该函数返回NULL,这会导致主要开销

 

游戏对象间的组件访问

一个玩家需要知道另一个玩家的位置,这种情况经常发生,尽管对象经常孤立运行,但对象是没有隐私的。

现在直接public一个游戏对象变量

public class Enemy : MonoBehaviour
{
    public GameObject player;
    
// 其他变量和函数...

}

这个变量的类型是游戏对象,它甚至可以在Inspector中显示出来。于是乎这个变量成为一个接口,一个用于操控其他目标对象的接口

你可以直接拖拽将想要的对象对接给他,也可以在Inspector里面调,哪样都行

对接上后就可以直接通过变量.这样的方式来直接调用了。同样的,这个“对接”过来的变量也可以被GetComponent函数引用

此外,如果在脚本中声明组件类型的公共变量,则可以拖动已附加该组件的任何游戏对象。这样可以直接访问组件而不是游戏对象本身

public Transform playerTransform; 

小tips:GameObject是Unity的一个关键类,Transform是GameObject属下的一个脚本API,掌管物体的大小位置旋转等信息

以上是游戏组件对象的最基本运行逻辑,以下介绍批量化处理方法

 

子游戏对象

从以上方式中可得知,每进行一次对象间的信息链接,就要重新声明一个对象变量,但游戏对象往往是批量化的,如果我们删除一个对象,那么引用这个对象的相关变量都要一个个删除,十分麻烦。

所以我们使用子游戏对象的概念来管理游戏对象

using UnityEngine;

public class WaypointManager : MonoBehaviour {
public Transform[] waypoints;

void Start() 
{
    waypoints = new Transform[transform.childCount];
    int i = 0;
    
    foreach (Transform t in transform)
    {
        waypoints[i++] = t;
    }
}

}

示例创建数组来对子游戏对象进行储存,方法对各个对象遍历,有意思的是与C++比起来C#的数组声明的尖括号居然是放在类型后面的,另外,查找子对象的可以使用Teansform.Find函数(记得加双引号)

在场景层级视图中查找任意可以识别的对象,可以用GameObject.Find

 

时间和帧率管理(子弹时间的实现)

//待补充//