脚本概述
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
时间和帧率管理(子弹时间的实现)
//待补充//