一、脚本书写规则
脚本:.cs的文本文件 类文件
作用:附加到游戏物体中,定义游戏对象行为指令的代码
与C#类的区别:
脚本只有字段和方法,没有自动属性和构造函数
public int A { get { return a; } set { a = value; } }
属性定义了在unity中不会显示
public Lifecycle()
{
Debug.Log("构造函数")
//b=Time.time;
}
不要在脚本中写构造函数,因为不能在子线程中访问主线程成员
public 定义字段 编译器中会看到,private不会看到,如果想进行其他操作如下:
//序列化字段 作用:在编译器中显示私有变量
[SerializeField]
private int a;
//作用:在编译器中隐藏字段
[HideInInspector]
public float b;
//作用:限制范围
[Range(0,100)]
public int c;
二、脚本生命周期(必然事件)
Unity脚本从唤醒到被销毁的过程
消息:当满足某种条件Unity引擎自动调用的函数
Unity中生命周期是单线程,先做一个再做一个
1.初始阶段
//执行时机:创建游戏对象(物体被载入时) 立即执行1次(早于Start)
//作用:初始化 (构造函数的功能来赋值)this.enable=true,可以来判断是否创建对象
private void Awake()
{
Debug.Log("Awake--" + Time.time+"--"+this.name);
}
//执行时机:创建游戏对象(物体被载入时) --》脚本启用--》才执行(1次)
//作用:初始化
private void Start()
{
Debug.Log("Start--" + Time.time+"--"+this.name);
}
//执行时机:每当脚本对象启用时调用
private void OnEnable()
{
}
在初始阶段肯定是先Awake再Start,多个物体也是多个物体Awake执行完毕后再执行Start
2.物理阶段
//执行时机:脚本启用后,固定时间被执行。0.02s
//适用性:适用于对游戏对象做物理操作(移动,旋转),不会受到渲染影响
//渲染时间不固定是因为每帧渲染量不同,机器性能不同
private void FixedUpdate()
{
}
3.输入事件
//执行时机:OnMouseEnter 鼠标移入 OnMouseOver 鼠标经过 OnMouseExit 鼠标离开: OnMouseDown鼠标按下 OnMouseUp 鼠标抬起
private void OnMouseDown()
{
Debug.Log("OnMouseDown" );
}
4.游戏逻辑
//执行时机:渲染帧执行,执行间隔不固定
//适用性:处理游戏逻辑
private void Update()
{
}
//在Update函数被调用后执行(但在同一帧)
//适用于跟随逻辑(相机)
private void LateUpdate()
{
}
5.场景渲染
//执行时机:当Mesh Renderer在任何相机中可见时被执行 被看见那一帧执行
private void OnBecameVisible()
{
}
//执行时机:当Mesh Renderer在任何相机中不可见时被执行
private void OnBecameInvisible()
{
}
6.结束阶段
//执行时机:对象变为不可用或附属游戏对象非激活状态时此函数被调用
private void OnDisable()
{
}
//执行时机:当脚本或附属游戏对象被销毁时执行
private void OnDestroy()
{
}
//执行时机:当程序结束,应用程序退出时被调用
private void OnApplicationQuit()
{
}
三、脚本调试
1.简单调试
①.控制台调试
Debug.Log(变量) print(变量)
②.定义共有变量,程序运行后在检测面板查看数据
2.复杂调试
①.不是在Update中时:先加断点,再F5 调试过程中,输入代码:右键--快速监视 查看即时窗口
②.在Update中,单帧调试:启动调试,运行场景 暂停游戏 加断点 单帧执行 结束调试
四、常用API
MonoBehaviour父类成员与函数都可以使用
1.Component类
component类提供了查找(在当前物体、后代、先辈)组件的功能
①GetComponent:如果游戏对象附加有type类型的组件,则返回,如果没有则为空
GetComponent<MeshRenderer>().material.color = Color.red;
②GetComponents:获取游戏对象所有组件
var allComponent = this.GetComponents<Component>();
③GetComponentsInChildren:获取游戏对象后代组件
//获取后代物体的指定类型组件(从自身开始)
//后代中有某个组件,不知道是哪个的情况下使用
var allComponentChildren = this.GetComponentsInChildren<MeshRenderer>();
foreach ( var child in allComponentChildren)
{
child.material.color = Color.red;
}
④GetComponentsInParent:获取先辈物体的指定类型组件(从自身开始)
var allComponentParent = this.GetComponentsInParent<MeshRenderer>();
foreach (var parent in allComponentChildren)
{
parent.material.color = Color.red;
}
2.Transform类
Transform类提供了查找(父、根、子)变换组件功能,改变位置、角度、大小功能
①查找变换组件
if (GUILayout.Button("foreach--transform"))
{
foreach (Transform child in this.transform)
{
//child 为每个子物体的transform组件
print(child.name);
}
}
if (GUILayout.Button("root"))
{
//获取根物体的Transform组件 根物体就是最顶层的根源的游戏对象
Transform rootTF = this.transform.root;
}
if (GUILayout.Button("parent"))
{
//获取父物体的Transform组件
Transform parentTF = this.transform.parent;
}
if (GUILayout.Button("SetParent"))
{
//设置父物体 将该游戏对象的父Transform设置为tf,
//默认设置true 则原先该游戏对象的transform为世界坐标系,体现为不发生变化
//若为false,则原先该游戏对象的transform为local坐标系,会相对于父物体的transform进行相应变化
transform.SetParent(tf,false);
}
if (GUILayout.Button("findchild"))
{
//根据子物体的名称获取子物体的Transform组件
Transform childTF = transform.Find("子物体名称");
//查找孙子
Transform childTF02 = transform.Find("子物体名称/子物体名称");//确保路径不会发生改变,建议不这样写
}
if (GUILayout.Button("Getchild"))
{
//根据子物体的索引获取子物体的Transform组件
int count = this.transform.childCount;
for (int i = 0;i<count; i++)
{
Transform childTF = this.transform.GetChild(i);
}
}
练习:在层级未知的情况下找到特定名字的子物体(递归)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
///
/// </summary>
public class TransformHelper
{
/// <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(i), childName);
if (childTF != null)
return childTF;
}
return null;
}
}
②改变游戏对象位置、角度、大小功能
if (GUILayout.Button("pos/scale"))
{
foreach (Transform child in this.transform)
{
//物体相对于世界坐标系原点的位置
//this.transform.positiion
//物体相对于父物体的轴心点的位置
//this.transform.localposition
//相对于父物体缩放比例
//this.transform.localScale
//理解为:物体与模型缩放比例(自身缩放比例*父物体缩放比例)
//this.transform.lossyScale (只能读,想改通过localScale改)
}
}
if (GUILayout.Button("Translate"))
{
//向自身坐标系z轴移动1米
transform.Translate(0, 0, 1);
//向世界坐标系z轴移动1米
transform.Translate(0, 0, 1,Space.World);
}
if (GUILayout.Button("Rotate"))
{
//沿自身坐标系y轴旋转10度
transform.Rotate(0, 10, 0);
//沿世界坐标系y轴旋转10度
transform.Rotate(0, 10, 0, Space.World);
}
if (GUILayout.RepeatButton("Rotate"))
{
//围绕旋转
transform.RotateAround(Vector3.zero,Vector3.up,1);
}
3. GameObject类
activeSelf :只读,该游戏对象的局部激活状态,若父未激活,它局部激活,仍然未激活
activeInHierarchy:场景内游戏对象是否激活
设置物体启用/禁用:this.gameObject.SetActive(true);
添加组件步骤:
if (GUILayout.Button("添加光源"))
{
//添加组件步骤
//创建物体
GameObject lightGO = new GameObject();
//添加组件
Light light = lightGO.AddComponent<Light>();
light.color = Color.red;
light.type = LightType.Point;
}
查找物体方法:
//在场景中根据名称查找物体(不建议使用)
GameObject.Find("游戏对象名称")
//获取所有使用该标签的物体
GameObject[] allEnemy = GameObject.FindGameObjectsWithTag("Enemy");
//获取单个使用该标签的物体
GameObject playerGO = GameObject.FindGameObjectWithTag("player");
4.object类
Destory:删除一个游戏对象,组件或资源
Destory(GameObject,5)//5秒后消除
DontDestroyOnLoad:加载新场景的适合使目标对象不被自动销毁
FindObjectOfType:根据类型找第一个激活的对象
FindObjectsOfType:根据类型找所有激活的对象
练习:查找血量最低的敌人
首先给所有敌人挂上Enemy脚本,脚本只有一个公开字段HP
再定义新的脚本去查找:
using JetBrains.Annotations;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
/// <summary>
///查找HP最小的敌人
/// </summary>
public class FindMinHPEnemy : MonoBehaviour
{
private void OnGUI()
{
if(GUILayout.Button("查找血量最低敌人"))
{
//查找场景中所有Enemy类型的引用
Enemy[] allEnemy = Object.FindObjectsOfType<Enemy>();
//获取血量最低的对象的引用
Enemy min = FindEnemyByMinHP(allEnemy);
//根据Enemy组件类型引用,获取其他组件类型引用
min.GetComponent<MeshRenderer>().material.color = Color.red;
}
}
public Enemy FindEnemyByMinHP(Enemy[] enemies)
{
Enemy minEnemy = enemies[0];
for (int i = 1; i < enemies.Length; i++)
{
if (minEnemy.HP > enemies[i].HP)
{
minEnemy = enemies[i];
}
}
return minEnemy;
}
}
5.time类
①Time.time:从开始到目前
②Time.deltaTime:帧间消耗的时间
public void Update 每渲染帧执行一次,想要一个物体均匀旋转
public void Update()
{
this.transform.Rotate(0, 1 * Time.deltaTime, 0);
}
帧多,1秒旋转速度块,希望1帧旋转量小;帧少相反
旋转速度*每帧消耗时间,可以保证旋转速度不受机器性能和渲染影响
③Time.scaledTime:缩放游戏时间
public void FixedUpdate 固定0.02秒执行一次,可以直接写this.transform.Rotate(0, 1 , 0);
它与渲染无关,但受到TimeScale影响,Time.deltaTime也会受影响,只要在渲染帧*了
//游戏暂停 个别物体不受影响,在渲染更新里乘以Time.unscaledDeltaTime
练习:在屏幕上显示倒计时 02:00
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
///时钟显示
/// </summary>
public class PrinttSeconds : MonoBehaviour
{
public int second = 120;
private float timer = 0.0f;
private float nextTime = 0;
private TextMeshProUGUI txtTimer;
private void Start()
{
txtTimer = GetComponent<TextMeshProUGUI>();
//重复调用(被执行的方法名称,第一次执行时间,每次执行间隔)
InvokeRepeating("CalculateTime", 1, 1);
//Invoke(被执行方法名称,执行时间)
//可以延迟调用
}
public void Update()
{
操作Time.deltaTime
//DeltaTimer();
操作Time.time
//TimeTimer();
}
private void TimeTimer()
{
if (Time.time > nextTime)
{
CalculateTime();
nextTime = Time.time + 1;
}
}
private void DeltaTimer()
{
timer += Time.deltaTime;
if (timer >= 1.0f)
{
CalculateTime();
timer = 0.0f;
}
}
private void CalculateTime()
{
txtTimer.text = string.Format("{0:d2}:{1:d2}", second / 60, second % 60);
second--;
if (second < 10)
{
txtTimer.color = Color.red;
}
if (second < 0)
{
CancelInvoke("CalculateTime");//取消调用
}
}
}
隔一段时间调用方法的方法:
①InvokeRepeating():每隔固定时间执行
若不是固定时间或者必须在UpDate中实现使用以下方法:
②Time.time:适合满足另外条件立马执行(先做再等),例如:按下鼠标发射子弹
③Time.deltaTime:适合到满足条件后等待规定时间再执行(先等再做),例如:到一个点等待再去下一个点。
五、小知识
预制件:如果一个游戏对象在多个场景中可以使用,将其拽到Project中
对预制件修改会对所有在场景中的游戏对象进行修改
若对场景中游戏对象进行修改了点Apply,会对所有使用预制件进行修改,若不点Apply,修改的地方就不归预制件管
若点击Select,会找到预制件位置
若点击Revert,会返回到预制件的属性