前言

引擎就不介绍了

由于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则在距离该距离的横截面上)

核心系统

光源组件

参数面板上的内容

  • Type:光源类型,有三种类型

    • Directional:方向光,模拟太阳光,光线平行,适用于大范围照明
    • Point:点光源,向各个方向发射光线,适用于局部照明
    • Spot:聚光灯,向一个方向发射锥形光线,适用于舞台灯光等效果
    • Area (Baked Only):面光源,仅在烘焙后使用
  • Color:光的颜色

  • Mode:光源模式

    • Realtime:实时运算
    • Baked:烘焙光源
    • Mixed:混合计算
  • Intensity:光照强度

  • Shadow Type:阴影设置

    • NoShadow:关闭阴影
    • HardShadows:硬边阴影
    • SoftShadows:柔边阴影
  • Cookie:投影遮罩

  • Draw Halo:光晕效果

  • Flare:耀斑(要摄像机能看到需要加Flare组件)

  • Culling Mask:剔除遮罩层

  • Indirect Multiplier:间接光设置

  • Realtime Shadows:实时阴影设置

  • Cookie Size:遮罩大小

  • Render Mode:渲染设置

代码部分

  • light.intensity:光源强度

光面板
Window->Rendering->Lighting Settings打开光源面板设置

  • Environment:环境设置

    • Skybox Material:天空盒材质
    • Sun Source:太阳光
    • Environment Lighting:环境光照
  • Other Settings:其他设置

    • Fog:雾效
    • Halo Texture:光晕纹理
    • Halo Strength:光晕强度
    • Flare Fade Speed:耀斑淡出速度
    • Flare Strength:耀斑强度
    • Spot Cookie:聚光灯遮罩

物理系统

刚体

两个物体要产生碰撞,两个物体都要有碰撞器,且至少有一个物体有刚体组件

刚体组件是Rigidbody,用于给物体添加物理属性,使其能够受到物理引擎的影响

  • Mass:质量,影响物体的惯性和受力效果,质量越大,物体惯性越大
  • Drag:线性阻力,影响物体在运动中的速度,0表示无阻力
  • Angular Drag:角阻力,影响物体旋转时的速度,0表示无阻力
  • Use Gravity:是否使用重力,勾选后物体会受到重力影响
  • Is Kinematic:是否为运动学刚体,勾选后物体不会受到物理引擎的影响,可以通过脚本控制其运动
  • Interpolate:插值模式,影响物体运动的平滑度
    • None:不使用插值
    • Interpolate:使用前一帧的位置进行插值
    • Extrapolate:使用后一帧的位置进行插值
  • Collision Detection:碰撞检测模式,影响物体碰撞的精度
    • Discrete:离散碰撞检测,默认模式,适用于大多数情况,性能最好
    • Continuous:连续碰撞检测,对快速移动的物体使用离散检测,对静态物体使用连续检测,性能较差
    • Continuous Dynamic:动态连续碰撞检测,性能开销最大,适用于高速移动的物体
    • Continuous Speculative:推测连续碰撞检测,适用于高速移动的物体,比连续碰撞检测性能更好
  • Constraints:约束选项,可以锁定物体的某些轴向位置或旋转,防止其在这些方向上移动或旋转

碰撞器

碰撞器用于定义物体的碰撞范围,使其能够与其他物体发生碰撞

默认显示的碰撞器颜色是绿色,选中时为黄色

默认的几何体都有碰撞器组件,组件名为Collider
常用的碰撞器类型有(3D):

  • Box Collider:盒状碰撞器,适用于立方体等方形物体
  • Sphere Collider:球状碰撞器,适用于球体等圆形物体
  • Capsule Collider:胶囊状碰撞器,适用于角色等物体
  • Mesh Collider:网格碰撞器,适用于复杂形状的物体,但性能较差
  • Wheel Collider:轮子碰撞器,适用于车辆等物体
  • Terrain Collider:地形碰撞器,适用于地形对象

也有一些2D的碰撞器,组件名带2D后缀,如Box Collider 2DCircle Collider 2D

几个参数:

  • Is Trigger:是否为触发器,勾选后碰撞器不会产生物理碰撞,而是触发事件
  • Material:物理材质,用于定义碰撞时的摩擦力和弹性
  • Center:碰撞器的中心位置

物理材质

物理材质用于定义物体在碰撞时的摩擦力和弹性

参数:

  • Dynamic Friction:动态摩擦力,物体在运动时的摩擦力,0表示无摩擦力
  • Static Friction:静态摩擦力,物体静止时的摩擦力,0表示无摩擦力
  • Bounciness:弹性,物体碰撞后反弹的力度,0表示无弹性
  • Friction Combine:摩擦力合并方式
    • Average:平均值
    • Minimum:最小值
    • Maximum:最大值
    • Multiply:相乘
  • Bounce Combine:弹性合并方式(同上)

碰撞检测函数

  • OnCollisionEnter(Collision collision):当物体开始碰撞时调用
  • OnCollisionExit(Collision collision):当物体结束碰撞时调用
  • OnCollisionStay(Collision collision):当物体持续碰撞时调用

以上三个函数用于处理物理碰撞事件,参数Collision包含碰撞信息

一些常用的Collision属性:

  • collision.gameObject:获取碰撞的物体
  • collision.contacts:获取碰撞点信息数组
  • collision.collider:获取碰撞器组件
  • collision.transform:获取碰撞物体的Transform组件
  • collision.contactCount:获取碰撞点数量

触发器检测函数

  • OnTriggerEnter(Collider other):当物体进入触发器时调用
  • OnTriggerExit(Collider other):当物体离开触发器时调用
  • OnTriggerStay(Collider other):当物体持续在触发器内时调用

同理,参数Collider包含触发器信息,也可以获取各种物体的信息

刚体添加力的方法

给刚体添加力,就可以让物体运动起来,获得一个初速度

首先要获取刚体组件

1
Rigidbody rb = GetComponent<Rigidbody>();

然后再添加力

  • rb.AddForce(Vector3 force):相对于世界坐标系,向刚体添加力
  • rb.AddRelativeForce(Vector3 force):相对于刚体自身坐标系,向刚体添加力

添加扭矩

  • rb.AddTorque(Vector3 torque):相对于世界坐标系,向刚体添加扭矩
  • rb.AddRelativeTorque(Vector3 torque):相对于刚体自身坐标系,向刚体添加扭矩

直接改变速度

  • rb.velocity:获取或设置刚体的线性速度
    速度的类型是Vector3,所以在使用上可以直接赋值
    1
    rb.velocity = new Vector3(0, 10, 0); // 直接设置刚体的速度

模拟爆炸,即模拟爆炸冲击对其他物体的影响

  • rb.AddExplosionForce(float explosionForce, Vector3 explosionPosition, float explosionRadius):向刚体添加爆炸力
    • explosionForce:爆炸力的强度
    • explosionPosition:爆炸中心的位置
    • explosionRadius:爆炸的半径
1
rb.AddExplosionForce(10f, transform.position, 5f);

力的模式

ForceMode枚举定义了四种不同的力的应用方式:

  • Acceleration:加速度模式,直接改变刚体的加速度,与质量无关
  • Impulse:冲击模式,瞬间改变刚体的速度,与质量有关
  • VelocityChange:速度变化模式,直接改变刚体的速度,与质量无关
  • Force:力模式,持续施加力,影响刚体的加速度,与质量有关

力场脚本

Unity提供了一个内置的力场脚本Constant Force,可以直接添加到物体上,实现持续施加力的效果

添加这个脚本的同时,如果物体没有刚体组件,会自动添加一个刚体组件

Constant Force脚本的参数:

  • Force:持续施加的力
  • Rotational Force:持续施加的扭矩
  • Torque:持续施加的力矩
  • Relative Torque:相对于物体坐标系施加的力矩

刚体的休眠

刚体在长时间静止后会进入休眠状态,以节省计算资源

  • rb.Sleep():手动将刚体置于休眠状态

可以用代码来结束休眠状态

  • rb.WakeUp():手动将刚体从休眠状态唤醒

音效系统

常用格式

Unity支持多种音频格式,常用的有以下几种:

  • WAV:无损音频格式,音质高,但文件较大
  • MP3:有损音频格式,文件较小,但音质较差
  • OGG:有损音频格式,文件较小,音质较好,常用于游戏开发
  • AIFF:无损音频格式,音质高,但文件较大,主要用于Mac系统

音频文件属性

Inspector面板中,可以设置音频文件的属性:

  • Force To Mono:强制转换为单声道
  • Normalize:标准化音频
  • Load In Background:在后台加载音频,不阻塞主线程
  • Ambisonic:启用立体混响效果,适用于360度音频
  • Load Type:加载类型
    • Decompress On Load:加载时解压缩,适用于短音频,加载快,内存占用较大
    • Compressed In Memory:内存中压缩,适用于中等长度音频,加载慢,内存占用较小
    • Streaming:流式加载,适用于长音频,如背景音乐,加载慢,内存占用最小,消耗CPU资源
  • Preload Audio Data:预加载音频数据,勾选后进入场景就会加载音频数据
  • Compression Format:压缩格式
    • PCM:无损压缩,音质高,文件大
    • ADPCM:有损压缩,音质一般,文件较小,包含噪音,一般用小音效
    • Vorbis:有损压缩,音质好,文件小,常用格式
  • Quality:压缩质量,范围0-100,数值越大,音质越好,文件越大
  • Sample Rate Setting:采样率设置
    • Preserve Sample Rate:保持原始采样率
    • Optimize Sample Rate:优化采样率,根据音频内容调整采样率
    • Override Sample Rate:自定义采样率,可以手动设置采样率数值

Audio Source组件

Audio Source组件用于播放音频文件,可以将其添加到场景中的任何物体上
常用参数:

  • Audio Clip:要播放的音频文件
  • Output:音频输出通道
  • Mute:静音开关
  • Bypass Effects:开关滤波器效果
  • Bypass Listener Effects:开关监听器效果
  • Bypass Reverb Zones:开关混响区域效果
  • Play On Awake:是否在物体激活时自动播放音频
  • Loop:是否循环播放音频
  • Priority:音频优先级,数值越小,优先级越高
  • Volume:音量
  • Pitch:音调
  • Stereo Pan:立体声平移,范围-1(左声道)到1(右声道)
  • Spatial Blend:空间混合,范围0(2D音频)到1(3D音频)
  • Reverb Zone Mix:混响区域混合,范围0到1
  • 3D Sound Settings:3D音频设置
    • Doppler Level:多普勒效应强度
    • Spread:声音扩散范围
    • Volume Rolloff:音量衰减模式
      • Logarithmic Rolloff:对数衰减
      • Linear Rolloff:线性衰减
      • Custom Rolloff:自定义衰减曲线
    • Min Distance:最小距离,声音开始衰减的距离
    • Max Distance:最大距离,声音完全衰减的距离

Audio Listener组件

Audio Listener组件用于接收和播放场景中的音频,通常添加到主摄像机上

音频控制代码

代码控制音频播放,首先需要获取Audio Source组件

1
2
3
4
5
6
7
AudioSource audioSource = GetComponent<AudioSource>();
// 播放音频
audioSource.Play();
// 暂停音频
audioSource.Pause();
// 停止音频
audioSource.Stop();

检测音频播放完毕,代码本身是没有提供相应的方法
一般是使用isPlaying来一直检测音频是否在播放

一个GameObject对象可以挂载多个Audio Source脚本,但要注意管理

可以通过实例化对象来动态加载Audio Source脚本

麦克风相关

  • Microphone.devices:获取所有可用的麦克风设备名称,返回值为字符串数组
  • Microphone.Start(string deviceName, bool loop, int lengthSec, int frequency):开始录音
    • deviceName:麦克风设备名称,传入null表示使用默认麦克风
    • loop:是否循环录音
    • lengthSec:录音长度,单位为秒
    • frequency:采样频率,常用值有44100Hz、22050Hz等
  • Microphone.End(string deviceName):停止录音
    • deviceName:麦克风设备名称,传入null表示停止默认麦克风
  • Microphone.IsRecording(string deviceName):检测麦克风是否正在录音,返回布尔值
    • deviceName:麦克风设备名称,传入null表示检测默认麦克风

存储录音数据的AudioClip可以通过以下方式获取

1
2
3
4
5
6
AudioClip recordedClip = Microphone.Start(null, false, 10, 44100);
Microphone.End(null);

float[] samples = new float[recordedClip.samples * recordedClip.channels];
recordedClip.GetData(samples, 0);
print("Recorded " + samples.Length + " samples");

数据持久化

PlayerPrefs

PlayerPrefs类用于在本地存储和读取简单的数据,如整数、浮点数和字符串

基本方法

存储

  • PlayerPrefs.SetInt(string key, int value):存储整数值
  • PlayerPrefs.SetFloat(string key, float value):存储浮点数值
  • PlayerPrefs.SetString(string key, string value):存储字符串值

数据会在应用程序退出时自动保存,也可以手动调用PlayerPrefs.Save()方法来强制保存数据
崩溃时可能会丢失未保存的数据

读取

  • PlayerPrefs.GetInt(string key, int defaultValue = 0):读取整数值,若不存在则返回默认值
  • PlayerPrefs.GetFloat(string key, float defaultValue = 0.0f):读取浮点数值,若不存在则返回默认值
  • PlayerPrefs.GetString(string key, string defaultValue = ""):读取字符串值,若不存在则返回默认值

默认值都可以自己设置,如果不设置则为0或空字符串

判断键是否存在:

  • PlayerPrefs.HasKey(string key):检查指定键是否存在,返回布尔值

一般用于判断数据是否存在,避免读取不存在的数据时出错

删除

  • PlayerPrefs.DeleteKey(string key):删除指定键的数据
  • PlayerPrefs.DeleteAll():删除所有存储的数据

存储位置

PlayerPrefs的数据存储位置因平台而异:

  • Windows:注册表路径为HKEY_CURRENT_USER\Software\[公司名称]\[产品名称]

公司名称和产品名称是在Project Setting中设置的

由于数据是直接存储在注册表中,因此很容易被用户修改或删除,一般会加密处理

一个自定义的数据管理器类

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;

// 这是一个单例,所以不继承MonoBehaviour
public class PlayerPrefsDataManager
{
private static PlayerPrefsDataManager instance = new PlayerPrefsDataManager();

// 外部访问实例
public static PlayerPrefsDataManager Instance
{
get { return instance; }
}

private PlayerPrefsDataManager() { }

// 保存数据对象
public void SaveData(object obj, string keyName)
{
Type dataType = obj.GetType();
FieldInfo[] fieldInfos = dataType.GetFields();

string saveKeyName = "";
for (int i = 0; i < fieldInfos.Length; i++)
{
// 创建自定义的存储key
saveKeyName = keyName + "_" + dataType.Name + "_" + fieldInfos[i].FieldType.Name + "_" + fieldInfos[i].Name;
SaveValue(fieldInfos[i].GetValue(obj), saveKeyName);
}
}

// 递归保存值
// 分情况保存基本类型、列表、字典、自定义类
private void SaveValue(object value, string keyName)
{
Type fieldType = value.GetType();
// 字符串
if (fieldType == typeof(string))
{
PlayerPrefs.SetString(keyName, value.ToString());
}
// 布尔值
else if (fieldType == typeof(bool))
{
PlayerPrefs.SetInt(keyName, (bool)value ? 1 : 0);
}
// 浮点数
else if (fieldType == typeof(float))
{
PlayerPrefs.SetFloat(keyName, (float)value);
}
// 整数
else if (fieldType == typeof(int))
{
PlayerPrefs.SetInt(keyName, (int)value);
}
// 列表
// 考虑到List<T>是泛型类型,不能确定具体类型,所以用IsAssignableFrom来判断
// IList是List<T>的基类
else if (typeof(IList).IsAssignableFrom(fieldType))
{
IList list = value as IList;
PlayerPrefs.SetInt(keyName, list.Count);
int index = 0;
foreach (object obj in list)
{
SaveValue(obj, keyName + "_" + index);
index++;
}
}
// 字典
// 字典同样的道理,IDictionary是Dictionary<K,V>的基类
else if (typeof(IDictionary).IsAssignableFrom(fieldType))
{
IDictionary dict = value as IDictionary;
PlayerPrefs.SetInt(keyName, dict.Count);
int index = 0;
foreach (object key in dict.Keys)
{
SaveValue(key, keyName + "_key_" + index);
SaveValue(dict[key], keyName + "_value_" + index);
index++;
}
}
// 自定义类
// 再次进行递归保存
// 因为其实大部分数据类型到最后都是基本类型、列表或字典
else
{
SaveData(value, keyName);
}
PlayerPrefs.Save();
}

// 加载数据对象
public object LoadData(Type type, string keyName)
{
object data = Activator.CreateInstance(type);
FieldInfo[] infos = type.GetFields();
string loadKeyName = "";
FieldInfo info;

for (int i = 0; i < infos.Length; i++)
{
info = infos[i];
// 根据存储时的规则,创建对应的key
loadKeyName = keyName + "_" + type.Name + "_" + info.FieldType.Name + "_" + info.Name;
info.SetValue(data, LoadValue(info.FieldType, loadKeyName));
}
return data;
}

// 递归加载值
private object LoadValue(Type type, string keyName)
{
if (type == typeof(int))
{
return PlayerPrefs.GetInt(keyName, 0);
}
else if (type == typeof(float))
{
return PlayerPrefs.GetFloat(keyName, 0f);
}
else if (type == typeof(string))
{
return PlayerPrefs.GetString(keyName, "");
}
else if (type == typeof(bool))
{
return PlayerPrefs.GetInt(keyName, 0) == 1;
}
else if (typeof(IList).IsAssignableFrom(type))
{
int count = PlayerPrefs.GetInt(keyName, 0);
IList list = Activator.CreateInstance(type) as IList;
for (int i = 0; i < count; i++)
{
list.Add(LoadValue(type.GetGenericArguments()[0], keyName + "_" + i));
}
return list;
}
else if (typeof(IDictionary).IsAssignableFrom(type))
{
int count = PlayerPrefs.GetInt(keyName, 0);
IDictionary dict = Activator.CreateInstance(type) as IDictionary;
Type[] genericArgs = type.GetGenericArguments();
for (int i = 0; i < count; i++)
{
object dictKey = LoadValue(genericArgs[0], keyName + "_key_" + i);
object dictValue = LoadValue(genericArgs[1], keyName + "_value_" + i);
dict.Add(dictKey, dictValue);
}
return dict;
}
else
{
return LoadData(type, keyName);
}
}
}

以上是一个基本的数据保存和加载管理器类,可以根据需要进行扩展和修改

使用上

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 自定义的数据类
public class ItemInfo
{
public int id;
public int num;

public ItemInfo(int id, int num)
{
this.id = id;
this.num = num;
}

public ItemInfo()
{

}
}

// 自定义的玩家信息类
public class PlayerInfo
{
public string name;
public int age;
public float height;
public bool sex;

public List<int> list = new List<int>();
public Dictionary<int, string> dic = new Dictionary<int, string>();

public ItemInfo itemInfo;
public List<ItemInfo> infos = new List<ItemInfo>();

public Dictionary<int, ItemInfo> dict = new Dictionary<int, ItemInfo>();
}

public class Test : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
//PlayerPrefs.DeleteAll(); // 清除所有数据

// 尝试获取已保存的数据
PlayerInfo p = PlayerPrefsDataManager.Instance.LoadData(typeof(PlayerInfo), "playerInfo") as PlayerInfo;

// 如果没有数据,则创建一个新的
p.age = 18;
p.name = "北山";
p.height = 175.5f;
p.sex = true;

p.infos.Add(new ItemInfo(1, 99));
p.infos.Add(new ItemInfo(2, 99));

p.dict.Add(1, new ItemInfo(3, 99));
p.dict.Add(2, new ItemInfo(4, 99));

// 保存数据
PlayerPrefsDataManager.Instance.SaveData(p, "playerInfo");
}

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

}
}

UI系统

UI即用户界面(User Interface),用于与玩家进行交互,显示游戏信息等

GUI

GUI全称Graphical User Interface,即图形用户界面,IMGUIImmediate Mode GUI的缩写,意为即时模式图形用户界面

Unity中,GUI由代码直接驱动,实现较为复杂,所以一般也不会用它制作复杂的UI界面(如玩家的面板)

更多的时候,GUI用于调试信息的显示,而不是作为游戏UI的主要实现方式

使用上调用OnGUI()函数,在该函数中使用GUI类的方法绘制界面元素

  • 它是在每一帧渲染时调用的,所以界面会实时更新
  • 一般只在其中执行GUI相关的绘制与操作逻辑
  • 该函数在OnDisable之前,LateUpdate之后调用
  • 只要当前脚本继承自MonoBehaviour,都可以使用OnGUI()函数

基本控件

文本与按钮

  • 显示文本
    1
    GUI.Label(new Rect(10, 10, 100, 20), "Hello, World!");
    Rect参数表示位置和大小,前两个参数是显示在屏幕上的位置(x,y),后两个参数是文本宽度和高度

GUI坐标系的原点在屏幕左上角,x轴向右,y轴向下

  • 使用Texture显示图片

    1
    2
    Texture2D myTexture; // 需要在脚本中定义并赋值
    GUI.Label(new Rect(10, 40, 100, 100), myTexture);
  • 综合使用(文本+图片)

    1
    2
    GUIContent content = new GUIContent("Click Me", myTexture);
    GUI.Label(new Rect(10, 150, 100, 120), content);
  • 自定义样式(字体颜色、背景等)
    可直接声明一个GUIStyle对象,并在Inspector面板中设置

  • 创建按钮

    1
    GUI.Button(new Rect(10, 280, 100, 30), "Click Me");

同样也可以直接使用GUIContentGUIStyle来创建更复杂的按钮

检测按钮点击事件

1
2
3
4
if (GUI.Button(new Rect(10, 320, 100, 30), "Click Me"))
{
Debug.Log("Button Clicked!");
}

单选框与多选框

  • 创建多选框

    1
    2
    bool toggleValue = false; // 需要在脚本中定义
    toggleValue = GUI.Toggle(new Rect(10, 360, 100, 30), toggleValue, "Toggle Me");

    其中的布尔值是用来表示单选框的选中状态的,可以通过该值来判断用户是否选中了该选项

  • 创建单选框
    单选框在Unity中没有直接的实现,可以通过多个多选框来模拟单选框的效果

要解决的问题在于,当选中一个选项时,其他选项需要取消选中

1
2
3
4
5
6
7
8
9
10
11
12
if (GUI.Toggle(new Rect(10, 400, 100, 30), selectedOption == 1, "Option 1"))
{
selectedOption = 1;
}
if (GUI.Toggle(new Rect(10, 440, 100, 30), selectedOption == 2, "Option 2"))
{
selectedOption = 2;
}
if (GUI.Toggle(new Rect(10, 480, 100, 30), selectedOption == 3, "Option 3"))
{
selectedOption = 3;
}
  • 自定义样式
    使用自己的图片资源时,可能会出现宽高比显示的问题,可以通过调整GUIStylefixedWidthfixedHeight属性来解决

而显示的文字与图片的位置关系,可以通过padding属性来设置

输入框与拖动条

  • 创建普通输入框

    1
    2
    string inputText = ""; // 需要在脚本中定义
    inputText = GUI.TextField(new Rect(10, 520, 200, 30), inputText);

    由于GUI每一帧都会重新绘制,所以需要将输入的文本存储在一个变量中

  • 创建密码输入框

    1
    2
    string passwordText = ""; // 需要在脚本中定义
    passwordText = GUI.PasswordField(new Rect(10, 560, 200, 30), passwordText, '*');

    同样需要将输入的密码存储在一个变量中,第三个参数是用于掩码显示的字符(注意是字符型)

  • 创建水平拖动条

    1
    2
    float hSliderValue = 0f; // 需要在脚本中定义
    hSliderValue = GUI.HorizontalSlider(new Rect(10, 600, 200, 30), hSliderValue, 0f, 100f);

    水平拖动条的第三个和第四个参数分别表示拖动条的最小值和最大值

  • 创建垂直拖动条

    1
    2
    float vSliderValue = 0f; // 需要在脚本中定义
    vSliderValue = GUI.VerticalSlider(new Rect(220, 10, 30, 200), vSliderValue, 0f, 100f);

    垂直拖动条的第三个和第四个参数同样表示拖动条的最小值和最大值

图片绘制和框

  • 绘制图片

    1
    2
    Texture2D myTexture; // 需要在脚本中定义并赋值
    GUI.DrawTexture(new Rect(260, 10, 100, 100), myTexture);
  • 缩放模式(ScaleMode)

    • ScaleToFit:缩放图片以适应矩形区域,保持宽高比
    • ScaleToFill:缩放图片以填充矩形区域,可能会裁剪部分图片
    • ScaleAndCrop:缩放并裁剪图片以适应矩形区域,保持宽高比
  • 其他属性

    • alphaBlend:是否启用透明度混合(boolean)
    • imageAspect:图片的宽高比,0表示自动计算(float)
  • 绘制框

    1
    GUI.Box(new Rect(260, 120, 120, 50), "This is a box");

复合控件

工具栏与选择网格

  • 创建工具栏

    1
    2
    3
    int toolbarIndex = 0; // 需要在脚本中定义
    string[] toolbarOptions = { "Option 1", "Option 2", "Option 3" };
    toolbarIndex = GUI.Toolbar(new Rect(10, 640, 300, 30), toolbarIndex, toolbarOptions);

    也可以结合switch语句,根据选择的选项执行不同的逻辑

  • 创建选择网格

    1
    2
    3
    int gridIndex = 0; // 需要在脚本中定义
    string[] gridOptions = { "Grid 1", "Grid 2", "Grid 3", "Grid 4" };
    gridIndex = GUI.SelectionGrid(new Rect(10, 680, 300, 90), gridIndex, gridOptions, 2);

    选择网格的第四个参数表示每行显示的选项数量

滚动列表与分组

  • 创建分组

    1
    GUI.BeginGroup(new Rect(400, 10, 200, 200));

    在分组内绘制控件

    1
    2
    GUI.Label(new Rect(10, 10, 100, 20), "Group Label");
    GUI.Button(new Rect(10, 40, 100, 30), "Group Button");

    结束绘制分组

    1
    GUI.EndGroup();
  • 创建滚动列表

    1
    2
    3
    4
    5
    6
    7
    Vector2 scrollPosition = Vector2.zero; // 需要在脚本中定义
    scrollPosition = GUI.BeginScrollView(new Rect(400, 220, 200, 100), scrollPosition, new Rect(0, 0, 180, 300));
    for (int i = 0; i < 20; i++)
    {
    GUI.Label(new Rect(0, i * 30, 180, 30), "Item " + i);
    }
    GUI.EndScrollView();

    滚动列表的第三个参数是内容区域的矩形,决定了内容的实际大小

窗口

  • 创建窗口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Rect windowRect = new Rect(650, 10, 200, 150); // 需要在脚本中定义
    windowRect = GUI.Window(0, windowRect, MyWindowFunction, "My Window");

    void MyWindowFunction(int windowID)
    {
    GUI.Label(new Rect(10, 20, 180, 20), "This is a window");
    if (GUI.Button(new Rect(10, 50, 80, 30), "Close"))
    {
    // 关闭窗口的逻辑
    }
    }
  • 模态窗口
    模态窗口会阻止用户与其他界面交互,直到关闭该窗口

    1
    2
    3
    4
    5
    bool showModal = false; // 需要在脚本中定义
    if (showModal)
    {
    GUI.ModalWindow(1, new Rect(300, 200, 200, 150), MyModalFunction, "Modal Window");
    }
  • 可拖动窗口

    1
    2
    windowRect = GUI.Window(0, windowRect, MyWindowFunction, "Draggable Window"); // 位置传递是前提
    GUI.DragWindow(new Rect(0, 0, windowRect.width, 20)); // 允许拖动标题栏

自定义整体样式

全局颜色

  • 设置全局颜色
    1
    2
    3
    GUI.color = Color.red; // 设置所有控件的颜色为红色
    GUI.contentColor = Color.white; // 设置所有控件的内容颜色为白色
    GUI.backgroundColor = Color.blue; // 设置所有控件的背景颜色为蓝色
    注意颜色的计算方式是通过乘法计算的,所以如果设置了全局颜色,控件的颜色会受到影响

皮肤样式
可在Project面板中创建一个GUISkin资源,然后在Inspector面板中进行各种样式的设置

  • 应用皮肤样式
    1
    2
    GUISkin mySkin; // 需要在脚本中定义并赋值
    GUI.skin = mySkin; // 应用自定义的GUISkin

GUI自动布局
GUILayout类提供了一种更简单的方式来创建GUI界面,自动处理控件的位置和大小

  • 其中创建的内容与GUI类似,但不需要手动指定位置和大小

    1
    GUILayout.Label("Hello, World!");
  • GUILayoutOptions参数

    • Width(float width):指定控件的宽度
    • Height(float height):指定控件的高度
    • MinWidth(float minWidth):指定控件的最小宽度
    • MaxWidth(float maxWidth):指定控件的最大宽度
    • MinHeight(float minHeight):指定控件的最小高度
    • MaxHeight(float maxHeight):指定控件的最大高度
    • ExpandWidth(bool expand):是否扩展宽度以填充可用空间
    • ExpandHeight(bool expand):是否扩展高度以填充可用空间