目录
一、项目
1.1 创建新项目
1.2 目录含义
1.3 添加项目
二、游戏场景
2.1 新建游戏场景
2.2 保存游戏场景
2.3 本质
三、窗口
3.1 窗口布局
3.2 Hierarchy 窗口
3.3 Scene 窗口
3.3.1 常用工具
3.3.2 世界坐标轴
3.3.3 快捷键
3.4 Game 窗口
3.5 Project 窗口
3.6 Inspector 窗口
3.6.1 Inspector 窗口可显示编辑的变量
3.7 Console 窗口
四、工具栏
五、对象
5.1 对象的本质
5.2 对象间的父子关系
六、反射机制
七、预制体
7.1 什么是预制体
7.2 如何创建预制体
7.3 修改预制体
7.4 解除预制体绑定
八、资源包
8.1 资源包的导出
8.2 资源包的导入
九、脚本
9.1 脚本基本规则
9.2 生命周期函数
9.3 MonoBehavior
十、GameObject
十一、重要内容
11.1 Time 相关
11.2 Transform 相关
11.2.1 位置和位移
11.2.2 角度和旋转
11.2.3 缩放和看向
11.2.4 父子关系
11.2.4.1 小案例
11.2.5 坐标转换
一、项目
1.1 创建新项目
1.2 目录含义
创建新项目后,会生成一系列目录文件,如下:
Assets(重要):工程资源文件夹
Library:库文件夹(Unity 自动生成管理)
Logs:日志文件夹(Unity 自动生成管理)
obj:编译产生中间文件(Unity 自动生成管理)
Packages:包配置信息(Unity 自动生成管理)
ProjectSettings:工程设置信息(Unity 自动生成管理)
1.3 添加项目
选择某项目后,会在下方项目列表中添加该项目。
二、游戏场景
2.1 新建游戏场景
2.2 保存游戏场景
直接 Ctrl + s 即可。
2.3 本质
游戏场景的本质是一个配置文件(包含场景对象相关信息),后缀为.unity。
三、窗口
3.1 窗口布局
点击右上角 Layout,可以选择合适的布局。
3.2 Hierarchy 窗口
- Hierarchy 窗口和 Scene 窗口是息息相关的,Hierarchy 窗口中的内容就是 Scene 窗口中的显示对象。
- Hierarchy 窗口中可以创建或拖入各种游戏对象(eg:模型、光源、图片、UI 等),可以通过点击左上角 + 键或在窗口中右键创建对象。
3.3 Scene 窗口
3.3.1 常用工具
3.3.2 世界坐标轴
- 垂直屏幕向内为 Z 轴正方向。
- 平行屏幕向右为 X 轴正方向。
- 平行屏幕向上为 Y 轴正方向。
Iso模式
平行视野。无论摄像头远近,物体大小给人的感觉是一样大的。
Persp模式
透视视野。随摄像头远近,近大远小。
3.3.3 快捷键
3.4 Game 窗口
Game 游戏窗口显示的是场景中摄像机拍摄范围内的游戏对象(玩家看到的内容)。
3.5 Project 窗口
- 窗口显示的内容主要是 Assets 文件夹中的所有内容。
- Assets 文件夹中默认文件夹 Scenes:里面有一个默认空场景。
3.6 Inspector 窗口
查看场景中游戏对象关联的 C# 脚本信息。
3.6.1 Inspector 窗口可显示编辑的变量
using JetBrains.Annotations; using System.Collections; using System.Collections.Generic; using UnityEngine; public enum E_MyEnum { Player, Monster } public class MyClass1 { public int age; public string name; } [System.Serializable] public class MyClass2 { public int age; public string name; } public class Lesson2 : MonoBehaviour { // Inspector 窗口可显示编辑的变量就是 脚本(Lesson2) 中的成员变量 // 1.私有/保护成员变量无法显示编辑 private int num1; protected string str1; // 2.通过添加特性[SerializeField],使得私有/保护成员变量可以显示编辑 [SerializeField] private int num2; [SerializeField] protected string str2; // 3.公共成员变量可以显示编辑 public int num3; public bool flag1 = false; // 4.通过添加特性[HideInInspector],使得公共成员变量不可以显示编辑 [HideInInspector] public bool flag2 = false; // 5.对于大部分复杂数据类型,公共成员变量可以显示编辑。但字典、自定义数据类型等不可以显示编辑。 public int[] array; public List<int> list; public E_MyEnum type; public GameObject gameobj; public Dictionary<int, string> dic; public MyClass1 myClass1; // 6.通过添加特性[System.Serializable],使得自定义数据类型可以显示编辑 public MyClass2 myClass2; // 7.一些辅助特性(了解) // [Header("内容")] —— 分组 [Header("基础属性")] public int level; public int health; [Header("战斗属性")] // 以下变量都在战斗属性下 public int atk; public int def; // [Tooltip("说明内容")] —— 为变量添加说明 [Tooltip("这是玩家闪避率")] public int miss; // [Range(min, max)] —— 滑条范围 [Range(0, 10)] public float luck; // [ContextMenuItem("方法说明", "方法名")] —— 为变量添加快捷方法(在 Inspector 窗口右键点击该变量即可) [ContextMenuItem("初始化金钱", "FunctionMoney")] public int money; private void FunctionMoney() { money = 50; } // 8.注意点 // 将脚本拖拽到 GameObject 对象后,再在代码中修改变量默认值,界面上不会改变。 // 运行时在 Inspector 窗口中修改的信息不会保存。 }3.7 Console 窗口
用于查看调试信息的窗口。
四、工具栏
File:新建工程、新建场景、工程打包等
- BuildSetting(工程发布打包)。
Edit:对象编辑操作相关、工程设置、引擎设置相关
- Project Setting(工程各系统设置)
- Perferences(首选项,可以设置编程软件)
Assets:基本等同于 Project 窗口中右键相关功能
GameObject:基本等同于 Hierarchy 窗口中右键相关功能
- Move To View:将选中的 GameObject 移动到当前 Scene 视野 3D 空间的正中间位置。
- Align With View:将选中的 GameObject 与当前 Scene 视角对齐。Eg:选中 Main Camera 主摄像机,【GameObject】-【Align With View】,可将主摄像机设置到当前 Scene 场景的视点位置,摄像机视角与观察者视角完全一致。
Component:Unity 自带的脚本,可以添加各系统中的脚本
Window:可以打开 Unity 各核心系统的窗口
Help:检查更新、查看版本等
五、对象
5.1 对象的本质
不管是图片、模型、音效、摄像机等都是依附于 GameObject 对象的。 GameObject 对象可以理解为世界场景中的演员,会有很多剧本,Transform 就是其中一个必不可少的剧本(相当于一个 GameObject 类对象和 Transform 类对象进行关联)。
5.2 对象间的父子关系
- 子对象会随着父对象的变化而变化。
- 子对象 Inspector 窗口中 Transform 信息是相对于父对象的。
六、反射机制
反射允许代码在运行时获取类型的信息(如类、方法、属性、字段等),并动态地调用方法或访问成员,而无需在编译时知道这些成员的具体名称。
七、预制体
7.1 什么是预制体
预制体(Perfab)是一种可重复使用的项目资产(.perfab文件)。
7.2 如何创建预制体
选中对象并拖入 Assets 中即可。
可以看到预制体颜色为蓝色。
以后我们就可以将 Assets 中的预制体拖入世界场景中啦。
7.3 修改预制体
如果我们想对预制体做一定的修改,然后应用到所有预制体上,下面是一种方法。
直接在 open 中修改预制体也是一种方法。
7.4 解除预制体绑定
八、资源包
8.1 资源包的导出
然后选择想要导出的资源包进行导出。
导出的资源包后缀为.unitypackage。
8.2 资源包的导入
可以直接将资源包进行拖入,或者选择如下方式:
九、脚本
9.1 脚本基本规则
创建规则:
- 建议放在 Assets 文件夹下 统一文件夹进行管理。
- 建议脚本文件名和类名一致,若不一致,某些低版本无法挂载。
- 建议不要使用中文名命名。
继承 MonoBehavior 的类:
- 脚本默认继承 MonoBehavior 基类,只有这样才能挂载到 GameObject 上。
- 继承 MonoBehavior 的脚本不要写构造函数,因为我们不能 new,只能进行挂载。
- 继承 MonoBehavior 的脚本也可以再次被继承。
不继承 MonoBehavior 的类:
- 类不能挂载到 GameObject 上。
- 类可以 new。
- 类不用保留生命周期函数。
- 一般是单例模式类(用于管理模块)或者数据结构类(用于存储数据)。
继承 MonoBehavior 的脚本执行先后顺序:
略
9.2 生命周期函数
生命周期函数:继承 MonoBehavior 的脚本所依附的 GameObject 对象在整个生命周期中通过反射自动调用的一些特殊函数。支持继承多态。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Lesson1 : MonoBehaviour { // 生命周期函数的访问修饰符一般为 private 和 protected,因为 unity 会自动帮我们调用 // 1.Awake // 当脚本被创建时,会调用 Awake 生命周期函数,只会执行一次。 private void Awake() { // 打印信息的两种方式 // 1.任意情况 //Debug.Log("Awake"); // 2.继承 MonoBehaviour 类 print("Awake"); } // 2.OnEnable // 当脚本所依附的 GameObject 对象每次激活时调用 private void OnEnable() { print("OnEnable"); } // 3.Start // 当脚本被创建后,第一次帧更新之前调用,只会执行一次。 void Start() { print("Start"); } // 4.FixUpdate // 固定间隔执行,不受帧数波动的影响。可以通过 Edit->Project Setting->time 修改间隔时间。 private void FixedUpdate() { print("FixUpdate"); } // 5.Update // 每帧调用一次,受帧数波动的影响会时快时慢。 void Update() { print("Update"); } // 6.LateUpdate // 每帧调用一次,在 Update 执行后调用该函数。Eg:摄像机跟随玩家移动 private void LateUpdate() { print("LateUpdate"); } // 7.OnDisable // 当脚本所依附的 GameObject 对象每次失活时调用 private void OnDisable() { print("OnDisable"); } // 8.OnDestroy // 当脚本所依附的 GameObject 对象销毁时调用,只会执行一次。 private void OnDestroy() { print("OnDestroy"); } }9.3 MonoBehavior
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Lesson3 : MonoBehaviour { // Start is called before the first frame update void Start() { // 重要成员 // 1.获取依附的 GameObject print(gameObject); // 2.获取依附的 GameObject 的位置信息 print(transform.position); print(transform.eulerAngles); print(transform.lossyScale); print(gameObject.transform.position); // 同上 print(gameObject.transform.eulerAngles); // 同上 print(gameObject.transform.lossyScale); // 同上 // 3.获取脚本是否激活 print(enabled); // 重要方法 // 1.获取依附的 GameObject 的脚本(单个) // 根据泛型获取(常用) Lesson3Test temp1 = GetComponent<Lesson3Test>(); print(temp1); // 2.获取依附的 GameObject 的脚本(多个) Lesson3Test[] array1 = GetComponents<Lesson3Test>(); print(array1.Length); // 3.获取依附的 GameObject 的子对象的脚本(单个) // 注意:经测试,该方法会先在依附的 GameObject 中找。 Lesson3Test temp2 = GetComponentInChildren<Lesson3Test>(); print(temp2); // 4.获取依附的 GameObject 的子对象的脚本(多个) // 注意:经测试,该方法会先在依附的 GameObject 中找。 Lesson3Test[] array2 = GetComponentsInChildren<Lesson3Test>(); print(array2.Length); // 5.获取依附的 GameObject 的父对象的脚本(单个) // 注意:经测试,该方法会先在依附的 GameObject 中找。 Lesson3Test temp3 = GetComponentInParent<Lesson3Test>(); print(temp3); // 6.获取依附的 GameObject 的父对象的脚本(多个) // 注意:经测试,该方法会先在依附的 GameObject 中找。 Lesson3Test[] array3 = GetComponentsInParent<Lesson3Test>(); print(array3.Length); } // Update is called once per frame void Update() { } }十、GameObject
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Lesson4 : MonoBehaviour { // 可以是世界场景中的对象,也可以是预设体对象。然后在外部进行拖入即可 public GameObject objTemp; // Start is called before the first frame update void Start() { // GameObject 中的成员变量 // 1.名字 print(gameObject.name); // 2.是否激活 print(gameObject.activeSelf); // 3.是否是静态 print(gameObject.isStatic); // 4.层级 print(gameObject.layer); // 5.标签 print(gameObject.tag); // 6.transform print(gameObject.transform); // GameObject 中的静态方法 // 1.创建自带几何体 GameObject obj1 = GameObject.CreatePrimitive(PrimitiveType.Cube); // PrimitiveType是一个枚举 obj1.name = "MyCube"; // 2.查找 gameObject 对象(单个) // 以下方法特点:无法找到失活对象、若存在多个满足条件的对象,则结果不确定 // 2.1.根据对象名查找,效率低 GameObject obj2 = GameObject.Find("MyCube"); if(obj2 != null) { print(obj2.name); } // 2.2.根据 Tag 查找 GameObject obj3 = GameObject.FindWithTag("MainCamera"); if(obj3 != null) { print(obj3.name); } // 3.查找 gameObject 对象(多个) // 以下方法特点:无法找到失活对象 GameObject[] objs = GameObject.FindGameObjectsWithTag("Player"); print(objs.Length); // 4.实例化 gameObject 对象 GameObject obj4 = GameObject.Instantiate(objTemp); // 5.删除 gameObject 对象(还可以删除脚本) // 本质:给对象添加删除标识,在下一帧时删除对象并在内存中删除 //GameObject.Destroy(obj4); GameObject.Destroy(obj4, 5); // 延迟5s删除 // 6.默认情况下,世界场景切换时,gameObject 对象都会被自动删除。若某 gameObject 对象不想被删除,则如下操作 GameObject.DontDestroyOnLoad(gameObject); // 该脚本依附的 gameObject 对象 // GameObject 中的成员方法 // 1.创建空物体 GameObject obj5 = new GameObject("创建的空物体"); //GameObject obj6 = new GameObject("创建的带脚本的空物体", typeof(Lesson2), typeof(Lesson3)); // 2.为 gameObject 对象添加脚本 Lesson2 les2 = obj5.AddComponent<Lesson2>(); // 3.获取 gameObject 对象的脚本(类似于十一,略) // 4.标签比较 if (gameObject.CompareTag("Player")) { print("符合"); } // 5.设置激活/失活 obj5.SetActive(false); } // Update is called once per frame void Update() { } }十一、重要内容
11.1 Time 相关
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Lesson5 : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { // 1.时间缩放 // 时间停止 Time.timeScale = 0; // 恢复正常 Time.timeScale = 1; // 两倍速 Time.timeScale = 2; // 2.上一帧到当前帧所花费的秒数 // Time.deltaTime —— 受 Time.timeScale 的影响(相当于乘 Time.timeScale),适用于大部分游戏逻辑。Eg:打开菜单栏,Time.timeScale = 0,角色静止 print("Time.deltaTime" + Time.deltaTime); // Time.unscaledDeltaTime —— 不受 Time.timeScale 的影响。Eg:打开菜单栏,Time.timeScale = 0,角色依旧移动 print("Time.unscaledDeltaTime" + Time.unscaledDeltaTime); // 3.游戏开始到现在的时间 // Time.time —— 受 Time.timeScale 的影响 print("Time.time" + Time.time); // Time.unscaledTime —— 不受 Time.timeScale 的影响。 print("Time.unscaledTime" + Time.unscaledTime); // 5.游戏开始到现在跑了多少帧 print(Time.frameCount); } private void FixedUpdate() { // 4.上一物理帧到当前物理帧所花费的秒数(固定) // Time.fixedDeltaTime —— 受 Time.timeScale 的影响 print("Time.fixedDeltaTime" + Time.fixedDeltaTime); // Time.fixedUnscaledDeltaTime —— 不受 Time.timeScale 的影响。 print("Time.fixedUnscaledDeltaTime" + Time.fixedUnscaledDeltaTime); } }11.2 Transform 相关
11.2.1 位置和位移
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Lesson6 : MonoBehaviour { private GameObject objTemp; // Start is called before the first frame update void Start() { // Vector3基础 // 1.声明 Vector3 v1 = new Vector3(10, 10, 10); // 2.基本计算 Vector3 v2 = new Vector3(20, 20, 20); print(v1 + v2); // (30, 30, 30) print(v1 - v2); // (-10, -10, -10) print(v1 * 2); // (20, 20, 20) print(v2 / 2); // (10, 10, 10) // 3.常用 print(Vector3.zero); // (0, 0, 0) print(Vector3.right); // (1, 0, 0) print(Vector3.up); // (0, 1, 0) print(Vector3.forward); // (0, 0, 1) // 4.计算两向量之间距离 print(Vector3.Distance(v1, v2)); // 位置 // 1.获取对象世界坐标 print(gameObject.transform.position); // 2.获取对象相对坐标(相对于父对象,即 Inspector 窗口上显示的坐标) print(gameObject.transform.localPosition); // 注意:位置只能整体改变 objTemp = GameObject.Find("Cube"); objTemp.transform.position = new Vector3(20, objTemp.transform.position.y, objTemp.transform.position.z); // 3.获取对象当前朝向 print(gameObject.transform.right); print(gameObject.transform.forward); print(gameObject.transform.up); } // Update is called once per frame void Update() { // 位移 // 1.手动计算 //objTemp.transform.position += objTemp.transform.forward * 1 * Time.deltaTime; // 2.API(常用) // 参数1:位移 参数2:世界坐标系/自身坐标系 objTemp.transform.Translate(objTemp.transform.forward * 1 * Time.deltaTime, Space.World); } }11.2.2 角度和旋转
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Lesson7 : MonoBehaviour { private GameObject objTemp; // Start is called before the first frame update void Start() { // 角度 // 1.获取对象世界角度 print(gameObject.transform.eulerAngles); // 2.获取对象相对角度(相对于父对象,即 Inspector 窗口上显示的角度) print(gameObject.transform.localEulerAngles); // 注意:角度只能整体改变 objTemp = GameObject.Find("Cube"); objTemp.transform.eulerAngles = new Vector3(objTemp.transform.eulerAngles.x, 45, objTemp.transform.eulerAngles.z); } // Update is called once per frame void Update() { // 旋转 // 1.自己计算(经测试,会触发万向节锁现象) objTemp.transform.eulerAngles += Vector3.right * 10 * Time.deltaTime; // 2.API // 2.1 自转 —— 需要考虑三个轴 // 参数1:三个轴的旋转角度 参数2:世界坐标系/自身坐标系 objTemp.transform.Rotate(Vector3.up * 10 * Time.deltaTime, Space.World); // 自转 —— 需要考虑一个轴(个人感觉这种方式好理解) // 参数1:轴向 参数2:一个轴的旋转角度 参数3:世界坐标系/自身坐标系 objTemp.transform.Rotate(Vector3.up, 10 * Time.deltaTime, Space.World); // 2.2 绕某个点旋转 // 参数1:该点坐标 参数2:绕该点旋转的轴向 参数3:一个轴的旋转角度 objTemp.transform.RotateAround(Vector3.zero, Vector3.up, 10 * Time.deltaTime); } }11.2.3 缩放和看向
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Lesson8 : MonoBehaviour { public GameObject objTemp; public Transform transTemp; // Start is called before the first frame update void Start() { // 缩放 // 1.获取对象世界缩放 print(gameObject.transform.lossyScale); // 2.获取对象相对缩放(相对于父对象,即 Inspector 窗口上显示的缩放) print(gameObject.transform.localScale); // 注意:缩放只能整体改变(经测试,lossyScale 为只读) objTemp = GameObject.Find("Cube"); gameObject.transform.localScale = new Vector3(2, 2, 2); } // Update is called once per frame void Update() { // 缩放 // 1.自己计算 objTemp.transform.localScale += new Vector3(0.01f, 0.01f, 0.01f) * Time.deltaTime; // 看向 // 让某对象的面朝向 看向 某个点或者某个 transform 对象 //objTemp.transform.LookAt(Vector3.zero); objTemp.transform.LookAt(transTemp); } }11.2.4 父子关系
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Lesson9 : MonoBehaviour { public GameObject parent; // Start is called before the first frame update void Start() { // 自身为子对象 // 1.获取父对象 print(gameObject.transform.parent); // 2.设置父对象(经测试,会将原父对象覆盖) gameObject.transform.parent = GameObject.Find("Father").transform; // 通过 API 设置父对象(经测试,会将原父对象覆盖) // 参数1:父对象 参数2:bool类型,默认为 true。若为 false,子对象 Inspector 窗口中 transform 信息保持不变,即子对象可能会发生变化。 gameObject.transform.SetParent(GameObject.Find("Father").transform); // 3.判断自身是否为某对象的子对象 if (gameObject.transform.IsChildOf(parent.transform)) { print("Yes"); } // 4.获取自身是第几个子对象 print(gameObject.transform.GetSiblingIndex()); // 5.将自身设置为第一个/最后一个子对象 gameObject.transform.SetAsFirstSibling(); gameObject.transform.SetAsLastSibling(); gameObject.transform.SetSiblingIndex(0); // 将自身设置为第 x 个对象 // 自身为父对象 // 1.按名字获取子对象 // 注意点1:该方法能找到失活对象。 // 注意点2:该方法不能找到子对象的子对象。 print(gameObject.transform.Find("Sphere")); // 2.获取子对象数量 // 注意点1:该方法能找到失活对象。 // 注意点2:该方法不能找到子对象的子对象。 print(gameObject.transform.childCount); // 3.通过索引号获取子对象 print(gameObject.transform.GetChild(0)); // 遍历子对象 for (int i = 0; i < gameObject.transform.childCount; i++) { print(gameObject.transform.GetChild(i)); } // 4.切断自身和所有子对象的关系 gameObject.transform.DetachChildren(); } // Update is called once per frame void Update() { } }11.2.4.1 小案例
using System.Collections; using System.Collections.Generic; using UnityEngine; // 扩展方法(Extension Methods)的核心思想是:给已有类"追加"新功能 // 为 Transform 类添加扩展方法 public static class Tools { // 根据子对象名字长短排序 public static void Sort(this Transform obj) // this 是扩展方法的标志,即我要为 Transform 类型扩展一个 Sort 方法 { List<Transform> list = new List<Transform>(); for(int i = 0; i < obj.childCount; i++) { list.Add(obj.GetChild(i)); } // 根据名字长短排序 list.Sort((a, b) => { if(a.name.Length < b.name.Length) return -1; else return 1; }); for(int i = 0; i < list.Count; i++) { list[i].SetSiblingIndex(i); } } // 根据名字找到子对象的子对象 public static Transform CustomFind(this Transform obj, string childName) { Transform target = null; // 先在自己身上找 target = obj.Find(childName); if(target != null) return target; for(int i = 0; i < obj.childCount; i++) { target = obj.GetChild(i).CustomFind(childName); if (target != null) return target; } return target; } }using System.Collections; using System.Collections.Generic; using UnityEngine; public class Lesson10 : MonoBehaviour { // Start is called before the first frame update void Start() { gameObject.transform.Sort(); print(gameObject.transform.CustomFind("building")); } // Update is called once per frame void Update() { } }