一、脚本相关概念
1-脚本的定义
- 脚本可以附加在游戏物体上,用于定义游戏对象行为的指令代码,如:xxx.cs文件
- Unity支持三种高级编程语言:C#、javascript和Boo Script(Unity4以前支持)
2-语法结构
//using命名空间,类比java的import包
using 命名空间;
public class 类名: MonoBehaviour
{
void 方法名();
Debug.Log("调试显示信息");
print("本质就是Debug.Log方法");
}
- 文件名与类名必须一致
- 写好的脚本必须附加到物体上才执行
- 附加到游戏物体的脚本类必须从MonoBehaviour类继承
3-脚本编译过程
- 编译运行过程:
- 源代码:(CLS)—>中间语言(dll文件)–(Mono Runtime)- >机器码
4-创建脚本文件
鼠标右键创建即可,选择C# script
在unity中创建出的C#脚本文件模板内部的构造:
using UnityEngine;
using System.Collections;
public class demo01 : MonoBehaviour {
// 默认会带两个方法,如果不使用请删除
// Use this for initialization
void Start () {
}
// Update is called once per frame
// Update方法不用的话就把它删除,因为此方法会被不断的调用,大概0.02s被调用一次
void Update () {
}
}
模板也是可以自定义的,我们到unity的目录下:
修改为:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
///
/// </summary>
public class #SCRIPTNAME# : MonoBehaviour
{
}
5-脚本在Unity中的一些操作
- 拖动脚本文件到游戏对象上,可以发现创建了一个对象,通过这个脚本对象我们可以进行许多操作
- 然后我们修改一下脚本内容:
public class NewBehaviourScript : MonoBehaviour
{
//public类型默认会在Insepctor中显示
public int A;
//private类型默认不会在Insepctor中显示
private string B = "abc";
//[SerializeField]
//作用:在Unity编译器中显示当前private的字段
[SerializeField]
private bool C = true;
//[Range(1,100)]
//作用:在Unity编译器中限定此字段的值为0~100
[Range(1, 100)]
public int D = 10;
//[HideInInspector]
//作用:在Unity编译器中隐藏当前public的字段
[HideInInspector]
public float E = 1.0f;
}
- 查看我们之前的游戏对象的Inspector窗口中的脚本位置,发现出现相关的变量,而且可以进行设置
6-脚本生命周期(重要)
6-1.定义
- Unity脚本从唤醒到销毁的过程
- 消息:当满足某种条件Unity引擎自动调用的函数
- 脚本生命周期也被称为必然事件
6-2.初始阶段
- Awake()唤醒:当物体载入时立即调用1次;常用于在游戏开始前进行初始化。可以判断当满足某种条件执行此脚本,然后使用此语句this.enable=true,表示会被调用。this.enable=false,之后的方法就不会被执行了。
- OnEnable()当可用:每当脚本对象启用时调用。
- Start()开始:物体载入且脚本对象启用时被调用1次。常用于数据或游戏逻辑初始化,执行时机晚于Awake。
/// <summary>
/// 脚本生命周期
/// </summary>
public class LIfeCycle : MonoBehaviour
{
//执行时机:创建游戏对象时,立即执行1次 (与脚本禁用与否无关)
//作用:初始化
private void Awake()
{
Debug.Log("Awake()方法被调用了!");
}
//执行时机:每当脚本对象启用时调用,执行1次
//作用:初始化
private void OnEnable()
{
Debug.Log("OnEnable()方法被调用了!");
}
//执行时机:创建游戏对象时,且脚本未被禁用时,执行1次
//作用:初始化
private void Start()
{
Debug.Log("Start()方法被调用了!");
}
}
将脚本挂到游戏对象上,测试一下
6-3.物理阶段
- FixedUpdate()表示固定更新:脚本启用后,固定时间被调用,适用于对游戏对象做物理操作,例如移动等。固定是0.02秒更新一次,这个数值我们可以进行更改,设置更新频率"Edit" --> “Project Setting”–>“Time” --> “Fixed Timestep” 值,默认为0.02s。
- OnCollisionXXX()碰撞:当满足碰撞条件时调用。
- OnTriggerXXX()触发:当满足触发条件时调用。
6-4.输入事件
需要碰撞器Collider,即unity中的这些collider:
- OnMouseEnter()鼠标移入:鼠标移入到当前Collider时调用。
- OnMouseOver()鼠标经过:鼠标经过当前Collider时调用。
- OnMouseExit()鼠标离开:鼠标离开当前Collider时调用。
- OnMouseDown()鼠标按下:鼠标按下当前Collider时调用。
- OnMouseUp()鼠标抬起:鼠标在当前Collider上抬起时调用。
6-5.游戏逻辑
- Update()更新:脚本启用后,每次渲染场景时调用,频率与设备性能及渲染量有关
- LateUpdate()延迟更新:在Update函数被调用后执行,适用于跟随逻辑
6-6.场景渲染
- OnBecameVisible()当可见:当Mesh Renderer在任何相机上可见时调用
- OnBecameInvisible()当不可见:当Mesh Renderer在任何相机上都不可见时调用
6-7.结束阶段
- OnDisable()当不可用:对象变为不可用或附属游戏对象非激活状态时此函数被调
- OnDestroy()当销毁:当脚本销毁或附属的游戏对象被销毁时被调用
- OnApplicationQuit()当程序结束:应用程序退出时被调用
6-8.示例
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 脚本生命周期
/// </summary>
public class LIfeCycle : MonoBehaviour
{
//===================初始阶段===================
//执行时机:创建游戏对象时,立即执行1次 (与脚本禁用与否无关)
//作用:初始化
private void Awake()
{
Debug.Log("Awake()方法被调用了!");
}
//执行时机:每当脚本对象启用时调用,执行1次
//作用:初始化
private void OnEnable()
{
Debug.Log("OnEnable()方法被调用了!");
}
//执行时机:创建游戏对象时,且脚本未被禁用时,执行1次
//作用:初始化
private void Start()
{
Debug.Log("Start()方法被调用了!");
}
//===================物理阶段===================
//执行时机:每隔固定时间被调用,默认为0.02s(时间可以更改)
//适用性:适合对物体做物理操作(移动,旋转等),不会受到渲染的影响。
//因为每帧渲染物体量不固定机器性能不同,进而渲染的时间是不固定。
//总之记住不管怎么样FixedUpdate()每隔0.02s执行一次就行
private void FixedUpdate()
{
Debug.Log("FixedUpdate()方法被调用了!");
}
//===================游戏逻辑阶段===================
//执行时机:每渲染一帧执行一次(联系FixedUpdate()对应理解),渲染受到机器性能个每帧渲染量影响,执行间隔时间不确定
//适用性:处理游戏逻辑,但是现在大部分移动也是写这儿
private void Update()
{
Debug.Log("Update()方法被调用了!");
}
}
6-9.脚本生命周期图:
具体就是脚本的一些函数执行流程:
二、开发工具
1-MonoDevelop
MonoDevelop:Unity:Unity自带脚本编辑器,创建Mono应用程序,适用于Linux、Mac OS X和Windows的集成开发环境,支援C#、BOO和JavaScript等高级编程语言。
2-Visual Studio(推荐)
微软公司的开发工具包,包括了整个软件生命周期中需要的大部分工具,如团队开发工具、集成开发环境等等。
3-修改默认开发工具
- 在Unity中通过菜单设置修改默认的脚本编辑器:Edit一Preferences一External Tools一External Script Editor
- 如安装了visual studio选择即可,以后在unity打开脚本自动启动vs打开
三、常用API
1-Unity核心类图
注意看图中的MonoBehaviour,一般我们是继承MonoBehaviour来编写脚本
2-脚本的Component类
主要是获取游戏对象的组件,改变组件相关数值
2-1.操作unity编译器中的属性
主要是使用GetComponent()方法获取组件,然后在对组件的属性进行操作
- 创建一个脚本,并将脚本拖动到游戏对象上
- 编辑脚本内容如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Component类提供了查找(在物体、后代、先辈)组件的功能
/// </summary>
public class ComponentDemo : MonoBehaviour
{
//OnGUI()基本不使用,这里用于测试功能,会在运行时在屏幕上添加按钮
private void OnGUI()
{
if (GUILayout.Button("修改位置"))
{
//改变当前游戏对象的位置,修改transform的position属性
this.transform.position = new Vector3(0, 0, 10);
}
if (GUILayout.Button("修改BoxCollider大小"))
{
//获取游戏对象里边BoxCollider类型的组件,并将其size属性设置为1, 2, 3
this.GetComponent<BoxCollider>().size = new Vector3(1, 2, 3);
}
if (GUILayout.Button("GetComponents"))
{
//获取游戏对象里边Component类型的组件
var allComponents = this.GetComponents<Component>();
//遍历出我们找到的组件
foreach(var item in allComponents)
{
print("获取到的组件为:" + item.GetType());
}
}
}
}
- 点击修改位置按钮后,物体的transform的position属性变为代码中设置值
- 运行后查看,发现我们其实可以获取Inspector面板的组件信息,并加以修改
2-2.获取游戏对象后代和先辈的组件
- 将脚本拖拽到游戏对象上,然后创建后代cube(后代cube无脚本)
- 在脚本中添加:
if (GUILayout.Button("GetComponentsInChildren"))
{
//获取当前游戏对象后代物体的里边MeshRenderer类型的组件(从自身开始找起,包括自己)
var allComponents = this.GetComponentsInChildren<MeshRenderer>();
//遍历出我们找到的组件
foreach (var item in allComponents)
{
print("获取到后代的组件为:" + item.GetType());
}
}
if (GUILayout.Button("GetComponentsInParent"))
{
//获取当前游戏对象先辈物体的里边MeshRenderer类型的组件(从自身开始找起,包括自己)
var allComponents = this.GetComponentsInParent<MeshRenderer>();
//遍历出我们找到的组件
foreach (var item in allComponents)
{
print("获取到后代的组件为:" + item.GetType());
}
}
- 运行点击对应按钮,可以发现获取到了相应组件
3-脚本的Transform类
主要是设置游戏对象物体的旋转缩放位置等等,可以制作游戏移动之类的操作
3-1.获取子对象变换组件
- 还是只在cube上设置脚本
- 将下列内容写入脚本
if (GUILayout.Button("foreach-transform"))
{
foreach (Transform child in this.transform)
{
//遍历出当前对象每个子物体的变换组件,孙子类就不行了
print("获取到子物体的变换组件为:" + child.name);
//物体相对与世界坐标系的位置
//this.transform.position;
//物体相对于父物体轴心点
//this.transform.localPosition;
//相对与父物体的缩放比例 1 2 1(xyz轴比例)
//this.transform.localScale;
//this.transform.lossyScale
//如:父物体localScale为3当前物体localScale为2
//lossyScale则为6
//理解为:物体与模型缩放比例(自身缩放比例*父物体缩放比例)
//this.transform.lossyScale
//如:父物体localScale为3当前物体localScale为2
//lossyScale则为6
}
}
- 查看结果,只打印出子类的名字,子类的子类(孙子类)则不会打印。
3-2.改变对象的坐标-1
- 还是脚本作用在cube上
- 添加脚本内容如下:
if (GUILayout.Button("z轴移动1m"))
{
//向自身坐标系z轴,移动1m
this.transform.Translate(0, 0, 1);
}
if (GUILayout.Button("y轴旋转1m"))
{
//沿着自身坐标系y轴旋转10度
this.transform.Rotate(0, 10, 0);
//沿着世界坐标系y轴旋转10度
this.transform.Rotate(0, 10, 0, Space.World);
}
- 点击之后可以发现物体移动
3-3.改变对象的坐标-2
- 还是将脚本作用与cube,内容如下:
public class ComponentDemo : MonoBehaviour
{
//传入一个物体的变换组件的引用,使得我们可以对其操作
public Transform tf;
private void OnGUI()
{
if (GUILayout.Button("GetParent-Transform"))
{
//获取父物体的变换组件
Transform parentTF =this.transform.parent;
}
if (GUILayout.Button("SetParent-true"))
{
//将传入的Transform设置当前对象的父物体
//true:当前物体的位置视为世界坐标,参照物为世界坐标
this.transform.SetParent(tf, true);
}
if (GUILayout.Button("SetParent-false"))
{
//将传入的Transform设置当前对象的父物体
//false:当前物体的位置视为localPosition,参照物为父物体轴心点
this.transform.SetParent(tf, false);
}
}
}
- 查看新增一个Tf变量,这里Tf可以接收一个Transform
- 我们新建一个cube(6)传入它的Transform给cube,类似于(public Transform tf=“cube的Transform组件”)
- 运行,然后点击
SetParent-true
按钮,然后改变cube(6)的位置,发现cube和cube(6)做相对运动(其实这就是正常的子物体与父物体关系)。类比载具,相当于cube上了名为cube(6)的车。cube就跟着父物体cube(6)一起走了。 - 运行,然后点击
SetParent-false
按钮,会发现cube的坐标会直接变为cube(6)的坐标(即重叠了),然后拖动cube(6),发现cube不会再移动。查看cube的坐标为(0,0,0),查看cube(6)的坐标为(0,2,0),所以就是cube将cube(6)的位置作为了参照物而已
- 总结:
SetParent-true
就是正常的父物体与子物体的关系,子物体跟着父物体移动,无需记忆;SetParent-false
则是将子物体的Transform的参照点改变了,原本物体创建都是默认将世界坐标作为参照点,这里是直接将父物体的坐标设置为了参照点而已。
3-4.查找子物体的Transform
if (GUILayout.Button("FindChildTransform"))
{
//根据名称寻找子物体的Transform
Transform childTF=this.transform.Find("cube(1)");
//根据索引寻找子物体的Transform
//Transform childTF = this.transform.GetChild(0);
}
4-GameObject类
4-1.激活状态
- 激活状态对应到Inspector面板中的勾选框
- 相关脚本如下:
public class GameObjectDemo : MonoBehaviour
{
private void OnGUI()
{
//获取在场景中物体激活状态(物体实际激活状态)
//this.gameObject.activeInHierarchy
//获取物体激活状态,激活也有可能不显示。例如:父物体取消激活,子物体就算激活了也显示不出来
//this.gameObject.activeSelf
//设置物体的激活状态
//this.gameObject.SetActive()
}
}
4-2.创建GameObject
- 脚本内容如下:
public class GameObjectDemo : MonoBehaviour
{
private void OnGUI()
{
if (GUILayout.Button("添加光源组件"))
{
//创建物体
GameObject lightGo = new GameObject();
//添加组件
Light light=lightGo.AddComponent<Light>();
//设置光的颜色
light.color=Color.red;
//设置光的类型
light.type = LightType.Point;
}
}
}
- 将脚本拖拽到cube运行,点击按钮后,Hierarchy面板创建了一个GameObject正好是我们定义的光组件
下:
public class GameObjectDemo : MonoBehaviour
{
private void OnGUI()
{
if (GUILayout.Button("添加光源组件"))
{
//创建物体
GameObject lightGo = new GameObject();
//添加组件
Light light=lightGo.AddComponent<Light>();
//设置光的颜色
light.color=Color.red;
//设置光的类型
light.type = LightType.Point;
}
}
}
- 将脚本拖拽到cube运行,点击按钮后,Hierarchy面板创建了一个GameObject正好是我们定义的光组件
四、练习
1-修改血量最低的敌人的组件属性
- 创建名为
Enemy
的脚本
public class Enemy : MonoBehaviour
{
//血量
public float HP;
}
- 创建名为
FindEnemyDemo
的脚本
public class FindEnemyDemo : MonoBehaviour
{
private void OnGUI()
{
if (GUILayout.Button("查找血量最低的敌人"))
{
//寻找所有含有Enemy类型的引用
Enemy[] Enemies = FindObjectsOfType<Enemy>();
//找出血量最低的游戏对象的引用
Enemy min = FindEnemyByMinHP(Enemies);
//获取血量最低的敌人的Transform组件
Transform minTransform = min.GetComponent<Transform>();
//将血量最低的敌人positon设置为3,3,3
minTransform.position = new Vector3(3, 3, 3);
}
}
public Enemy FindEnemyByMinHP(Enemy[] Enemies)
{
//假设第一个就是血量最低的敌人
Enemy min = Enemies[0];
//依次与后面比较
for (int i = 1; i < Enemies.Length; i++)
{
if (min.HP > Enemies[i].HP)
{
min = Enemies[i];
}
}
return min;
}
}
- 在hierachy面板中创建6个cube,并将
Enemy
挂到6个cube上。然后给cube中的HP赋值。这样我们就可以开始找血量最低的游戏对象了
-
将名为
FindEnemyDemo
的脚本挂到Main Camera上,然后运行
-
点击按钮,发现血量最少的cube的positon变为了3,3,3。
2-变换组件查找工具类
public class TransformHelper : MonoBehaviour
{
/// <summary>
/// 在层级位置的情况下查找子物体
/// </summary>
/// <param name="parentTF"></param>
/// <param name="childName">子物体名称</param>
/// <returns></returns>
public static Transform GetChild(Transform parentTF, String childName){
//在子物体中查找
Transform childTF=parentTF.Find(childName);
//找到了返回引用
if (childTF!=null){
return childTF;
}
//将问题交给子物体
int count = parentTF.childCount;
for (int i = 0; i < count; i++){
childTF = GetChild(parentTF.GetChild(0), childName);
//找到了返回引用
if (childTF!=null){
return childTF;
}
}
//以上都没找到,就返回null
return null;
}
}