前言

引擎就不介绍了

由于U3D的各种诡异的操作,我对于U3D的学习将只会停留在会用就行,重点在UE上

当然,我相信,其实都是差不多的

引擎安装

叨叨叨

现在国内能下载到非团结的只有2022了,Unity 6不再向大陆提供,取而代之的是团结引擎

常规安装通过Unity Hub进行,需要注册账号,安装时使用LTS版

还需要申请许可证才能使用,学习的话就只用个人版就行了,免费

至于国际版,就得用7根木棍飞到外面去,到官网上去下载,不然在点下载时还是会跳转到国内的下载界面

不过,使用国际版暂不清楚是否有问题,但学习吧,问题不大

中国官网: https://unity.cn/

国际官网: https://unity.com/

安装

添加版本时,可以选择一些额外的模块,是一些别的平台的开发包

可以选择Android, Windows, iOS,毕竟这三个算是常用的,其他的可能没有设备来测试

另外的文档Documentation,可以选上,查看一些说明

开始

创建项目

创建项目时,可以选择一些模板,比如2D,3D等,输入名称,选择项目保存的路径,点击创建即可

项目结构

  • Assets:存放资源文件,美术资源、脚本、预制体等都放在这里
  • Library:存放库文件
  • Logs:存放日志文件
  • obj:编译产生的中间文件
  • Packages:存放包配置信息
  • ProjectSettings:存放项目设置

基本界面

使用的话其实建议直接用英文的界面,好看文档和一些教程,有时候中文翻译可能存在问题

窗口布局

右上角的位置Layout,可以调整窗口布局

Hierarchy(层级)

Hierarchy窗口,存放场景中的所有物体

包括模型灯光摄像机UI

左上角的+或者鼠标右键都可以创建物体

一些快捷键:

  • Ctrl + D:直接克隆一个当前选择的物体(复制+粘贴)
  • F2:重命名当前选中的物体
  • Delete:删除当前选中的物体
  • Ctrl + C:复制当前选中的物体
  • Ctrl + V:粘贴当前复制的物体

Scene(场景)

Scene窗口,显示当前场景的视图

窗口上方栏从左到右依次为(2022版):

  • 枢轴选择,可选择物体的轴心,默认为Pivot,可以选择Center,即物体的中心
  • 坐标轴,可以选择物体的坐标轴,默认为Global,可以选择Local,即物体的局部坐标轴
  • 显示辅助线,即显示地平面上的网格线
  • 吸附模式,只能在全局坐标轴下启用
  • 吸附模式参数调整,按照特定的距离、角度进行调整
  • 渲染模式
  • 2D、3D切换
  • 灯光显示
  • 声音显示
  • 粒子等其他特效的显示
  • 场景可见性,即显示或隐藏场景中的物体
  • 场景摄像机设置
  • 其他的辅助功能设置

右上角的Persp是场景轴向,显示当前视图下的坐标轴方向

左侧栏是一些操作工具,对应的快捷键:

  • Q:平移
  • W:移动
  • E:旋转
  • R:缩放
  • T:2D上的操作
  • Y:综合(除了2D)上述功能

鼠标的一些操作:

  • 左键:选择物体
  • 左键 + Ctrl:添加选择物体
  • 框选:选择框选范围内的物体
  • 左键 + Alt + 拖动:相对视口中心点旋转
  • 左键选择物体 + F:最大化显示选择的物体
  • 右键 + 拖动:平移
  • 右键 + WASD:漫游场景
  • 右键 + WASD + Shift:快速漫游场景
  • 右键 + Alt + 拖动:相对屏幕中心拉近拉远
  • 中键滚动:拉近拉远
  • 中键 + 拖动:平移
  • 中键滚动 + Alt:按照鼠标位置拉近拉远

Game(游戏)

Game窗口,显示当前游戏运行时的视图,玩家可以看到的窗口

至少有一个摄像机才能在窗口中显示

在上方中央位置,有一个播放按钮,点击即可运行游戏;
第二个是暂停运行;第三个是逐帧运行

Game窗口中,上方位置的各栏:

  • 选择运行平台,Game为PC端,Simulator为模拟器,模拟在移动平台上的效果
  • 显示的显示器,一般就Display1
  • 显示的分辨率,可以自定义分辨率和比例
  • 显示缩放,缩放当前显示的内容
  • 运行时显示模式,全屏或者窗口模式
  • Show Frame Debugger,显示帧调试器
  • Mute Audio,静音
  • Unity Shorts,显示快捷键
  • Stats,显示帧率等统计信息
  • Gizmos,其他的显示设置,包括显示图标在内

Project(项目)

Project窗口,存放项目中的资源文件

默认文件夹Scene,存放场景文件,双击即可打开场景

官方拓展包Packages,存放项目使用的包

  • 左上角+创建资源文件,如C#脚本
  • 搜索栏,搜索资源文件
  • 搜索栏右侧第二个按钮(几何图形图标),按类型查找
  • 搜索栏右侧第三个按钮(标签图标),按名字查找

Inspector(检查器)

Inspector窗口,显示当前选中物体的属性(其实也是与之相关的C#脚本信息)

第一块部分:
游戏对象的基本设置

  • 左侧(默认是一个方块):打标签
  • 复选框:是否激活
  • 名称:物体的名称,可以重命名
  • Tag:标签,用于分类
  • Layer:层级,用于控制物体之间的交互
  • Static:是否为静态物体,静态物体不会在运行时被销毁,可以优化性能

后面的部分都是C#关联的脚本

  • Transform:与位置相关的脚本信息,改变物体的位置、旋转、缩放
  • Box Collider:碰撞器,用于检测物体之间的碰撞
  • Camera:摄像机,用于显示场景

Console(控制台)

Console窗口,显示游戏运行时的日志信息

默认是不开启的,在Window -> General -> Console中开启

快捷键Ctrl + Shift + C,可以快速打开

上方栏:

  • Clear,清空日志
  • 右侧选项,分运行时清理,构建时清理和编译时清理
  • Collapsed,折叠日志,折叠相同信息
  • Error Pause,错误暂停,当出现错误时暂停游戏
  • Editor,选择显示的日志信息
  • 搜索栏,搜索日志信息
  • 右侧三个图标,分别是是否显示错误、警告和普通打印信息,以及它们的计数

Toolbar(工具栏)

  • File:文件操作,如保存、导入、导出等
  • Edit:编辑操作,如撤销、重做、复制、粘贴等
  • Assets:资源操作,如创建资源、导入资源等
  • GameObject:游戏对象操作,如创建游戏对象、添加组件等
  • Components:组件(脚本)操作,如添加组件、移除组件等
  • Service:服务操作
  • Window:窗口操作,如打开窗口、关闭窗口等
  • Help:帮助操作,如查看帮助文档、访问官网等

一些快捷键:

  • Ctrl + Shift + F:移动物体到当前视角,常用于摄像机视角的设置
  • Ctrl + P:运行游戏
  • Ctrl + Shift + P:暂停游戏
  • Ctrl + Alt + P:逐帧运行游戏

父子级

Hierarchy窗口中,物体之间有父子级关系,子物体跟随父物体移动

在选择物体时右键新建就会创建该物体的子对象

变换父对象时,子物体的坐标也会随之改变

但操作子对象时,父对象不会随之改变

注意子对象的Transform组件中显示的是相对父对象的

Unity工作原理

反射机制

Unity使用反射机制来加载和执行C#脚本

游戏中的物体是GameObject,脚本继承自MonoBehaviour,通过反射机制来加载和执行脚本

GameObject会自带一个Transform组件,用于控制物体的位置、旋转、缩放

反射的体现:

  • 修改Inspector面板中的Transform的内容:利用反射,已知对象、类名、变量名,通过反射为该对象设置值
  • 新建一个脚本后,添加一个指定的GameObject对象:利用反射,已知类名,可以获取所有公共成员,所以在Inspector面板中可以显示该脚本的所有公共成员

游戏场景

游戏场景的后缀名是.unity,可以用记事本打开,其中存储了场景上的内容,包括设置、物体等

本质是一个配置文件,由unity引擎读取

预设体

预设体用来保存物体的信息,包括位置、旋转、缩放、组件等,然后可以在其他场景中使用这个物体

即便Hierarchy窗口中的物体被删除,只要保存了预设体,就可以在其他场景中重新使用这个物体

后缀名为.prefab

  • 创建预设体:
    Hierarchy窗口中,选中一个物体,直接拖入到Project窗口中,即可创建一个预设体

  • 使用预设体:
    直接将Project中的预设体拖入到Hierarchy窗口中,即可使用

  • 修改预设体:

    • Hierarchy窗口中,编辑了预设体之后,在Inspector窗口中,第一块内容会多一行,
      最右边的Overrides有两个选项,一个是重置所有,一个是应用到所有,前者会重置所有修改,后者会将修改应用到所有使用该预设体的物体
    • 另一种简单粗暴的方法是将修改后的预设体重新拖入Project窗口中,这样会覆盖原来的预设体
    • 删除预设体中的部分内容,需要先打开预设体,然后才能删除
  • 删除预设体:
    Project窗口中,选中预设体,直接删除

  • 独立预设体:
    Unpack Prefab,将预设体中的内容独立出来,这样就可以单独修改预设体中的内容,而不会影响到其他使用该预设体的物体

资源包导入导出

可以将Project窗口中的资源导出为包,然后在其他项目中使用

后缀是.unitypackage

也可以导入资源包,将其他项目中的资源导入到当前项目中

Unity 脚本

基本规则

  • 不直接在VS中创建脚本
  • 可以在Assets文件夹中创建脚本
  • 类名和文件名必须一致,不然不能挂载
  • 不要使用中文命名
  • 没有特殊需求,不用管命名空间
  • 创建的脚本默认继承MonoBehaviour

MonoBehaviour

MonoBehaviour是Unity中所有脚本的基类,所有的脚本都必须继承MonoBehaviour

  • 只有继承了MonoBehaviour的脚本,才能挂载到GameObject上
  • 继承了MonoBehaviour的脚本,可以挂载到GameObject上,但不能new
  • 继承了MonoBehaviour的脚本不要写构造函数
  • 继承MonoBehaviour的脚本,可以在一个GameObject上挂载多个(如果没有DisallowMultipleComponent特性,默认可以挂载多个,但过多的脚本会影响性能)
  • 继承了MonoBehaviour的脚本,可以再被继承(但是要避免层层继承)

生命周期函数

生命周期函数是MonoBehaviour中的一些特殊函数,它们在游戏运行时会自动调用

关于帧

  • Unity 底层已经做好了游戏的循环
  • 需要我们来学习它的生命周期函数,用它的规则来执行游戏的相关逻辑

生命周期函数

  • 所有继承MonoBehaviour的类都有这些生命周期函数
  • 生命周期函数是该脚本依附的GameObject在游戏运行过程中,从初始化到销毁会自动调用的一些函数
  • Unity会帮助我们记录一个GameObject对象依附了哪些脚本,在运行过程中会自动得到这些对象,并通过反射去执行一些固定名字的函数
  • 生命周期函数一般是private或protected的,因为它们是Unity自动调用的,我们不需要手动调用
  • 生命周期函数的名字是固定的,不能自己定义

一些函数:

  • Awake:在类对象被创建时调用,只调用一次,用于初始化(类似于构造函数的存在)
  • OnEnable:依附的GameObject每次激活时调用
  • Start:在类对象被创建出来后,第一次帧更新之前被调用,只调用一次
  • FixedUpdate:物理帧更新,在每个物理帧调用一次,可以处理一些物理逻辑(物理帧的频率可以在项目设置的Time中调整,默认是每0.02秒调用一次,即50次每秒)
  • Update:每一帧调用一次,用于处理游戏逻辑
  • LateUpdate:每一帧调用一次,在Update之后调用,用于处理一些需要在Update之后处理的逻辑,一般是处理摄像机的逻辑
  • OnDisable:依附的GameObject每次被禁用时调用
  • OnDestroy:在类对象被销毁时调用,只调用一次,用于清理资源

生命周期函数可以根据需要选择性地实现,不需要全部实现

也支持继承和重写

打印信息

在Unity中打印信息可以使用Debug.Log,它会将信息打印到控制台;
同时,衍生的方法是Debug.LogWarningDebug.LogError,分别表示警告和错误

继承了MonoBehaviour的类,有一个专门的方法用来打印,print

Inspector窗口中可编辑的变量

Inspector窗口中显示的可编辑内容,就是脚本的公共成员变量

  • 私有和受保护的变量不会显示在Inspector窗口中,但可以使用[SerializeField]属性来显示私有变量
  • 公共的变量会显示在Inspector窗口中,但如果使用[HideInInspector]属性,则不会显示
  • 大部分类型都可以显示在Inspector窗口中,包括基本类型、字符串、枚举等
  • 自定义类需要使用[System.Serializable]属性才能显示在Inspector窗口中
  • 字典不论如何都无法显示在Inspector窗口中

一些辅助特性:

  • Header:标题,说明
  • Tooltip:鼠标悬停时显示的提示信息
  • Space:在变量之间添加空格
  • Range:用于限制数值类型变量的范围,并在Inspector窗口中显示为滑动条
  • Multiline:用于显示多行文本,默认为3行
  • TextArea:用于显示多行文本区域
  • ContextMenuItem:为方法添加右键菜单选项
  • ContextMenu:让方法能在Inspector窗口中右键调用

注意:

  • Inspector窗口中显示的变量就是该脚本的公共成员变量,运行时可以通过Inspector窗口修改这些变量的值
  • 将脚本拖到GameObject上后,再改变脚本中的默认值,界面上的值不会改变
  • 运行中修改的信息不会保存,停止运行后会恢复到之前的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum EType
{
Normal,
Player,
Monster
}

[System.Serializable]
public struct SData
{
public int id;
public string name;
}

[System.Serializable]
public class CData
{
public float value;
public bool isActive;
}

public class Lesson2 : MonoBehaviour
{
// 私有和受保护成员变量,不会显示在Unity Inspector中
private int i1;
protected string s1;

// 但使用[SerializeField]属性,可以让私有和受保护成员变量显示在Unity Inspector中
[SerializeField]
private string s2;
[SerializeField]
protected int i2;

// 公共成员变量,会显示在Unity Inspector中
public float f1;
public bool b1;

// 不过公共的变量也可以使用[HideInInspector]属性来隐藏
[HideInInspector]
public bool b2;
[HideInInspector]
public float f2;

// 大部分类型都可以显示在Unity Inspector中
public int[] array1;
public List<string> list1;
public EType type1;

// 字典、自定义类、结构体等复杂类型则不能显示在Unity Inspector中
// 但可以加上[System.Serializable]属性来实现显示(除了字典)
public Dictionary<int, string> dict1; // 不能显示
public SData struct1; // 可以显示
public CData class1; // 可以显示

// 其他的辅助特性
[Header("Header Example")] // 在Inspector中添加标题说明
public int headerExample;

[Tooltip("Tooltip Example")] // 在Inspector中添加鼠标悬停提示
public float tooltipExample;

[Space(10)] // 在Inspector中添加空白间距
public string spaceExample;

[Range(0, 100)] // 在Inspector中添加滑动条
public int rangeExample;

[Multiline(3)] // 在Inspector中添加多行文本框,指定行数
public string multilineExample;

[TextArea(3, 5)] // 在Inspector中添加可调整大小的文本区域,指定最小和最大行数
public string textAreaExample;

[ContextMenuItem("Reset Value", "ResetValue")] // 在Inspector中添加右键菜单项
public int contextMenuExample;
void ResetValue()
{
contextMenuExample = 0;
}

[ContextMenu("Print Info")] // 在组件的齿轮菜单中添加菜单项
void PrintInfo()
{
Debug.Log("Context Menu Example Invoked!");
}

// Start is called before the first frame update
void Start()
{

}

// Update is called once per frame
void Update()
{

}
}

MonoBehaviour中重要的内容

  • gameObject:获取当前脚本依附的GameObject对象
    1
    2
    // 获取依附的GameObject
    print(this.gameObject);
  • transform:获取当前脚本依附的GameObject对象的Transform组件
    1
    2
    3
    4
    // 获取GameObject的位置信息
    print(this.transform.position);
    print(this.transform.eulerAngles); // 获取欧拉角
    print(this.transform.localScale); // 获取缩放信息
  • enabled:获取或设置脚本是否激活
    1
    2
    // 获取脚本是否激活
    print(this.enabled);

重要方法

  • 得到自己挂载的单个脚本
    • 根据脚本名获取
    • typeof获取
    • 用泛型获取
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      Lesson2 t = this.GetComponent("Lesson2") as Lesson2;
      print(t);

      // Type方式获取
      Lesson2 t2 = this.GetComponent(typeof(Lesson2) ) as Lesson2;
      print(t2);

      // 泛型方式获取
      Lesson2 t3 = this.GetComponent<Lesson2>();
      print(t3);
  • 得到自己挂载的多个脚本
    1
    2
    3
    4
    5
    6
    Lesson3[] arr = this.GetComponents<Lesson3>();
    print(arr.Length);

    List<Lesson3> list = new List<Lesson3>();
    this.GetComponents<Lesson3>(list);
    print(list.Count);
  • 得到子物体上的脚本(默认会找自己身上是否挂载该脚本,也可以获取多个),参数可填入一个布尔值,表示是否包含非激活的物体
    1
    2
    3
    4
    5
    Lesson2 t4 = this.GetComponentInChildren<Lesson2>();
    print(t4);

    Lesson2[] arr2 = this.GetComponentsInChildren<Lesson2>();
    print(arr2.Length);
  • 得到父物体上的脚本(默认会找自己身上是否挂载该脚本)
    1
    2
    3
    4
    Lesson2 t5 = this.GetComponentInParent<Lesson2>();
    print(t5);
    Lesson2[] arr3 = this.GetComponentsInParent<Lesson2>();
    print(arr3.Length);
  • 尝试获取脚本,避免空引用异常
    1
    2
    3
    Lesson2 t6;
    bool ret = this.TryGetComponent<Lesson2>(out t6);
    print(ret);

Unity重要组件和API

GameObject

成员变量

  • 名字:name
    通过this.gameObject.name获取或设置物体名称
  • 是否激活:activeSelf
    通过this.gameObject.activeSelf获取物体是否激活
  • 是否是静态物体:isStatic
    通过this.gameObject.isStatic获取物体是否为静态物体
  • 标签:tag
    通过this.gameObject.tag获取或设置物体标签
  • 层级:layer
    通过this.gameObject.layer获取或设置物体层级
  • 变换组件:transform
    通过this.gameObject.transform获取物体的变换组件(与this.transform相同)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// GameObject的成员变量
// 名字
print(this.gameObject.name);
this.gameObject.name = "NewName";
print(this.gameObject.name);
// 是否激活
print(this.gameObject.activeSelf);
// 是否是静态
print(this.gameObject.isStatic);
// 层级
print(this.gameObject.layer);
// 标签
print(this.gameObject.tag);
// transform组件
print(this.gameObject.transform.position);
print(this.gameObject.transform.rotation);

静态方法

  • 创建物体:GameObject.CreatePrimitive(PrimitiveType type)
    创建一个基本的几何体物体,参数为几何体类型
    1
    2
    3
    // 创建自带几何体
    GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
    obj.name = "MyCube";
  • 查找对象:
    1. 查找单个物体
      • GameObject.Find(string name)根据名称查找物体,返回第一个找到的物体(这个方法效率较低)
      • GameObject.FindWithTag(string tag)根据标签查找物体,返回第一个找到的物体
      • GameObject.FindGameObjectWithTag(string tag)根据标签查找物体,返回第一个找到的物体(效果与上面的一样)
      • 上面的两个方法都无法找到未激活的对象,且如果场景中存在多个物体,也无法精确定位到具体的物体
    2. 查找多个物体
      • GameObject.FindGameObjectsWithTag(string tag)根据标签查找物体,返回所有找到的物体数组
      • 也是只能找到激活的物体
    3. 用的比较少的方法(效率更低,是Object类中的方法)
      • GameObject.FindObjectsOfType<T>()根据类型查找物体,返回所有找到的物体数组(泛型可填特定脚本的名字,用来找到挂载特定脚本的物体)
      • GameObject.FindObjectOfType<T>()根据类型查找物体,返回第一个找到的物体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 查找场景中的物体
// 查找单个物体
GameObject obj2 = GameObject.Find("MyCube");
if (obj2 != null)
{
print("Found MyCube");
}
else
{
print("MyCube not found");
}
// 上面的方法效率较低,不推荐使用
// 通过Tag查找
GameObject obj3 = GameObject.FindWithTag("Player");
if (obj3 != null)
{
print("Found Player");
}
else
{
print("Player not found");
}

// 查找多个物体
// 通过Tag找到多个对象
GameObject[] objs = GameObject.FindGameObjectsWithTag("Enemy");
print("Found " + objs.Length + " Enemies");
  • 实例化(克隆)对象(动态创建物体)
    • 准备用来克隆的对象必须是预设体或者场景中的物体
    • GameObject.Instantiate(GameObject original)克隆一个物体,返回克隆后的物体
    • Instantiate方法有多个重载,可以指定位置、旋转和父物体
1
2
// 实例化物体
GameObject.Instantiate(myObj);
  • 销毁对象
    • GameObject.Destroy(GameObject obj)销毁一个物体
    • 同样也有多个重载,可以指定延迟时间
    • Destroy也可以销毁组件和脚本
    • 移除操作是在下一帧进行的,并从内存中移除
    • DestroyImmediate立即销毁物体,一般只在编辑器模式下使用
1
2
// 销毁物体
GameObject.Destroy(obj);
  • 过场景不移除物体
    • GameObject.DontDestroyOnLoad(GameObject obj)设置物体在场景切换时不被销毁
      1
      2
      // 不销毁物体
      GameObject.DontDestroyOnLoad(obj);

成员方法

  • 创建空物体:new GameObject(string name)
    创建一个空的物体,参数为物体名称(可选),也可以加脚本

    1
    2
    3
    4
    5
    6
    // 创建空物体
    GameObject obj4 = new GameObject("MyEmptyObject");
    print(obj4.name);
    // 创建空物体并添加脚本
    GameObject obj5 = new GameObject("MyScriptedObject", typeof(Lesson2));
    print(obj5.name);
  • 添加脚本组件:AddComponent<T>()
    为物体添加一个脚本组件,返回添加的脚本组件

    1
    2
    3
    4
    5
    6
    // 为物体添加脚本组件
    Lesson2 lesson = obj4.AddComponent<Lesson2>();
    print(lesson);
    // 也可以用类型方式添加(不常用)
    Lesson2 lesson2 = obj4.AddComponent(typeof(Lesson2)) as Lesson2;
    print(lesson2);
  • 标签比较:CompareTag(string tag)
    比较物体的标签是否与指定标签相同,返回布尔值

    1
    2
    3
    4
    5
    6
    7
    8
    if (obj4.CompareTag("Player"))
    {
    print("This is a Player");
    }
    else
    {
    print("This is not a Player");
    }
  • 设置物体激活状态:SetActive(bool value)
    设置物体是否激活

    1
    2
    3
    4
    // 激活物体
    obj4.SetActive(true);
    // 禁用物体
    obj4.SetActive(false);
  • 其他的方法:通过广播或者发送消息的方法,让自己或者别人执行行为方法

    • SendMessage(string FunName):向物体发送消息,调用物体上所有脚本中的指定方法
    • BroadcastMessage(string FunName):向物体及其所有子物体发送消息,调用所有脚本中的指定方法

Time相关内容

Time是Unity中用于获取时间信息的类,主要用于控制游戏的时间流逝和获取时间相关的信息

时间缩放比例

  • Time.timeScale:获取或设置时间缩放比例,默认值为1.0,设置为0.0时游戏暂停
    1
    2
    3
    4
    // 设置时间缩放比例
    Time.timeScale = 0.5f; // 游戏时间变慢一半
    Time.timeScale = 1.0f; // 恢复正常时间
    Time.timeScale = 0.0f; // 暂停游戏

帧间隔时间

指最近的两帧之间的时间间隔

  • Time.deltaTime:获取上一帧到当前帧的时间间隔,单位为秒
    它会受到时间缩放比例的影响
  • Time.unscaledDeltaTime:获取上一帧到当前帧的时间间隔,单位为秒
    它不受时间缩放比例的影响
    1
    2
    3
    // 获取帧间隔时间
    print("Delta Time: " + Time.deltaTime);
    print("Unscaled Delta Time: " + Time.unscaledDeltaTime);
    注意:在编辑器模式下,如果不设置帧率,编辑器会以CPU的最高性能运行,所以帧间隔会很小

实际开发这,根据需要去选择参与计算的时间间隔

游戏运行时间

指游戏从开始运行到当前的总时间(一般用于计时)

  • Time.time:获取游戏从开始运行到当前的总时间,单位为秒
    同样它会受到时间缩放比例的影响
  • Time.unscaledTime:获取游戏从开始运行到当前的总时间,单位为秒
    它不受时间缩放比例的影响
    1
    2
    3
    // 获取游戏运行时间
    print("Game Time: " + Time.time);
    print("Unscaled Game Time: " + Time.unscaledTime);

物理帧时间

指物理引擎更新的时间间隔

  • Time.fixedDeltaTime:获取物理帧的时间间隔,单位为秒,不受时间缩放比例影响
    它是一个固定值,默认值为0.02(即50帧每秒),在项目设置的Time中可以调整
    1
    2
    3
    // 获取物理帧时间
    print("Fixed Delta Time: " + Time.fixedDeltaTime);
    print("Fixed Unscaled Delta Time: " + Time.fixedUnscaledDeltaTime);

帧率相关

  • Time.frameCount:获取从游戏开始运行到当前的总帧数
    1
    2
    // 获取总帧数
    print("Frame Count: " + Time.frameCount);

Transform相关内容

Transform是Unity中用于表示物体位置、旋转和缩放的组件,每个GameObject都有一个Transform组件

Vector3

Vector3是Unity中用于表示三维向量和位置的结构体,包含x、y、z三个分量

  • 创建Vector3

    1
    2
    Vector3 v1 = new Vector3(1.0f, 2.0f, 3.0f);
    Vector3 v2 = new Vector3(); // 默认值为(0, 0, 0)
  • 基本运算

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Vector3 a = new Vector3(1.0f, 2.0f, 3.0f);
    Vector3 b = new Vector3(4.0f, 5.0f, 6.0f);
    Vector3 add = a + b; // 向量加法
    Vector3 sub = a - b; // 向量减法
    Vector3 mul = a * 2.0f; // 向量数乘
    Vector3 div = a / 2.0f; // 向量数除
    float dot = Vector3.Dot(a, b); // 向量点积
    Vector3 cross = Vector3.Cross(a, b); // 向量叉积
    float magnitude = a.magnitude; // 向量长度
    Vector3 normalized = a.normalized; // 单位向量
  • 一些常用的量

    1
    2
    3
    4
    5
    6
    7
    8
    Vector3 v3 = Vector3.zero; // 零向量(0, 0, 0)
    Vector3 v4 = Vector3.one; // 单位向量(1, 1, 1)
    Vector3 v5 = Vector3.up; // 上方向向量(0, 1, 0)
    Vector3 v6 = Vector3.down; // 下方向向量(0, -1, 0)
    Vector3 v7 = Vector3.left; // 左方向向量(-1, 0, 0)
    Vector3 v8 = Vector3.right; // 右方向向量(1, 0, 0)
    Vector3 v9 = Vector3.forward; // 前方向向量(0, 0, 1)
    Vector3 v10 = Vector3.back; // 后方向向量(0, 0, -1)
  • 计算距离的方法
    Distance:计算两个点(向量)之间的距离

    1
    2
    3
    4
    Vector3 p1 = new Vector3(1.0f, 2.0f, 3.0f);
    Vector3 p2 = new Vector3(4.0f, 5.0f, 6.0f);
    float distance = Vector3.Distance(p1, p2);
    print("Distance: " + distance);

位置

  • 获取或设置物体的位置
    • transform.position:获取或设置物体在世界坐标系中的位置
    • transform.localPosition:获取或设置物体在父物体坐标系中的位置

位置的设置不能单一改变某一个分量,只能整体赋值

1
2
3
4
5
6
7
// 获取物体位置
Vector3 worldPosition = transform.position;
Vector3 localPosition = transform.localPosition;

// 设置物体位置
transform.position = new Vector3(0, 5, 0);
transform.localPosition = new Vector3(0, 2, 0);

或者可以先获取位置,然后修改某个分量,再赋值回去

1
2
3
Vector3 pos = transform.position;
pos.y = 10.0f;
transform.position = pos;
  • 对象的朝向
    • transform.forward:获取物体的前方向向量
    • transform.up:获取物体的上方向向量
    • transform.right:获取物体的右方向向量
      1
      2
      3
      4
      5
      6
      Vector3 forward = transform.forward;
      Vector3 up = transform.up;
      Vector3 right = transform.right;
      print("Forward: " + forward);
      print("Up: " + up);
      print("Right: " + right);

位移

理解坐标系下的位移计算

  • 自己计算:当前位置 + 方向向量 * 速度 * 时间

    1
    this.transform.position += this.transform.forward * 5.0f;
  • API计算:Transform.Translate(Vector3 translation)

    • translation:位移向量
    • relativeTo:(可选参数)相对空间,默认是相对于自身坐标系(Space.Self),也可以选择相对于世界坐标系(Space.World
      1
      2
      this.transform.Translate(Vector3.forward * 5.0f);
      this.transform.Translate(Vector3.forward * 5.0f, Space.World);

角度与旋转

  • 获取或设置物体的旋转

    • transform.rotation:获取或设置物体在世界坐标系中的旋转,返回值为四元数(Quaternion)
    • transform.localRotation:获取或设置物体在父物体坐标系中的旋转,返回值为四元数(Quaternion)
    • transform.eulerAngles:获取或设置物体在世界坐标系中的旋转,返回值为欧拉角(Vector3)
    • transform.localEulerAngles:获取或设置物体在父物体坐标系中的旋转,返回值为欧拉角(Vector3)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      // 获取物体旋转
      Quaternion worldRotation = transform.rotation;
      Quaternion localRotation = transform.localRotation;
      Vector3 worldEulerAngles = transform.eulerAngles;
      Vector3 localEulerAngles = transform.localEulerAngles;
      // 设置物体旋转
      transform.rotation = Quaternion.Euler(0, 90, 0);
      transform.localRotation = Quaternion.Euler(0, 45, 0);
      transform.eulerAngles = new Vector3(0, 90, 0);
      transform.localEulerAngles = new Vector3(0, 45, 0);
  • 旋转物体

    • Transform.Rotate(Vector3 eulerAngles):绕每个轴旋转指定的欧拉角
    • Transform.Rotate(Vector3 axis, float angle):绕指定轴旋转指定的角度
    • relativeTo:(可选参数)相对空间,默认是相对于自身坐标系(Space.Self),也可以选择相对于世界坐标系(Space.World
      1
      2
      this.transform.Rotate(new Vector3(0, 90, 0));
      this.transform.Rotate(new Vector3(0, 90, 0), Space.World);

也可以让它实现自转

1
this.transform.Rotate(Vector3.up * 90.0f * Time.deltaTime);

相对于某个轴旋转

1
this.transform.Rotate(Vector3.right, 90.0f * Time.deltaTime);

绕轴旋转中的RotateAround方法已弃用,所以直接使用transform.Rotate来实现

相对于一个点旋转

1
this.transform.RotateAround(Vector3.zero, Vector3.up, 90.0f * Time.deltaTime);

缩放与看向

  • 获取或设置物体的缩放
    • transform.localScale:获取或设置物体在父物体坐标系中的缩放,返回值为Vector3
    • transform.lossyScale:获取物体在世界坐标系中的缩放,返回值为Vector3(只读)

世界坐标系下的缩放不能修改,只能修改局部缩放

且不能只修改某一个分量,只能整体赋值

1
transform.localScale = new Vector3(2, 2, 2);

Unity也没有直接设置缩放的API,只能通过修改localScale来实现

1
transform.localScale += Vector3.one * Time.deltaTime;

可以通过上面的方式来实现逐渐放大

  • 让物体看向某个点
    • Transform.LookAt(Vector3 target):让物体朝向指定的目标点
      1
      2
      transform.LookAt(new Vector3(0, 0, 0));
      transform.LookAt(targetTransform.position);
  • Transform.LookAt(Transform target):让物体朝向指定的目标物体
    1
    transform.LookAt(targetTransform);
    这个方法可以用在摄像机上,让摄像机始终看向某个物体

父子关系

  • 获取或设置物体的父物体

    • transform.parent:获取或设置物体的父物体的Transform组件
    • transform.SetParent(Transform parent):设置物体的父物体
    • transform.SetParent(Transform parent, bool worldPositionStays):设置物体的父物体,并指定是否保持世界位置不变
      1
      2
      3
      4
      5
      6
      // 获取
      Transform parentTransform = transform.parent;
      // 设置
      transform.parent = newParentTransform;
      transform.parent = null; // 清除父对象
      transform.SetParent("Cube");
  • 去除所有子对象

    • transform.DetachChildren():将物体的所有子物体从该物体中分离出来,变为独立的物体
      1
      transform.DetachChildren();
  • 获取子对象

    • transform.Find(string name):按名字查找子物体,返回第一个找到的子物体的Transform组件(Find能找到非激活的子物体)
    • transform.childCount:获取子物体的数量
      1
      2
      Transform childTransform = transform.Find("ChildName");
      int childCount = transform.childCount;

根据索引获取子物体

1
2
3
4
5
for (int i = 0; i < transform.childCount; i++)
{
Transform child = transform.GetChild(i);
print("Child " + i + ": " + child.name);
}
  • 子对象的操作
    • transform.IsChildOf(Transform parent):判断物体是否是指定父物体的子物体,返回布尔值
    • transform.GetSiblingIndex():获取物体在兄弟物体中的索引
    • transform.SetAsFirstSibling():将物体设置为兄弟物体中的第一个
    • transform.SetAsLastSibling():将物体设置为兄弟物体中的最后一个
    • transform.SetSiblingIndex(int index):将物体设置为兄弟物体中的指定索引位置(超出范围则设置为最后一个)

坐标转换

  • 世界坐标系转换为局部坐标系

    • transform.InverseTransformPoint(Vector3 position):将世界坐标系中的点转换为物体的局部坐标系中的点(会受缩放影响)
    • transform.InverseTransformDirection(Vector3 direction):将世界坐标系中的方向转换为物体的局部坐标系中的方向(不受缩放影响)
    • transform.InverseTransformVector(Vector3 vector):将世界坐标系中的向量转换为物体的局部坐标系中的向量(会受缩放影响)
      1
      2
      Vector3 localPos = transform.InverseTransformPoint(new Vector3(10, 0, 0));
      print("Local Position: " + localPos);
  • 局部坐标系转换为世界坐标系

    • transform.TransformPoint(Vector3 pos):将局部坐标系的点转换到世界坐标系中
    • transform.TransformDirection(Vector3 direction):将局部坐标系的方向转换到世界坐标系中
    • transform.TransformVector(Vector3 vector):将局部坐标系中的向量转换到世界坐标系中

输入Input

鼠标所在位置

  • Input.mousePosition:获取鼠标在屏幕坐标系中的位置,返回值为Vector3,z分量为0
    1
    2
    Vector3 mousePos = Input.mousePosition;
    print("Mouse Position: " + mousePos);
    屏幕坐标的原点是在左下角,x轴向右,y轴向上

鼠标按键状态

  • Input.GetMouseButtonDown(int button):鼠标按下一瞬间执行,返回布尔值

    1
    2
    3
    4
    if (Input.GetMouseButtonDown(0))
    {
    print("Left Mouse Button is just pressed");
    }
  • Input.GetMouseButtonUp(int button):鼠标抬起一瞬间执行,返回布尔值

    1
    2
    3
    4
    if (Input.GetMouseButtonUp(0))
    {
    print("Left Mouse Button is just released");
    }
  • Input.GetMouseButton(int button):鼠标持续按下时执行,返回布尔值

    1
    2
    3
    4
    if (Input.GetMouseButton(0))
    {
    print("Left Mouse Button is being held down");
    }

    button参数表示鼠标按键,0表示左键,1表示右键,2表示中键

  • Input.mouseScrollDelta:获取鼠标滚轮的滚动增量,返回值为Vector2,y分量表示滚动方向和幅度

    1
    2
    Vector2 scrollDelta = Input.mouseScrollDelta;
    print("Mouse Scroll Delta: " + scrollDelta);

    -1表示向下滚动,1表示向上滚动,0表示没有滚动

键盘按键状态

  • Input.GetKeyDown(KeyCode key):按键按下一瞬间执行,返回布尔值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    if (Input.GetKeyDown(KeyCode.Space))
    {
    print("Space key is just pressed");
    }
    // 另一个重载方法(不能大写)
    if (Input.GetKeyDown("space"))
    {
    print("Space key is just pressed");
    }
  • Input.GetKeyUp(KeyCode key):按键抬起一瞬间执行,返回布尔值
    1
    2
    3
    4
    if (Input.GetKeyUp(KeyCode.Space))
    {
    print("Space key is just released");
    }
  • Input.GetKey(KeyCode key):按键持续按下时执行,返回布尔值
    1
    2
    3
    4
    if (Input.GetKey(KeyCode.Space))
    {
    print("Space key is being held down");
    }
    KeyCode枚举包含了所有键盘按键的定义

输入轴

控制轴输入是一种抽象的输入方式,可以通过配置输入轴来实现对多个按键或控制器的支持
也是Unity为开发者提供的一种方便的输入处理方式,更好地处理对象的移动和操作

  • AD键返回-1至1之间的值,A键为-1,D键为1,X轴
  • WS键返回-1至1之间的值,S键为-1,W键为1,Y轴

Edit -> Project Settings -> Input Manager中可以查看和修改输入轴的配置

  • Input.GetAxis(string axisName):获取指定输入轴的值,返回值为浮点数

    1
    2
    3
    4
    5
    6
    7
    float horizontal = Input.GetAxis("Horizontal"); // X轴
    float vertical = Input.GetAxis("Vertical"); // Y轴
    print("Horizontal: " + horizontal + ", Vertical: " + vertical);

    float mouseX = Input.GetAxis("Mouse X"); // 鼠标X轴
    float mouseY = Input.GetAxis("Mouse Y"); // 鼠标Y轴
    print("Mouse X: " + mouseX + ", Mouse Y: " + mouseY);
  • Input.GetAxisRaw(string axisName):获取指定输入轴的原始值,返回值为浮点数
    它的返回值没有中间值,只有-1、0、1三种状态

    1
    2
    3
    float horizontalRaw = Input.GetAxisRaw("Horizontal"); // X轴
    float verticalRaw = Input.GetAxisRaw("Vertical"); // Y轴
    print("Horizontal Raw: " + horizontalRaw + ", Vertical Raw: " + verticalRaw);

其他

  • Input.anyKey:检测是否有任意按键被按下,返回布尔值
    1
    2
    3
    4
    if (Input.anyKey)
    {
    print("Some key is being pressed");
    }
  • Input.anyKeyDown:检测是否有任意按键刚刚被按下,返回布尔值
    1
    2
    3
    4
    if (Input.anyKeyDown)
    {
    print("Some key is just pressed");
    }
  • Input.inputString:获取当前帧输入的字符串,返回值为字符串
    1
    2
    string inputStr = Input.inputString;
    print("Input String: " + inputStr);
  • Input.GetJoystickNames():获取连接的所有手柄名称,返回值为字符串数组
    1
    2
    3
    4
    5
    string[] joystickNames = Input.GetJoystickNames();
    foreach (string name in joystickNames)
    {
    print("Joystick Name: " + name);
    }
  • Input.touches:获取当前所有触摸点的信息,返回值为Touch数组
    1
    2
    3
    4
    5
    Touch[] touches = Input.touches;
    foreach (Touch touch in touches)
    {
    print("Touch Position: " + touch.position);
    }
  • Input.touchCount:获取当前触摸点的数量,返回值为整数
    1
    2
    int touchCount = Input.touchCount;
    print("Touch Count: " + touchCount);
  • Input.multiTouchEnabled:获取或设置是否启用多点触控,返回值为布尔值
    1
    2
    3
    bool isMultiTouchEnabled = Input.multiTouchEnabled;
    print("Multi-Touch Enabled: " + isMultiTouchEnabled);
    Input.multiTouchEnabled = true; // 启用多点触控
  • Input.gyro:获取设备的陀螺仪信息,返回值为Gyroscope对象
    1
    2
    3
    4
    5
    Gyroscope gyro = Input.gyro;
    print("Gyro Attitude: " + gyro.attitude);
    gyro.enabled = true; // 启用陀螺仪
    print("Gravity: " + Input.gyro.gravity); // 获取重力加速度
    print("Rotation Rate: " + Input.gyro.rotationRate); // 获取旋转速率

Screen相关内容

Screen类用于获取和设置屏幕相关的信息

屏幕分辨率

  • Screen.currentResolution:获取当前屏幕(显示器)的分辨率,返回值为Resolution结构体
  • currentResolution结构体包含以下成员变量:
    • width:屏幕宽度,单位为像素
    • height:屏幕高度,单位为像素
    • refreshRate:屏幕刷新率,单位为赫兹Hz
      1
      2
      Resolution res = Screen.currentResolution;
      print("Current Resolution: " + res.width + "x" + res.height + " @ " + res.refreshRate + "Hz");
  • Screen.width:获取屏幕的宽度,单位为像素
  • Screen.height:获取屏幕的高度,单位为像素
    以上两个属性返回当前游戏窗口的宽度和高度
    1
    2
    print("Screen Width: " + Screen.width);
    print("Screen Height: " + Screen.height);
  • Screen.sleepTimeout:获取或设置屏幕休眠超时时间,单位为秒
    • 设置为SleepTimeout.NeverSleep表示永不休眠
      1
      2
      Screen.sleepTimeout = SleepTimeout.NeverSleep;
      print("Sleep Timeout: " + Screen.sleepTimeout);

全屏与窗口模式

  • Screen.fullScreen:获取或设置是否为全屏模式,返回值为布尔值
    1
    2
    3
    4
    Screen.fullScreen = true; // 设置为全屏模式
    print("Is Fullscreen: " + Screen.fullScreen);
    Screen.fullScreen = false; // 设置为窗口模式
    print("Is Fullscreen: " + Screen.fullScreen);
  • Screen.fullScreenMode:获取或设置全屏模式,返回值为FullScreenMode枚举
    • FullScreenMode.ExclusiveFullScreen:独占全屏模式
    • FullScreenMode.FullScreenWindow:全屏窗口模式
    • FullScreenMode.MaximizedWindow:最大化窗口模式
    • FullScreenMode.Windowed:窗口模式
      1
      2
      3
      4
      Screen.fullScreenMode = FullScreenMode.FullScreenWindow;
      print("FullScreen Mode: " + Screen.fullScreenMode);
      Screen.fullScreenMode = FullScreenMode.Windowed;
      print("FullScreen Mode: " + Screen.fullScreenMode);

移动设备屏幕方向

  • Screen.autoRotationToLandscapeLeft:获取或设置是否允许自动旋转到左横屏模式,返回值为布尔值
  • Screen.autoRotationToLandscapeRight:获取或设置是否允许自动旋转到右横屏模式,返回值为布尔值
  • Screen.autoRotationToPortrait:获取或设置是否允许自动旋转到竖屏模式,返回值为布尔值
  • Screen.autoRotationToPortraitUpsideDown:获取或设置是否允许自动旋转到倒竖屏模式,返回值为布尔值
  • Screen.orientation:获取或设置屏幕的当前方向,返回值为ScreenOrientation枚举
    • ScreenOrientation.Portrait:竖屏模式
    • ScreenOrientation.LandscapeLeft:左横屏模式
    • ScreenOrientation.LandscapeRight:右横屏模式
    • ScreenOrientation.PortraitUpsideDown:倒竖屏模式
    • ScreenOrientation.AutoRotation:自动旋转模式

设置分辨率

  • Screen.SetResolution(int width, int height, bool fullscreen):设置屏幕分辨率和全屏模式
    • width:屏幕宽度,单位为像素
    • height:屏幕高度,单位为像素
    • fullscreen:是否为全屏模式,布尔值
      1
      2
      3
      4
      Screen.SetResolution(1920, 1080, true);
      print("Resolution Set to 1920x1080 Fullscreen");
      Screen.SetResolution(1280, 720, false);
      print("Resolution Set to 1280x720 Windowed");

Camera相关内容

以下内容是组件中的参数设置

Clear Flags

  • Skybox:使用天空盒清除背景
  • Solid Color:使用纯色清除背景
  • Depth Only:只清除深度缓冲区,只画该层,背景透明
  • Don't Clear:不清除任何缓冲区,直接在上面绘制

Culling Mask

  • 用于设置摄像机渲染哪些层级的物体,可以通过勾选或取消勾选来控制摄像机的渲染范围

Projection

  • Perspective:透视投影
    • FOV Axis:视野轴,可以选择水平视野(Horizontal)或垂直视野(Vertical)
    • Field of View:视野角度,表示摄像机的视野范围,单位为度
    • Physical Camera:物理摄像机选项,启用后可以模拟真实摄像机的参数
  • Orthographic:正交投影,一般用于2D游戏开发
    • Size:正交大小,表示摄像机的视野范围

Clipping Planes

  • Near:近裁剪面,表示摄像机能够看到的最近距离(即离摄像机多近的物体会被渲染)
  • Far:远裁剪面,表示摄像机能够看到的最远距离(即离摄像机多远的物体会被渲染)

Depth

  • 用于设置摄像机的渲染顺序,数值越大,渲染优先级越高
    在制作UI界面时,通常会将UI摄像机的Depth值设置得比主摄像机高,以确保UI元素能够正确显示在前景,同时也可以保证后面的3D场景不会被UI遮挡

Target Texture

  • 用于将摄像机的渲染结果输出到一个Render Texture(渲染纹理)中,而不是直接显示在屏幕上,主要用于制作小地图等

Occlusion Culling

  • 用于启用或禁用遮挡剔除功能,提升渲染性能
    当摄像机启用遮挡剔除功能时,只有摄像机视野内且未被其他物体遮挡的物体才会被渲染,从而减少不必要的渲染计算,提高游戏性能

Viewport Rect

  • 用于设置摄像机在屏幕上的显示区域,参数包括X、Y、Width、Height,取值范围为0到1

Rendering Path

  • 用于设置摄像机的渲染路径,常用的有以下几种:
    • Use Graphics Settings:使用图形设置中的渲染路径
    • Forward:前向渲染路径
    • Deferred:延迟渲染路径
    • Legacy Vertex Lit:传统顶点光照渲染路径

Allow HDR

  • 用于启用或禁用高动态范围渲染(HDR),提升图像质量

Allow MSAA

  • 用于启用或禁用多重采样抗锯齿(MSAA),提升图像质量

Allow Dynamic Resolution

  • 用于启用或禁用动态分辨率调整,提升性能

Target Display

  • 用于设置摄像机渲染到哪个显示器,适用于多显示器环境

代码相关

  • Camera.main:获取场景中标记为MainCamera的摄像机

    1
    Camera mainCamera = Camera.main;
  • Camera.allCameras:得到所有摄像机

    1
    Camera[] allCameras = Camera.allCameras;
  • Camera.current:获取当前正在渲染的摄像机

    1
    Camera currentCamera = Camera.current;
  • Camera.onPreCull:委托,剔除前处理

  • Camera.onPreRender:委托,渲染前处理

  • Camera.onPostRender:委托,渲染后处理

  • Camera.main.depth:获取或设置主摄像机的深度

  • Camera.main.WorldToScreenPoint:世界坐标转换为屏幕坐标(比如敌人上方的血条)

  • Camera.main.ScreenToWorldPoint:屏幕坐标转换为世界坐标(注意传入坐标的z,为0则直接在摄像机焦点上,不为0则在距离该距离的横截面上)