脚本是使用 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()
{
}
}
注意:
UnityEngine.MonoBehavior
的脚本
new
对象,不能写构造函数UnityEngine.MonoBehavior
的脚本
只有继承了MonoBehavior的脚本才可挂载到游戏对象上,这样的脚本才有生命周期。脚本的生命周期与游戏对象的状态密切相关,但脚本的生命周期函数会按照固定顺序执行,即该脚本从创建到销毁的各个阶段,这就是我们说的Unity的生命周期。
游戏对象的生命周期主要有这几个状态:创建、激活、禁用和销毁。游戏对象的状态直接影响挂载在其上的脚本的生命周期和函数调用。
OnEnable()
、Start()
等生命周期函数。主要有四个阶段:初始化阶段->更新阶段->渲染阶段->销毁阶段,下面的讲述的顺序也是生命周期函数的执行顺序。
在脚本实例被加载时调用,用于初始化变量或设置对象的初始状态。这是在游戏对象启用之前调用的。注意:在脚本整个生命周期内它仅被调用一次,且每个游戏物体上的Awke以随机的顺序被调用,Awake总是在Start之前被调用。
using UnityEngine;
public class Test01 : MonoBehaviour
{
private void Awake()
{
//在控制台打印
Debug.Log("Awake()函数被调用!");
}
}
当脚本或对象被激活时调用,用于处理对象激活时的逻辑。
using UnityEngine;
public class Test01 : MonoBehaviour
{
private void OnEnable()
{
Debug.Log("OnEnable()函数被调用!");
}
}
在所有Awake()方法调用完成后并且所有游戏对象已启用时调用,用于脚本的初始设置和游戏逻辑初始化。在第一个Update发生之前调用一次。
using UnityEngine;
public class Test01 : MonoBehaviour
{
private void Start()
{
Debug.Log("Start()函数被调用!");
}
}
每固定时间间隔调用一次,通常是0.02s,用于处理物理计算。如处理力,给游戏对象加上刚体(Rigidbody)组件后,通过刚体给物体加一个作用力时,必须在FixedUpdate里的固定帧执行,而不是Update中的帧,两者帧长不同,每帧应用一个力到刚体上。如果没有刚体(Rigidbody)组件,对象将不会受到物理引擎的影响,也就无法参与物理模拟和力的应用,所以需要给游戏对象添加刚体,但没加刚体组件的物体可以通过碰撞检测(Collider)来触发事件。
using UnityEngine;
public class FixedUpdateTest : MonoBehaviour
{
public new Rigidbody rigidbody;
private void FixedUpdate()
{
rigidbody.AddForce(Vector3.up);
}
}
每帧调用一次,用于常规的游戏逻辑处理,如输入检测和对象移动。同理,新建一个三维物体,挂载脚本进行测试。
using UnityEngine;
public class UpdateTest : MonoBehaviour
{
//Unity中脚本的成员访问权限不写,默认是private
void Update()
{
transform.position += new Vector3(Time.deltaTime * 1.2f, 0,0);
}
}
在所有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);
}
}
主要用于绘制即时用户界面元素和调试信息,适用于创建和管理GUI界面,如绘制一个按钮。也可以用于自定义EditorWindow或Editor类的GUI绘制,在方法中实现自定义的界面布局和绘制逻辑,此时该方法会在Unity编辑器的GUI渲染过程中调用。
当应用程序退出时调用,用于清理代码、保存数据或执行其他在退出时需要完成的操作。
using UnityEngine;
public class Test01 : MonoBehaviour
{
private void OnApplicationQuit()
{
Debug.Log("OnApplicationQuit()函数被调用!");
}
}
当脚本或游戏对象被禁用时调用,用于清理操作或停止处理。
using UnityEngine;
public class Test01 : MonoBehaviour
{
private void OnDisable()
{
Debug.Log("OnDisable()函数被调用!");
}
}
当游戏对象被销毁时调用,用于释放资源或执行清理操作。
using UnityEngine;
public class Test01 : MonoBehaviour
{
private void OnDestroy()
{
Debug.Log("OnDestroy()函数被调用!");
}
}
对于同一个物体上面存在多个脚本组件的时候,如果需要限制脚本的执行顺序,可以采用如下方法
点击脚本,在检查器中点击Execution Order
点击+
将需要管理执行顺序的脚本添加进来,然后修改数值,数值小的先执行,数值大的后执行,注意不要小于系统默认的脚本。
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()
{
}
}
[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;
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)) { }
在Unity中,想要控制台输出就不能使用Console.WriteLine
了,Unity提供了Debug
,包含了三个方法:Log
、LogWarning
、LogError
初次之外,还提供了在场景编辑器中画线的方法:Debug.DrawLine()
,例如
Debug.DrawLine(new Vector3(0,0,0),new Vector3(5,5,5),Color.yellow);
this.gameObject
:获取当前脚本挂载的游戏物体gameObject.name
:获取游戏物体的名称gameObject.tag
:获取游戏物体的标签gameObject.layer
:获取游戏物体的图层gameObject.activeInHierarchy
:获取游戏物体是否真正激活,例如父物体未激活,那么子物体也不会激活gameObject.activeSelf
:获取游戏物体自己是否是激活状态gameObject.transform
:获取游戏物体的transform组件GameObject.Find()
:通过游戏物体的名称获取游戏物体GameObject.FindWithTag()
:通过标签获取游戏物体GameObject g = Instantiate(prefab)
:实例化预制体GameObject g = Instantiate(prefab,transform)
:实例化预制体,并将父物体设置为transform组件所属的游戏物体GameObject g = Instantiate(prefab,Vector3.zero,Quaternion.identity)
:实例化预制体,并将位置设置为世界坐标系原点,并且不旋转Destory(gameObject)
:销毁游戏物体gameObject脚本中直接通过transform
即可获取挂载物体的Transform
组件
transform.position
:获取游戏物体的位置,相对于世界坐标transform.localPosition
:获取游戏物体的位置,相对于父物体transform.rotation
:获取游戏物体的旋转,相对于世界坐标,值类型为四元数Quaternion
transform.localRotation
:获取游戏物体的旋转,相对于父物体,值类型为四元数Quaternion
transform.eulerAngles
:获取游戏物体的旋转,相对于世界坐标,值类型为欧拉角的Vector3
transform.localEulerAngles
:获取游戏物体的旋转,相对于父物体,值类型为欧拉角的Vector3
表示旋转的有欧拉角和四元数,其中欧拉角就是使用Vector3
的xyz三个值表示在不同坐标轴旋转的角度(0 - 360)。
四元数使用Quaternion
来表示,有四个属性:x、y、z、w
Quaternion.Euler()
:将一个欧拉角的Vector3
转换为四元数quaternion.eulerAngles()
:将一个四元数转换为欧拉角Quaternion.identity
:创建一个没有旋转的四元数,四属性均为0Quaternion.LookRotaion()
:将物体旋转看向一个物体transform.localScale
:获取游戏物体的缩放transform.forward
:获取游戏物体前方(面对方向)的向量,为游戏物体+z
方向transform.right
:获取游戏物体前方(面对方向)的向量,为游戏物体+x
方向transform.up
:获取游戏物体前方(面对方向)的向量,为游戏物体+y
方向transform.LookAt()
:让游戏物体时刻看向一个位置,也就是游戏物体的+z
方向始终对着这个点transform.Rotate(Vector3.up,1);
:让游戏物体旋转,按照Vector3.up
即y轴,旋转1度transform.RotateAround(otherGameObject.transform.position,Vector3.up,3);
:让游戏物体绕着某个点,或另一个游戏物体旋转transform.Translate(Vector3.forward * 0.01f);
:让游戏物体向前方移动0.01
的距离在Unity中,游戏物体的父子级关系是有Transform组件控制的。
transform.parent
:获取父物体的Transform
组件transform.parent.gameObject
:获取父物体transform.childCount
:获取子物体的个数transform.DetachChildren
:解除与所有子物体的父子关系transform.Find("")
:通过子物体名称查找transform.GetChild(0)
:根据子物体索引获取transform.setParent()
:设置父物体Unity封装了Time
类
Time.time
:游戏开始到现在的时间,单位秒Time.timeScale
:时间缩放值,默认是1.0
Time.fixedDeltaTime
:方法FixedUpdate()
调用的固定时间间隔Time.deltaTime
:上一帧到这一帧所用的时间Application.dataPath
:获取游戏数据目录,这个目录是只读的,而且游戏发布出去后,目录中内容会被加密Application.persistentDataPath
:持久化文件路径,可读写Application.streamingAssetsPath
:和dataPath
类似,只读,但是不会被加密Application.temporaryCachePath
:临时文件目录Application.runInBackground
:控制游戏是否在后台运行Application.OpenURL()
:使用默认浏览器打开URL Application.Quit()
:退出游戏新增场景可以在project窗口中右键进行创建,但是创建的场景默认是不会被游戏最终打包的,需要在(文件 - 生成设置)中将场景添加进来
场景管理类,使用需要导入命名空间using UnityEngine.SceneManagement;
SceneManager.LoadScene()
:同步加载场景
Single
:替换当前场景Additive
:添加场景和当前场景并存AsyncOperation ao = SceneManager.LoadSceneAsync()
:异步加载场景,参数与LoadScene
相同SceneManager.GetActiveScene()
:获取当前场景Scene
实例SceneManager.sceneCount
:当前已加载场景的数量SceneManager.CreateScene("newScene")
:创建一个名为newScene的新场景SceneManager.UnloadSceneAsync(1)
:卸载索引为1的场景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.name
:场景的名称scene.buildIndex
:场景的索引scene.isLoaded
:场景是否已经加载scene.path
:场景的路径