Unity脚本

official::Unity 脚本 API

VS提取方法:Ctrl + R && Ctrl + M


一、基本概念

定义:脚本是附加在游戏物体上用于定义游戏对象行为的指令代码。

添加:

  • 文件名必须与类名一致。
  • 写好的脚本必须附加到物体上才执行。
  • 附加到游戏物体的脚本类必须从MonoBehaviour类继承。

编译:源代码( CLS ) -> 中间语言( Mono Runtime )(中间产物是 .dll ) -> 机器码。


二、脚本

1 脚本操作

1.1 新建脚本默认

注意:

  • 在 Unity 中,默认情况下,脚本是没有命名空间的。这意味着在编写脚本时,不需要显式地添加命名空间声明,因为 Unity 编辑器会自动将所有位于 “Assets” 文件夹下的脚本视为全局命名空间下的一部分。
  • 例如,如果你在 Unity 中创建了一个名为 “PlayerController.cs” 的脚本,并且该脚本位于 “Assets/Scripts” 文件夹中,那么在该脚本中可以直接访问和使用其他位于 “Assets/Scripts” 文件夹中的脚本,而无需添加额外的命名空间声明。
  • 这种做法简化了 Unity 中脚本的编写和管理,但也可能导致命名冲突或代码整理不够清晰。因此,一些开发人员仍然选择在脚本中显式添加命名空间声明,以确保代码的可维护性和可扩展性。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour
{
// Start is called before the first frame update
// 初始化被调用
void Start()
{
}
// Update is called once per frame
// 约0.02秒被调用一次
void Update()
{
}
}

1.2 修改新建默认

默认模板存放位置:Unity\2021.3.5f1c1\Editor\Data\Resources\ScriptTemplates

1.3 开发工具设置

Edit——Preferences——External Tools——External Script Editor

1.4 脚本辅助操作

在Unity中,[RequireComponent] 是一个属性(Attribute),它用于确保一个MonoBehaviour脚本所附加的游戏对象上还存在其他指定的组件。当你在脚本类定义上方使用 [RequireComponent] 属性时,Unity引擎会自动检查并确保在运行时该游戏对象上存在指定的组件。如果没有,Unity会自动为游戏对象添加缺失的组件。

// 写在其他脚本中,写了后Untiy会自动判断该物体是否挂了MyScript,没有则挂上
[RequireComponent(typeof(MyScript))]

2 脚本生命周期

official::脚本API

脚本生命流程图

函数执行顺序 阶段 作用
Awake 物体初始化阶段 赋值
OnEnable 物体初始化阶段 赋值
Start 物体初始化阶段 赋值
FixedUpdate 物理计算阶段 物理物件计算
OnTriggerEnter 物理计算阶段 物理物件计算
OnCollisionEnter 物理计算阶段 物理物件计算
Update 游戏过程计算阶段 计算更新
LateUpdate 游戏过程计算阶段 Camera
OnDisable 物体退出阶段 禁用事件
OnDestroy 物体退出阶段 销毁事件

2.1 格式

类:字段+方法。

属性:一般不写。(编辑器中无法普通显示)

构造函数:不写。

2.2 字段

public 会显示可重新赋值。(要隐藏在字段上一行加上 [HideInInspector]

private不会显示。(要显示需要在字段上一行加上 [SerializeField]

[SerializeField]:(序列化字段)在编辑器中显示私有变量。(可以重新赋值)

[HideInInspector]:(隐藏检查器)在编辑器中隐藏公有变量。

[Range(n,m)]:限定数字范围为[n,m]

2.3 方法

// 初始化
// 都是在创建游戏对象时执行一次,但是执行完所有对象的Awake()后才执行所有对象的Start()
private void Awake();
private void Start();
// Awake:物体启用执行(仅一次)
// Start:脚本启用执行(仅一次)

private void OnEnable();
// OnEnable:脚本启用执行(每次)


// 持续更新
// FixedUpdate()每隔固定时间执行一次(时间可以修改),默认0.02s,不会受渲染的影响
// 适用于:对物体做物理操作(移动,旋转)
// 修改:Edit——Project Settings——Time——Fixed Timestep
private void FixedUpdate();

// Update()每个渲染帧执行一次,执行间隔不固定(受渲染影响)
//适用于:处理游戏逻辑
private void Update();

// 注:渲染时间不固定(每帧的渲染量不同)
// LateUpdate():在Update()执行完后执行一次(与Update处于同一渲染帧)
private void LateUpdate();


// 输入事件
OnMouseEnter(); // 鼠标移入到当前Collider时调用
OnMouseOver(); // 鼠标经过当前Collider时调用
OnMouseExit(); // 鼠标离开当前Collider时调用
OnMouseDown(); // 鼠标按下当前Collider时调用
OnMouseUp(); // 鼠标在当前Collider上抬起时调用


// 场景渲染
OnBecameVisible(); // 当Mesh Renderer在任何相机上可见时调用
OnBecameInvisible(); // 当Mesh Renderer在任何相机上都不可见时调用


// 结束阶段
OnDisable(); // 对象变为不可用或附属游戏对象非激活状态时此函数被调用
OnDestroy(); // 当脚本摧毁或附属游戏对象被销毁时被调用
OnApplicationQuit(); // 应用程序退出时被调用

3 调试

C#逆向工具ILspy:Library——ScriptAssemblies——脚本中间语言存储位置

控制台调试:

  • Windows——General——Console
  • Collapse:折叠相同项。

VS调试:

  1. F12——找到方法源头
  2. VS断点——VS启动调试——Unity开始游戏——回到VS中调试
  3. 调试——窗口——即时调试 / 自动调试 / 监视
// 在控制台中显示一条语句
Debug.Log("");
Debug.LogFormat(""); //格式化输出
// 在控制台中显示一条语句,只在脚本里用(属于MonoBehaviour的方法)
print();

// a到b用白线连接
Debug.DrawLine(a,b);

// 2D画线,距离地面距离
private void OnDrawGizmos()
{
Gizmos.DrawLine(transform.position, new Vector3(transform.position.x, transform.position.y - groundCheckDistance));
}

4 核心类图

三、常用API

1 Component

查找(在当前物体、后代、先辈)组件的功能。

this.GetComponent<name>():其他组件

获取所有组件名

var comp = this.GetComponents();
foreach (var i in comp)
{
Debug.Log(i);
}

获取父子组件

// 父
var comp = this.GetComponentsInParent<MeshRenderer>();
// 子
var comp = this.GetComponentsInChildren<MeshRenderer>();

2 Transform

查找(父、根、子)变换组件,改变对象的位置、旋转、缩放比例。

属性

// 对象在世界坐标系的位置
this.transform.position;
// 对象相对父物体的位置
this.transform.localPosition;
// rotation同理,scale的local同理(相对父物体)
// lossyscale:模型与自身的缩放比例(可理解为全局)

// 孩子数
this.transform.childCount;

方法

// y是高度轴,Space.Self:自身坐标轴,Space.World:世界坐标轴(Space默认self)
// 位置增减变化
this.transform.translate(x,y,z,space);
// 角度增减变化(四元数旋转)
this.transform.Rotate(x,y,z,space);
// 围绕点,按哪个轴,旋转角度
this.transform.transform.RotateAround(point,axis,angle);

// 物体在局部位置移动xyz后转化成世界坐标返回
worldPosition = this.transform.TransformPoint(x, y, z);

// 旋转,照相机的视口对准目标
this.transform.LookAt(Vector3);

获取父子物体的transform

// 子物体
foreach(Transform child in this.transform);
// 根物体
this.transform.root;
// 父物体
this.transform.parent;

// 认爸爸(参数2:坐标视为世界坐标(true,默认),本地坐标(false))
this.transform.setParent(transform , worldposition);

// 找孩孙(可以用于找孩子的孩子,但是不建议)
this.transform.Find("child's name [ / child's name ... ]");
// 找孩子
for (int i=0 ; i < this.transform.childCount ; i++ )
Transform tf = this.transform.GetChild(i) ;

// 解孩子
this.transform.DetachChildren();
// 摧毁孩子
Transform tf = screen.transform.GetChild(0);
Destroy(tf.gameObject);

3 GameObject

属性

// 实际激活状态(父可能被禁用但自身没有)
this.gameObject.activeInHierarchy;
// 自身激活状态
this.gameObject.activeSelf;

方法

// 设置(启用/禁用)
this.gameObject.SetActive(bool);
// 添加class类组件
this.gameObject.AddComponent<class>();

// 通过游戏名称找到物体(不建议使用)
GameObject.Find("name");
// 获取所有使用该标签的物体,返回数组
GameObject.FindGameObjectsWithTag( "tag" );
// 获取单个标签的物体
GameObject.FindWithTag( "tag" );

案例

// 例:找光
GameObject lightGo = new GameObject("light");
Light light = lightGo.AddComponent<Light>();

// 找子物体Object
private Transform pointLight;
pointLight = this.transform.GetChild(0);
pointLight.gameObject.SetActive(false);

4 Object

属性

方法

// 删除一个对象(对象,延迟时间)
Destroy(obj,delay);
// 加载新场景时该对象不被删除
DontDestroyOnLoad(obj);

// 找对象(单个/全部)
FindObjectOfType<class>();
FindObjectsOfType<class>();

5 Time

属性

// 秒计算,游戏开始的时间(只读)
Time.time;
// 秒计算,完成最后一帧的时间,渲染影响(只读)
Time.deltaTime;
// 秒计算,完成最后一帧时间,固定。
Time.fixedDeltaTime;
// 技巧:Update中为了同步可以用速度 * 每帧消耗时间
this.transform.Rotate(0,1*Time.deltaTime,0);

// 倍数,时间缩放,影响FixedUpdate更新速度(0:暂停,1:正常)
Time.timeScale;
// 倍数,不受缩放影响的每帧间隔
Time.unscaledDeltaTime;
// 实际游戏运行时间
Time.realtimeSinceStartup;

方法

案例

// 倒计时
// 1.Update 调用,判断当前时间和下一执行时间差。
// (例:发射子弹)
void TimeOne()
{
float time = Time.time;
if (time > this.nextTime && second > 0)
{
this.second--;
this.text.text = string.Format("{0:d2}:{1:d2}", this.second / 60, this.second % 60);
this.nextTime++;
if (second < 10)
{
this.text.color = Color.red;
}
}
}

// 2.Update 调用,判断每帧累计时间是否达到目标时间。
// (例:蓄力技能)
void TimeTwo()
{
this.countTime += Time.deltaTime;
if ( countTime >= 1 && second > 0)
{
this.second--;
this.text.text = string.Format("{0:d2}:{1:d2}", this.second / 60, this.second % 60);
if (second < 10)
{
this.text.color = Color.red;
}
countTime -= 1;
}
}

// 3.Start 调用,每固定时间执行一次。(InvokeRepeating)
// (例:固定刷新)
InvokeRepeating("TimeThree", 1, 1);
void TimeThree()
{
if(second > 0)
{
this.second--;
this.text.text = string.Format("{0:d2}:{1:d2}", this.second / 60, this.second % 60);
if (second < 10)
{
this.text.color = Color.red;
}
}
else
{
CancelInvoke("TimeThree");
}
}

6 Input

属性

方法

// 鼠标按键被按住时返回true(持续,按住时一直返回true)
bool res = Input.GetMouseButton(0);
// 用户按下鼠标第一帧时返回true(单帧)
bool res = Input.GetMouseButtonDown(0);
// 用户释放鼠标第一帧时返回true(单帧)
bool res = Input.GetMouseButtonUP(0);
// 0:左键,1:右键,2:中键

// 键盘按键被按住时返回true(
bool res = Input.GetKey(KeyCode.A);
// 用户按下键盘按键第一帧时返回true
bool res = Input.GetKeyDown(KeyCode.A);
// 用户释放键盘按键第一帧时返回true
bool res = Input.GetKeyUp(KeyCode.A);
// KeyCode.*:键盘按钮

// 判断虚拟轴是否被按下,同理上面,虚拟轴名在InputManage中设置
bool res = Input.GetButton("name");
bool res = Input.GetButtonDown("name");
bool res = Input.GetButtonUp("name");
// 获得虚拟轴名被按下后的值
// 存在中间值的返回,如0->1过程中返回了0.3
float value = Input.GetAxis("name");
// 只有-1,0,1的返回
float value = Input.GetAxisRaw("name");

案例

// 按C同时按D(要有一个down,否则会一直执行)
if(Input.GetKey(KeyCode.C)&&Input.GetKeyDown(KeyCode.D));

7 其他函数

唤醒

// 延时后执行。
void Invoke("MethodName" , "time");
// 重复执行。(方法,开始时间,间隔(单位秒))
InvokeRepeating("MethodName" , start_time , Repeat_rate );
// 取消重复。(不填方法名将停止当前脚本所有 Invoke 和 InvokeRepeating 方法)
CancelInvoke("MethodName");

8 常用API组成功能

// 获得物体材质
MeshRenderer meshRenderer = this.GetComponent<MeshRenderer>();
this.material = meshRenderer.materials[0];

// 改颜色
this.material.color = Color.yellow;

// 每帧更新,乘deltaTime依渲染移动
this.transform.Translate(0, 0, moveSpeed * Time.deltaTime);

// 不随镜头角度变化的移动
this.transform.Translate(0, 0, 0,Space.World);
// 随镜头角度变化的移动
this.transform.Translate(0, 0, 0,Space.Self);

// Update中动画要用IsPlaying判断,不能做成else
void Update()
{
if(anim.a){};
if(anim.b){};
}

四、动画

1 UI

using UnityEngine.UI;

2 Animation

脚本控制 Animation 时要把组件中的 Play Automatically 关闭。

属性

方法

// animation是对象,name是动画片段名称
// 是否正在播放
bool isPlay = anim.isPlaying;
bool isPlay = anim.isPlaying("name");

// 立刻播放动画
anim.Play("name");
// 播放队列,一个一个播放
anim.PlayQueued("name");

// 播放速度,0不播,1正常,-1倒着播
anim["name"].speed = num;
// 动画总长度
anim["name"].length;
// 当前播放时间
anim["name"].time;

// 淡入播放name且淡出其他动画
anim.animation.CrossFade("name");
anim.CrossFadeQueued("name");

// 停止播放动画
anim.stop();

案例

// 开关门
private void OnMouseDown()
{
if(!doorState)
{
// 开门
door[animName].speed = 1;
// 正着播不用设置时间,默认从0开始
}
else
{
// 关门
door[animName].speed = -1;
// 默认时间是0,倒着播要设置,否则是0->0
// 判断当前不在播放才从尾开始,否则直接当前位置倒着播
if(door.isPlaying == false)
door[animName].time = door[animName].length;
}
door.Play(animName);
doorState = !doorState;

异常

Legacy异常

事件没有绑定方法

// Legacy(遗产):2019版本后
1.动画——Inspector的Normal改Debug;
2.勾选Legacy;

3 Animator

属性

private Animator animator = GetComponent<Animator>();

方法

// 返回指定索引(Layers,从0开始)的动画状态机中当前状态的信息
animator.GetCurrentAnimatorStateInfo(idx);
// 返回当前索引对应的动画是否在播放
animator.GetCurrentAnimatorStateInfo(idx).IsName(name);

案例

// 判断Base Layer中的Walk动画是否播放
animator.GetCurrentAnimatorStateInfo(0).IsName("Walk")

4 导入MMD

  1. 插件:Author::Stereoarts Homepage
  2. 导入包。
  3. 导入pmx文件包,会在路径下自动生成一个.MMD4Mecanim文件,点击agree
  4. 配置pmx和vmd文件。
  5. 生成了一个可播放物件,该物件右边有一个播放按钮。
  6. 若模型显示不全则进入同名文本文件下修改<commentEn></commentEn>中的内容

5 音频

属性

// 音频切片
audio.clip;

// 多个音频切片(使用则是使用数组索引)
protected AudioClip[] audioClips;

方法

private audio audioSource = GetComponent<AudioSource>();

// 播放音频(一个音频播放完才能播放下一个)
audio.Play();
// 播放音频(上一个音频未播完也可以继续播放下一个)
audio.PlayOneShot(audio.clip);

案例

private AudioSource audioSource;
audioSource = GetComponent<AudioSource>();

audioSource.clip = Resources.Load<AudioClip>("bgm");

// 设置音频
audioSource.loop = true; // 循环
audioSource.volume = 0.5f; // 声音大小

// 播放新音频
audioSource.Play();
// 停止播放
audioSource.Stop();

五、三维数学

1 Mathf

属性

// π
Mathf.PI;

// 角度=>弧度
radian = angle * Mathf.Deg2Rad;
// 弧度=>角度
angle = radian * Mathf.Rad2Deg;

方法

// 绝对值
Mathf.Abs(num);
// 次方
Mathf.Pow(num,pow);
// 开方
Mathf.Sqrt(num);

// 三角函数(知道一边一角求另一边)
Mathf.Sin/Cos/Tan(float radian);
// 反三角函数(知道两边求角)
Mathf.Asin/Acos/Atan(float radian);
// 起点向终点移动。起终步长(1f为100%),返回移动后位置
point = Mathf.Lerp(start,end,step);

案例

// 弧度转
radian = angle * Mathf.PI / 180;
radian = angle * Mathf.Deg2Rad;
// 角度转弧度
angle = radian * 180 / Mathf.PI;
angle = radian * Mathf.Rad2Deg;

2 Vector

属性

// 静态变量
// 简写,向后
Vector3.back = new Vector3(0,0,-1);
// 依次类推还有
forward,back,up,down,left,right,one,zero;

// 变量
// 向量长
Vector3.magnitude
// 单位向量
Vector3.normalized
// 向量长的平方
Vector3.sqrMagnitude

方法

// 输入向量,返回夹角
angle = Vector3.Angle(from,to);
// 限制模长,超出了最大值会等比压缩
clampedVector = Vector3.ClampMagnitude(inputVector, maxMagnitude);

// 点乘
vector = Vector3.Cross(a,b);
// 叉乘
vector = Vecotr3.Dot(a,b);
// 向量距离
vector = Vecotr3.Distance(a,b);

// 将A标准化并返回两根与A相互垂直的向量
vector3.OrthoNormalize(ref basisA,ref returnB,ref returnC);
// A在平面上的投影
vector = Vector3.ProjectOnPlane(A,plane);
// 反射(入射向量,法线)
vector = Vector3.Reflect(inDirection,inNormal);

// 匀速移动
vector = Vector3.MoveTowards(src,dest,speed);
// 按speed移动
vector = Vector3.Lerp(src,dest,speed); // speed不超过1
vector = Vector3.LerpUnclamped(src,dest,speed); // speed会超过1

案例

// 自然的运动
public AnimationCurve curve; // 手动绘制曲线
private float x; // 曲线x值
public float duration; // 持续时间
if(GUILayout.RepeatButton("LerpUnclamped"))
{
x += Time.deltaTime / duration;
this.transform.position = Vector3.LerpUnclamped(Vector3.zero,targetPos,curve.Evaluate(x));
}

3 Quaternion

属性

// 四元数->欧拉角
Quaternion qt = this.transform.rotation;
Vector3 euler = qt.qulerAngles;

// 旋转角度为零的单位四元数,用来重置物体的旋转值。
Quaternion.identity;

方法

// 欧拉角->四元数
rotation = Quaternion.Euler(x,y,z);

// 角度差
float angle = Quaternion.Angle(rotationA, rotationB);

// 沿轴旋转
rotation = Quaternion.AngleAxis(angle, axis);
// 注视旋转
rotation = Quaternion.LookRotation(position);
// 匀速旋转
Quaternion newRotation = Quaternion.RotateTowards(currentRotation, targetRotation, maxDegreesDelta);
// 从A转到B
rotation = Quaternion.FromToRotation(A,B);

案例

// 缓慢旋转
if(GUILayout.RepeatButton("rotation"))
{
Quaternion dir = Quaternion.LookRotation(tf.position - this.transform.position);
this.transform.rotation = Quaternion.Lerp(this.transform.position,dir,0.1f);
}

4 向量

属性

二维向量的模长用公式自己算。

// 原点
Vector3.zero;

// 向量平方和的平方根
float m = vector.magnitude;
// 向量模平方
float m = vector.sqrMagnitude;
// 单位向量,向量方向,归一化,标准化
Vector3 n = pos.normalized;

运算

向量点乘:,反余弦角度在[0,180]之间。

向量叉乘:,Unity中方向左手规则。反余弦角度在[0,90]之间。

// 向量相减,各分量相减(结果的起点在原点,加同理。乘除只能对数值操作)
Vector3 vector = vector_x - vector_y;
// 向量点乘,常用于获得夹角(两标准化向量点乘就是夹角的余弦值)(返回值范围0-180)
float dot = Vector3.Dot(va,vb);
// 向量叉乘
Vector vector = Vector3.Cross(a,b);

方法

// UnityEngine和System.Numerics命名空间都有Vector3,需要明确引用的命名空间
using UnityEngine;

// 两点距离
Vector3.Distance(a,b);
// 将向量设为单位向量
vector.Normalize();

案例

// 1.求模长的三种方法
float m = Mathf.Sqrt(Mathf.Pow(pos.x,2)+Mathf.Pow(pos.y,2)+Mathf.Pow(pos.z,2));
float m = pos.magnitude;
float m = Vector3.Distance(Vector3.zero,pos);

// 2.获取向量的方向
Vector3 n = pos / pos.magnitude;
Vector3 n = pos.normalized;

// 3.沿方向移动(避免物体间距产生影响)
point.Translate(Direction.normalized);

5 旋转

概念

欧拉角:用三个角度来保存方位。(xz轴转沿自身旋转,y轴转沿世界旋转)

欧拉角的数据类型是Vector3,欧拉角没有大小和方向,仅因为有xyz所以用Vector3存储。

欧拉角优点:好使方便。

欧拉角弊端:

  1. 方位表达不唯一([250,0,0]和[290,180,180]是同一个角度)。所以为了保证唯一,用代码操作角度时,x限定在[-90,90],yz限定[0,360]。

  2. 万向节死锁。物体沿X轴旋转90度后,自身Z轴会与世界Y轴重合,此时再沿Y或Z旋转时将失去一个自由度。在次情况下,规定沿Y轴完成绕竖直轴的全部旋转,此时Z轴旋转为0。

四元数:3D中表示旋转,旋转都沿自身,分量如下(值域都是[-1,1]):

四元数优点:没有死锁。

四元数弊端:难用,不建议单独改某个值,存在不合法的四元数值。

属性

// 方向向量(没有下左后属性,想获得直接取负)
this.transform.up;
this.transform.right;
this.transform.forward;

// 欧拉角
Vector3 eulerAngle = this.transform.eulerAngles;

// 四元数
Quaternion qt = this.transform.rotation;

运算

// 两个四元数相乘可以组合旋转效果
Quaternion.Euler(0,50,0) = Quaternion.Euler(0,30,0) * Quaternion.Euler(0,20,0);

方法

// 欧拉角转换成四元数
this.transform.rotation = Quaternion.Euler(x,y,z);

案例

// 四元数原始设置
// 设置旋转轴
Vector3 axis = Vector3.up;
// 设置旋转弧度
float rad = 50 * Mathf.Deg2Rad;
// 设置四元数
Quaternion qt = new Quaternion();
qt.x = Mathf.Sin(rad / 2) * axis.x;
qt.y = Mathf.Sin(rad / 2) * axis.y;
qt.z = Mathf.Sin(rad / 2) * axis.z;
qt.w = Mathf.Cos(rad / 2);
this.transform.rotation = qt;

// 前方10m,30度位置
Vector3 rect = this.transform.position + Quaternion.Euler(0,30,0) * this.transform.rotation * new Vector3(0,0,10);
// 分步
// vect向量根据当前物体旋转而旋转
rect = this.transform.rotation * new Vector3(0,0,10);
// vect向量沿y轴旋转30度
rect = Quaternion.Euler(0,30,0) * rect;
// vect向量移动到当前物体位置
rect = this.transform.position + rect;

6 碰撞

概念

碰撞条件:两者都有碰撞组件,运动的物体有刚体组件。

速度不能过快,不然检测不到。一般100以下。

属性

// 获取第一个接触点
ContactPoint cp = other.contacts[0];

// 接触点的世界坐标
cp.point;
// 接触面法线
cp.normal;

方法

// 当进入碰撞时执行
void OnCollisionEnter(Collision other);
// 碰撞体与刚体接触时每帧执行
void OnCollisionStay(Collision other);
// 当停止碰撞时执行
void OnCollisionExit(Collision other);

案例

private void OnCollisionEnter(Collision other)
{
// 获取对方碰撞器组件
other.collider.GetComponent<>();
// 获取第一个接触点
ContactPoint cp = other.contacts[0];
}

7 触发

概念

带有碰撞器组件,且Is Trigger被勾选的物体。

现象:无碰撞效果。

触发条件:

  1. 两者具有碰撞组件。
  2. 其中之一带有刚体组件。
  3. 其中之一勾选Is Trigger。

速度不能过快,不然检测不到。一般100以下。

方法

// 当Collider进入触发器时执行
void OnTriggerEnter(Collider other);
// 碰撞体与触发器接触时每帧执行
void OnTriggerStay(Collider other);
// 当停止触发时执行
void OnTriggerExit(Collider other);

案例

private void OnTriggerEnter(Collision other)
{
// 就是对方碰撞器组件
other.GetComponent<>();
}

8 坐标系转化

属性

方法

// Local Space -> World Space
// 转换点,受位置、旋转、缩放影响
vector = transform.TransformPoint(position);
// 转换方向,仅受旋转影响
vector = transform.TransformDirection(position);
// 转换向量,受旋转和缩放影响
vector = transform.TransformVector(position);

// World Space -> Local Space
vector = transform.InverseTransformPoint(position);
vector = transform.InverseTransformDirection(position);
vector = transform.InverseTransformVector(position);

// 下列两个获得的坐标是作用于tag:Main Camera的摄像机(为了不重复找摄像机可以先创建Camera变量,再Camera.func())
// World Space <--> Screen Space
vector = Camera.main.WorldToScreenPoint(position);
vector = Camera.main.ScreenToWorldPoint(position);

// World Space <--> Viewport Space
vector = Camera.main.WorldToViewportPoint(position);
vector = Camera.main.ViewportToWorldPoint(position);

案例

// 获得屏幕坐标
Vector3 screenPosition = new Vector3(0.3f * Screen.width, 0.3f * Screen.height, 0f);
Vector3 worldPosition = Camera.main.ScreenToWorldPoint(screenPosition);

六、 UGUI

UI的进化:OnGUI——NGUI——UGUI

UI操作时,设置为2D,并且按 T(左上角第五个操作) ,便于进行UI物体的操作。

左键操作物体,右键操作画布。

1 Canvas

UGUI中的所有元素必须在Canvas之下。

Render Mode(画布模式):

  1. Screen Space - Overlay(屏幕空间 - 覆盖):
    • Canvas以屏幕为坐标系,覆盖在其他所有对象之上。
    • 不会受到场景中物体的遮挡,始终会显示在最前面。
    • 适用于出现在游戏界面上方的固定UI元素。
  2. Screen Space - Camera(屏幕空间 - 相机):
    • Canvas以相机为坐标系,并且渲染在相机的近裁剪面前。
    • 可以通过指定渲染的目标摄像机来控制可视范围。
    • 适用于与3D场景进行交互或需要基于相机进行渲染的UI元素。
  3. World Space(世界空间):
    • Canvas以世界坐标系进行绘制,可以放置在场景中的任意位置。
    • 可以根据世界坐标位置进行缩放、旋转等变换操作。
    • 注:在World Space模式下,通常需要借助Event System来处理交互事件。

Sort Order:

  • 同一画布下元素,后面的元素盖住前面的元素。
  • 不同画布下,Sort Order大的盖住小的。

Canvas Scaler(做任何UI前必做,否则后续界面会乱!):

  • UI Scale Mode:UI缩放模式:

    • Scale With Screen Size:设置参考缩放大小。
  • Match:缩放自适应。一般选小的那个(min(width,height)

技巧:

  • 3D的UI:让文体或图片贴在3D物体上。首先设置相机为World Space模式,然后该Scale为(0.01,0.01,0.01)。然后改变Image或Text的物件的位置,让其和物体重合。之后移动相机就会发现它们一直是重合的。

2 简单组件

通用属性:

  • Raycast Target:是否事件检测。

Panel:

  • 一个界面就是一个Panel。切换界面是在同一个Canvas下切换不同Panel。

TextMeshPro:

  • 安装:Window——TextMeshPro——Import TMP Essential Resources
  • Rich Text:可以写部分html标签改变字的样式。
  • 字体格式:.ttf——右键——Cteate——TextMeshPro——Font Asset

Button:

  • 在Button绑子物体Text完成一个简单按钮制作。
  • Interactable:是否可交互。
  • Transition:不同情况下按钮的变化。
  • Navigation:一般没用,设置None可以解决点击后颜色显示不正常的BUG。

Toggle:

  • 复选框。
  • Toggle——Background:背景框样式。
  • Toggle——Background——Checkmark:选中样式。
  • Toggle——Label:文本样式。

  • Is On:是否选中。

Slider:

  • 滑动条。

  • 做血条可以点Interactable不允许交互。

Scrollbar:

  • 滚动条。

Dropdown:

  • 下拉框。

Image

格式:图片——Texture Type——Sprite。

Image Type:图片类型

  • Filled:类似技能冷却。(Fill Amount:转圈)

Set Native Size:根据图片大小设置大小。

优化原理:界面中默认一张图片一个Draw Calls,同一张图多次显示仍是一个Draw Calls。所以可以把多个小图放到一个大图中,这样就是一个Draw Calls。

Sprite打包步骤如下:

  1. Window——Package Manager——Packages:Unity Registry——2D Sprite——Install
  2. Edit——Project Settings——Editor——Sprite Packer——Mode设置为Enabled For Builds 或 Always Enabled(建议第一个。第一个是仅项目打包时检查图片变化,第二个是每次都会检查图片变化。)
  3. Create——2D——Sprite altas。
  4. 将图片添加到图集中。

Sprite切割步骤如下(图集不可切割):

  1. Sprite Mode——Multiple
  2. Sprite Editor
  3. 左上角第二个箭头——Type(切割模式)——切割后改名(若名字错误不可在文件中直接改,而是按此步骤重新在编辑器中改)
// 新版加载图集
SpriteAtlas sprites = Resources.Load<SpriteAtlas>("path");

// 获取图集中元素(打包时无法通过编译)
foreach (Sprite sprite in sprites.GetPackables()){}

// 获取图集中元素
static ResourcesManager()
{
spriteDict = new Dictionary<int, Sprite>();

Sprite[] sprites = LoadSpritesFromAtlas("People");
foreach (Sprite sprite in sprites)
{
// 去除(Clone后缀)
sprite.name = sprite.name.Replace("(Clone)", "");
int id = int.Parse(sprite.name);
spriteDict.Add(id, sprite);
}
}
// 加载 SpriteAtlas 中的精灵
private static Sprite[] LoadSpritesFromAtlas(string atlasName)
{
Sprite[] sprites = null;

// 加载 SpriteAtlas
SpriteAtlas spriteAtlas = Resources.Load<SpriteAtlas>(atlasName);
if (spriteAtlas != null)
{
// 获取 SpriteAtlas 中的所有精灵
sprites = new Sprite[spriteAtlas.spriteCount];
spriteAtlas.GetSprites(sprites);
}
return sprites;
}

3 Rect Transform

概念

Pos:控件轴心点相对于自身锚点的位置。

Pivot:控件轴心点位置。

锚点:合并会进行位置自适应,分开会进行大小自适应。(点击middle center图像可以选择锚点位置)

属性

// 画布为overlay时,世界坐标等同于屏幕坐标
this.transform.position;
// 当前轴心点相对于父UI的轴心点位置
this.transform.localPosition;

// 获得组件
RectTransform rtf = GetComponent<RectTransform>();
RectTransform rtf = this.transform as RectTransform;
// 等同transform
rtf.position;rtf.localPosition;
// 自身轴心点相对于锚点位置
vector3 rtf.anchoredPosition3D;
// 设置锚点
vector2 rtf.anchorMin;
// 中心点
rtf.pivot;

// 获取UI宽高
rtf.rect.width;
rtf.rect.height;
// 物体大小 - 锚点间距
vector2 rtf.sizeDelta;

// 官方文档搜索查询其中的方法
RectTransformUtility

方法

// 设置宽高(设置方式,宽或高)
rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 250);

4 事件

绑定事件:

  1. 编辑器绑定:
    1. 把脚本拖到物体上
    2. 把物体拖到按钮事件中
    3. 找到函数并添加即可(函数可以有参数string)
  2. AddListener
  3. 实现接口
  4. 自定义框架

管理事件:

  • Event System(Script):管理事件
  • Standalone Input Module(Script):分发和检测事件(作用于PC和移动)
  • Graphic Raycaster(Script):检测事件

接口事件

使用:

  1. 在类后面继承对应的事件类
  2. Ctrl + .后接回车,会自动生成一个函数
// 鼠标指针类
IPointerEnterHandler;
IPointerExitHandler;
IPointerDownHandler;
IPointerUPHandler;
IPointerClickHandler;

// 拖拽类
IBeginDragHandler;
IDragHandler;
IEndDragHandler;
IDropHandler;

// 点选类
IUpdateSelectedHandler;
ISelectHandler;
IDeselectHandler;

// 输入类
IScrollHandler;
IMoveHandler;
ISubmitHandler;
ICancelHandler;

案例

// 2.AddListener
// 找到物体
Button btn = this.transform.Find("Button").GetComponent<Button>();
// 绑定事件(参数是函数,不同事件函数要求不同)
btn.onClick.AddListener(Func);


// 3.实现接口(引用事件类,编写事件类函数,然后把脚本给有Raycast Target的物体)
// 双击事件(IPointerClickHandler)
public void OnPointerClick(PointerEventData eventData)
{
if(eventData.clickCount == 2) { }
}

// 拖拽事件(鼠标会自动居中)
public void OnDrag(PointerEventData eventData)
{
// 仅限相机是overlay模式
this.transform.position = eventData.position;

// 通用的拖拽
RectTransform parentRTF = this.transform.parent as RectTransform;
Vector2 worldPos;
// (父物体坐标,事件坐标,相机(选press,enter会出错),输出坐标)
RectTransformUtility.ScreenPointToLocalPointInRectangle(parentRTF, eventData.position, eventData.pressEventCamera, out worldPos);
this.transform.localPosition = worldPos;
}

// 拖拽鼠标不居中
public class EventDemo : MonoBehaviour, IPointerDownHandler, IDragHandler
{
private RectTransform parentRectTransform;
private Vector2 offset;

void Start()
{
parentRectTransform = transform.parent as RectTransform;
}
public void OnPointerDown(PointerEventData eventData)
{
// 计算鼠标点击位置与控件中心的偏移量
offset = (Vector2)transform.position - eventData.position;
}

public void OnDrag(PointerEventData eventData)
{
Vector2 localPos;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(parentRectTransform, eventData.position, eventData.pressEventCamera, out localPos))
{
// 将鼠标点击位置与偏移量应用到控件的位置上
transform.localPosition = localPos + offset;
}
}
}

5 iTween

概念

下载:official::iTween下载

导入:Window——Package Manager——Packages:My Assets——Download——Import

目的是轻松实现各种动画。

方法

// 1.物体移动
iTween.MoveTo();
// 2.颜色渐变
iTw2een.ColorTo();
// 3.物体淡入淡出
iTween.FadeTo();
// 4.摄像机淡入淡出
iTween.CameraFadeAdd();
iTween.CameraFadeTo();
// 5.注视旋转
iTween.LookTo();
// 6.物体旋转
iTween.RotateTo();
// 7.物体缩放
iTween.ScaleTo();
// 8.晃动
iTween.PunchPosition();
iTween.PunchRotation();
iTween.PunchScale();
// 9.震动
iTween.ShakePosition();
iTween.ShakeRotation();
iTween.ShakeScale();

// 10.hash值,自己设置动画参数(参数都是逗号隔开)
iTween.Hash();

案例

// 平缓移动,绑定在事件中调用一次即可
public class ITweenDemo : MonoBehaviour
{
public Transform imgTF, btnTF;
public float moveSpeed = 100;
public void DoMovement()
{
// 调用一次即可
iTween.MoveTo(imgTF.gameObject, btnTF.position, 2);
}
}

// 利用hash设置参数
public iTween.EaseType type;
public void DoMovement()
{
// 调用一次即可
iTween.MoveTo(imgTF.gameObject, iTween.Hash(
"position",btnTF.position,
"speed",moveSpeed
"easetype",type
));
}

// 缩放由0->1
public void CreateEffect()
{
iTween.ScaleFrom(gameObject, Vector3.zero, 0.3f);
}

// 动画结束时调用函数
iTween.MoveFrom(imgTF.gameObject,iTween.Hash(
"position",btnTF.position,
"speed",moveSpeed,
"easetype",type,
// 完成动画时调用的函数
"oncomplete","Func",
// 函数所在的对象
"oncompletetarget",gameObject
));

private void Func()
{
print("over!");
}

七、存储

1 PlayerPrefs

概念

持久的在本地进行存储。

方法

// 保存数据
PlayerPrefs.SetFloat(key,value);
PlayerPrefs.SetInt(key,value);
PlayerPrefs.SetString(key,value);

// 读取数据
PlayerPrefs.GetFloat(key,default);
PlayerPrefs.GetInt(key,default);
PlayerPrefs.GetString(key,default);

// 删除数据
PlayerPrefs.DeleteAll();
PlayerPrefs.DeleteKey(key);

// 判断存在键
PlayerPrefs.HasKey(key);

案例

...

2 SceneManager

概念

场景切换。

方法

// 加载场景
SceneManager.LoadScene("SceneName");

3 外部资源加载

Resources

// 加载Resources文件夹中的内容
Sprite Resources.Load<Sprite>("path");



// 加载Resources文件夹中的图集
// 老版打包
Sprite[] Resources.LoadAll<Sprite>("path");
// 新版图集
SpriteAtlas sprites = Resources.Load<SpriteAtlas>("path");
foreach (Sprite sprite in sprites.GetPackables()){}

AssetDatabase

// 加载Assets文件夹中的内容
string assetPath = "Assets/Folder/FileName.extension";
GameObject asset = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);

八、Component

1 Rigidbody 2D

概念

2D刚体

属性

private Rigidbody2D rb = GetComponent<Rigidbody2D>();

// 刚体的速度矢量
rb.velocity = new Vector2(x,y);

// 力的方式
ForceMode2D.Force;
/*这种力的方式是通过应用持续的力来改变物体的速度。例如,使用这种方式可以为角色施加一个持续的推力,使其沿着某个方向移动。*/

ForceMode2D.Impulse;
/*这种力的方式是通过一次性的冲量来改变物体的速度。例如,在玩家跳跃时可以使用这种方式为角色施加一个瞬间冲击力,使其获得垂直的向上速度。*/

ForceMode2D.VelocityChange;
/*这种力的方式是通过改变物体的速度来实现所需效果。例如,如果要使物体突然停止或改变方向,可以使用这种方式为物体施加一个速度变化的力。*/

ForceMode2D.Acceleration;
/*这种力的方式是通过施加一个持续的加速度来改变物体的速度。例如,在模拟重力场景中,可以使用这种方式为物体施加一个持续的向下加速度,以模拟物体受到地球引力的影响。*/

方法

// 给刚体施加力(力的大小方向,力的方式)
rb.AddForce(Vector2.up, ForceMode2D.Impulse);

案例

// 跳跃
rb.AddForce(Vector2.up * jump * jumpSpeed, ForceMode2D.Impulse);

2 NavMeshAgent

概念

2D刚体

属性

private NavMeshAgent agent = GetComponent<NavMeshAgent>();

// 是否停止寻路
agent.isStopped = true;

方法

// 设置可走区域。值为2^n。所以当value=9时表示选择第1,4区域。
// Everything = -1; Nothing = 0;
agent.areaMask = value;

3 Line Renderer

概念

线渲染,常用于射线。

Materials:材质。

Positions:

  • Size:点数
  • Element:点坐标

Paramters

  • Start:开始属性。
  • End:结束属性。

案例

// 射线
line = GetComponent<LineRenderer>();
if(true)
{
line.enabled = true;
// 设置点坐标(第几个点,坐标)
line.SetPosition(0,startPosition);
line.SetPosition(1,endPosition);
}
else
{
line.enabled = false;
}

九、其他

1 NGUI

$95购买:official::NGUI

绑定事件在物体中绑定。

// 找到父物体下的按钮
public class ButtonClickHander : MonoBehaviour
{
private void Start()
{
for(int i = 0;i < transform.childCount; i++)
{
transform.GetChild(i).GetComponent<UIButton>().onClick.Add(new EventDelegate(OnButtonClick));
}
}

public void OnButtonClick()
{
print("Button name is" + UIButton.current.name);
}
}

2 协程

类似python迭代器。

多个协程可以同时运行,它们会根据各自的启动顺序来更新。

协程可以嵌套。

多个脚本访问一个协程,协程可以定义为静态协程。

协程是单线程。

协程的方法不能带ref和out型参数。

方法

// 定义协程(到yield处返回后,下次从yield后面开始执行代码)
// Func不能是Update
private IEnumerator Func()
{
yield return n;
yield return n;
}

// 等待一帧
yield return n;
// 等待指定时间,受时间缩放影响(单位:s)
yield return new WaitForSeconds(time);
// 等待指定时间,不受时间缩放影响(单位:s)
yield return new WaitForSecondsRealtime(time);
// 等待FixedUpdate结束
yield return new WaitForFixedUpdate();
// 等待该帧结束
yield return new WaitForEndOfFrame();

// 等待网络请求完成
WWW www = new WWW(url);
yield return www;

// 开启协程,返回值是一个协程
StartCoroutine(func(para));
// 停止协程,只能停止StartCoroutine()开启的协程
StopCoroutine(func(para));
// 停止本对象中开启的所有协程
StopAllCoroutines();

案例

// 游戏开始后的倒计时
private void Start()
{
StartCoroutine(CountDown(60));
}
private IEnumerator CountDown(int timeCount)
{
do{
print(timeCount);
yield return new WaitForSeconds(1);
timeCount -= 1;
} while (timeCount > 0);
print("Game Over!");
}

// 缓慢移动
private void OnGUI()
{
if(GUILayout.Button("Start"))
StartCoroutine(MoveToPath());
}
private IEnumerator MoveToPath()
{
foreach(Transform point in points)
{
yield return(StartCoroutine(MoveToTarget point.position);
yield return new WaitForSeconds(2);
}
}
private IEnumerator MoveToTarget(Vector3 target)
{
while(transform.position != target)
{
transform.position = Vector3.MoveToWards(transform.position,target,Time.deltaTime * speed);
yield return new WaitForFixedUpdate();
}
}

3 射线

概念

射线。

属性

// 射线
private Ray ray;
// 击中物体
private RaycastHit hit;

// 射线原点
ray.origin;
// 射线方向
ray.direction;

// 碰撞点
hit.point;
// 碰到的碰撞器
hit.collider;
// 射线原点到触碰点的距离
hit.distance;

方法

// 射线,起始位置是主摄像机,方向是鼠标位置
ray = Camera.main.ScreenPointToRay(Input.mousePosition);

// 是否碰撞到物体(射线,碰到的物体的信息,最远距离,图层)
public LayerMask layer;
Physics.Raycast(ray,out hit,distance,layer.value);
// (起点,方向,目标,最远距离,图层)
Physics.Raycast(origin,direction,out hit,distance,layer.value);

案例

// 小地图寻路(找子相机射线)
public Camera camera;
ray = camera.ScreenPointToRay(Input.mousePosition);

十、状态机

如下为Player框架

1 状态控制脚本

1.1 Player

...

1.2 PlayerState

...

1.3 PlayerStateMachine

...

2 普通状态

2.1 PlayerIdleState

...

2.2 PlayerMoveState

...

3 超级状态

一个超级状态可以有其他不同的普通状态存在。例如:接地状态和空中状态。接地可以是闲置和移动。因此接地是一个超级状态。

3.1 PlayerGroundState

...

3.2 PlayerAirState

...