目录
Unity脚本基础
生命周期函数 知识点
Inspector窗口可编辑变量 知识点
- 私有和保护是无法显示编辑的
- 让私有和保护可以显示编辑
加上强制序列化字段特性 [SerializeField] (序列化就是把一个对象保存到一个文件或数据库字段中)
如:[SerializeField] private string privateStr
- 公共的可以显示编辑
- 公共让其不能显示编辑
加上特性 [HideInInspector] - 大部分类型都能显示编辑
如//可以被显示 public int[] array; public List<int> list; public E_TestEnum type; public GameObject gameObj; //不能被显示(字典,自定义类型) public Dictionary<int,string> dic; public MyStruct myStruct; public MyClass myClass;
- 让自定义类型可以显示编辑
加上序列化特性 [System.Serializable][System.Serializable] public struct MyStruct { public int age; public bool sex; }
- 一些辅助特性
//1. 分组特性 Header [Header("基础属性")] public int age; public bool sex; [Header("战斗属性")] public int atk; public int def; //2. 悬停注释 Tooltip // 为变量添加说明 [Tooltip("闪避")] public int miss; //3. 间隔特性 Space(); [Space()] public int crit; //4. 修饰数值的滑条范围 Range [Range(min, max)] public float luck; //5. 多行显示字符串,默认不写参数显示3行 //先参数对应行 [Multiline()] public string tips; //6. 滚动条显示字符串 //默认不写为3行 [TextArea(min, max)] public string myLife; //7. 为变量添加快捷方法 ContextMenuItem //方法名不能有参数 [ContextMenuItem("显示按钮名", "方法名")] public int money; //8. 为方法增加特性能够在Inspector中执行 [ContextMenu("哈哈哈哈")] private void TestFun() { print("测试方法"); }
注意
1. Inspector窗口中的变量关联的就是对象的成员变量,运行时改变他们就是在改变成员变量
2.拖拽到GameObject对象后,再改变脚本中变量默认值,界面上不会改变
3.运行中修改的信息不会保存(可以copy后paste)
MonoBehavior中的重要内容
1. 重要成员
1.获取依附的GameObject
//gameObject方法
print(this.gameObject.name);
2.获取依附的GameObject的位置信息
print(this.transform.position);
print(this.transform.eulerAngles);
print(this.transform.lossyScale);
//与上面效果相同
//this.gameObject.transform
3.获取脚本是否激活
this.enable = true;
4.获取别的脚本对象依附的gameObject的信息
public Lesson3 otherLesson3;
//...
print(otherLesson3.gameObject.name);
print(otherLesson3.transform.position);
2. 重要方法
1.得到自己挂载的单个脚本
//根据脚本名获取,获取失败返回Null
Lesson3_Test t = this.GetComponent("Lesson3_Test") as Lesson3_Test;
//返回的基类,可以用as 转换为子类
print(t);
//根据Type获取
t = this.GetComponent(typeof(Lesson3_Test)) as Lesson3_Test;
print(t);
//根据泛型获取(不用二次转换)
t = this.GetComponent<Lesson3_Test>();
//只要能得到场景中别的对象或其依附的脚本,则能得到它的所有信息
//若挂载多个同一类型脚本,无法确定得到的是哪个(故一般不会挂载多个同一类型脚本)
2.得到自己挂载的多个脚本
//1
Lesson3[] array = this.GetComponents<Lesson3>();
//2
List<Lesson3> list = new list<Lesson3>();
this.GetComponents<Lesson3>(list);
3.得到子对象(子的子对象也行)挂载的脚本(默认也会找自己身上是否挂载该脚本)
t = this.GetComponentInChildren<Lesson3_Test>();
//参数默认False,失活时不找,若参数为True,失活也找
//多个脚本
Lesson3_Test[] lts = this.GetComponentsInChildren<Lesson3_Test>(true);
List<Lesson3_Test> list2 = new List<Lesson3_Test>;
this.GetComponentsInChildren<Lesson3_Test>(true, list2);
4.得到父对象(父的父亲也行)挂载的脚本(默认也会找自己身上是否挂载该脚本)
t = this.GetComponentInParent<Lesson3_Test>();
//多个
lts = this.GetComponentsInParent<Lesson3_Test>();
//也有List的
5.尝试获取脚本
Lesson3_Test l3t;
//提供了更安全的获取单个脚本的方法,得到了返回true
if(this.TryGetComponent<Lesson3_Test>(out l3t))
{
...
}
Unity重要组件和Api
GameObject知识点
GameObject的成员变量
//名字
print(this.gameObject.name);
this.gameObject.name = "唐老狮";
//是否激活
print(this.gameObject.activeSelf); // bool
//是否静态
print(this.gameObject.isStatic);
//层级
print(this.gameObject.layer); //int
//标签
print(this.gameObject.tag); //string
//transform
//this.transform 上一知识点
//得到信息相同
print(this.gameObject.transform.position);
GameObject的静态方法
//创建自带几何体
//只要的到了一个GameObject对象,就可以得到它挂载的任何脚本信息
//通过obj.GetComponent来得到
GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
obj.name = "我创建的立方体";
//查找对象的相关知识
string tip = "无法找到失活对象"; //注意
string tip1 = "若场景中存在多个满足条件的对象,无法确定找到的是谁";
//1. 查找单个对象
// 通过对象名查找(效率低,因为会在场景中所有对象去查找)
GameObject obj2 = GameObject.Find("唐老狮");
if(obj2 != null)
{
print(obj2.name);
}
else {...}
//通过tag查找对象
//GameObject obj3 = GameObject.FindWithTag("Player");
//该方法与上述效果相同
GameObject obj3 = GameObject.FindGameObjectWithTag("Player");
目前有两种方式得到某一单个对象:
1. 是public从外部面板拖,进行关联
2. 通过API来找
// 2. 查找多个对象
string tip3 = "同样只能找激活对象";
//找多个对象的API,只能通过tag
GameObject[] objs = GameObject.FindGameObjectsWithTag("Player");
print("找到tag为Player对象的个数" + objs.Length);
//用的比较少的方法,GameObject父类 Object类的方法
//Unity里的Object是命名空间在UnityEngine中的Object类,也是集成万物之父的一个自定义类
//C#中的Object命名空间在System中
//可以找到场景中挂载的某一脚本对象
//效率更低,要遍历对象,还要遍历它挂载的脚本
Lesson4 o = GameObject.FindObjectofType<Lesson4>();
print(o.gameObject.name);
实例化对象(克隆对象)的方法
实例化(克隆)对象 ,作用是根据一个GameObject对象,克隆一个
//准备克隆的对象
//可以是一个场景对象
//也可以是一个预设体对象
public GameObject myObj;
...
//克隆
GameObject obj5 = GameObject.Instantiate(myObj);
//以后学了更多知识点,就可以操作obj5
//如果继承了MonoBehavior,则不用GameObject
//该方法是 Unity中的Object基类提供的
Instantiate(myObj);
删除对象的方法
GameObject.Destroy(obj5);
//第二个参数 代表延迟几秒钟删除
GameObject.Destroy(obj5, 5);
//Destroy 还可以删除脚本
GameObject.Destroy(this); //删除自己这个脚本
删除对象有两个作用
1. 删除指定的一个游戏对象
2. 删除一个指定的脚本对象
注意:Destroy方法不会马上移除对象,只给对象加一个移除标识,一般情况下会在下一帧时移除并从内存中移除
//没特殊需求建议上一方法,因为异步 降低卡顿几率
GameObject.DestroyImmediate(myObj);
该方法会马上删除对象
如果是继承MonoBehavior类,不用写GameObject
Destroy(myObj2);
DestroyImmediate(myObj);
过场景不移除
默认情况下过场景会移除场景中的对象
//自己依附的GameObject对象过场景不被移除
GameObject.DontDestroyOnLoad(this.gameObject);
//如果继承MonoBehavior也可以写
DontDestroyOnLoad(this.gameObject);
GameObject中的成员方法
创建空物体
//new一个GameObject对象就是创建空物体
GameObject obj6 = new GameObject();
GameObject obj7 = new GameObject("创建的空物体");
GameObject obj8 = new GameObject("顺便加脚本的空物体", typeof(Lesson2), typeof(Lesson1)); //脚本数可变
为对象添加脚本
//继承MonoBehavior的脚本 不能够去new
//如果想要动态的添加继承MonoBehavior的脚本 在某一对象上
//直接用GameObject提供的方法
Lesson1 les1 = obj6.AddComponent(typeof(Lesson1)) as Lesson1;
obj6.AddComponent<Lesson2>();
string tip = "使用泛型更方便";
//通过返回值可以得到加入的脚本信息
//得到脚本的成员方法和继承Mono类得到脚本的方法一样
标签比较
if(this.gameObject.CompareTag("Player"))
{
print("对象的标签是 Player");
}
if(this.gameObject.tag == "Player")
{
print("对象的标签是 Player");
}
设置激活/失活
//false 失活
//true 激活
obj6.SetActive(false);
次要的成员方法(了解即可,不建议使用,因为效率低)
//通知自己 执行什么行为
//命令自己 去执行TestFun这个函数,会在自己身上挂载的所有脚本中去找该名字(可能重复名字)的函数
this.gameObject.SendMessage("TestFun", 参数);
//广播行为,让自己和自己的子对象执行
this.gameObject.BroadcastMessage("函数名");
//向父对象和自己发送消息并执行
this.gameObject.SendMessageUpwards("函数名");
时间相关 Time
时间缩放比例
//时间停止
Time.timeScale = 0;
//恢复正常
Time.timeScale = 1;
//2倍速
Time.timeScale = 2;
帧间隔时间(最近的一帧用了多长时间(秒))
//受scale影响
print(Time.deltaTime);
//不受scale影响的帧间隔时间
print(Time.unscaledDeltaTime);
帧间隔时间 主要用来计算位移, 路程 = 时间 * 速度
根据需求选择帧间隔时间
deltaTime = unscaledDeltaTime * timeScale
游戏开始到现在的时间
//它主要用来 在单机游戏中计时 ,网络游戏用服务器时间
//收scale影响
print(Time.time);
//不受scale影响
print(Time.unscaledTime);
物理帧间隔时间 用于FixedUpdate
//收scale影响
print(Time.fixedDeltaTime);
//不受scale影响
print(Time.fixedUnscaledDeltaTime);
帧数
//从开始到现在游戏跑了多少帧(多少次循环)
print(Time.frameCount);
Transform
游戏对象(GameObject) 位移、旋转、缩放、父子关系、坐标转换等相关操作都由它处理
它是Unity提供的极其重要的类
Vector3基础和Transform位置
Vector3主要用来表示三维坐标系中的 一个点 或 一个向量
Vector3声明
Vector3 v = new Vector3();
v.x = 10;
v.y = 10;
v.z = 10;
//只传x,y 默认z是0
Vector3 v2 = new Vector3(10, 10);
//一步到位
Vector3 v3 = new Vector3(10,10,10);
//Vector3 为结构体,可以不new,默认值均为0
Vector3 v4;
Vector的基本运算
// + - * /
//+ - 就是对应加减
print(v1 * 10);
print(v1 / 2); //xyz均乘除
Vector3常用向量
print(Vector3.zero); // 0 0 0
print(Vector3.right); // 1 0 0
print(Vector3.left); // -1 0 0
print(Vector3.forward); // 0 0 1
print(Vector3.back); // 0 0 -1
print(Vector3.up); // 0 1 0
print(Vector3.down); // 0 -1 0
Vector3常用方法
//计算两个点的距离
print(Vector3.Distance(v1, v12));
位置
//相对世界坐标系
//this.gameObject.transform
//通过position得到的位置 是相对于 世界坐标系 原点的位置
//可能和面板上显示不同
//因为如果对象有父子关系,可能是相对父对象的坐标
print(this.transform.position);
//相对父对象
//如果想要以面板坐标为准则使用该方法
print(this.transform.localPosition);
两种方法得到的坐标可能一样
1. 父对象的坐标就是 世界坐标系原点 0,0,0
2. 对象没有父对象
注意:位置的赋值不能直接改变x, y, z 只能整体改变
//this.transform.position.x = 10;
//上述方法不行
this.transform.position = new Vector3(10, 10, 10);
this.transform.localPosition = Vector3.up * 10;
//如果只想改一个值x
//1.直接赋值
this.transform.position = new Vector3(10, this.transform.position.y, this.transform.position.z);
//2.先取出来,再赋值
Vector3 vPos = this.transform.localPosition;
vPos.x = 101;
this.transform.localPosition = vPos.x;
对象当前的各朝向
//如果想得到对象当前的 一个朝向
//那么就是通过 transform.出来的
//对象当前的面朝向
print(this.transform.forward);
//对象当前的头顶朝向
print(this.transform.up);
//对象当前的右手边
print(this.transform.right);
Transform位移
理解坐标系下的位移计算公式
路程 = 方向 * 速度 * 时间
//方式一 自己计算
//用当前的位置 + 我要动多长距离 得出最终位置
//this.transform.forward 相对于自己的面朝向
//Vector3.forward 世界坐标系的正朝向 0 0 1
this.transform.position += this.transform.forward * 1 * Time.deltaTime;
//方式二 API
//参数一: 表示位移多少
//参数二: 表示 相对坐标系 默认相对于自己坐标系
//相对于世界坐标系的 Z轴 动
this.transform.Translate(Vector3.forward * 1 * Time.deltaTime, Space.World);
//相对于世界坐标系的 自己面朝方向 动
this.transform.Translate(this.transform.forward * 1 * Time.deltaTime, Space.World);
//相对于自己坐标系的 自己面朝方向 动 (一定不会这样让物体移动!)
//this.transform.forward 得到的是相对于世界坐标系的方向, 而相对于自己坐标系 会进一步偏移
this.transform.Translate(this.transform.forward * 1 * Time.deltaTime, Space.Self);
//相对于自己坐标系下的 Z轴 正方向移动
this.transform.Translate(Vector3.forward * 1 * Time.deltaTime, Space.Self);
Transform角度和旋转
角度相关
//相对世界坐标的角度
print(this.transform.eulerAngles);
//相对于父对象的角度
print(this.transform.localEulerAngles);
//注意:设置角度和设置位置一样,不能单独设置xyz,要一起设置
this.transform.localEulerAngles = new Vector3(10, 10, 10);
//如果希望改变的 角度 是面板上的内容, 那一定是改变 相对父对象的角度
旋转相关
//API计算
//自转
//每个轴 具体转多少度
//参数一 相当于 每一帧 旋转的角度
//参数二 默认 相对于自己坐标系
this.transform.Rotate(new Vector3(0,10,0) * Time.deltaTime);
this.transform.Rotate(new Vector3(0,10,0) * Time.deltaTime, Space.World);
//相对于某个轴 转多少度
//参数一 相对于哪个轴进行转动
//参数二 转动的 角度 是多少
//参数三 默认相对于自己坐标系
this.transform.Rotate(Vector3.up, 10 * Time.deltaTime);
//相对于某一个点旋转
//参数一 相对于哪个轴进行转动
//参数二 转动的 角度 是多少
//参数三 默认相对于自己坐标系
this.transform.RotateAround(Vector3.zero, Vector3.up, 10 * Time.deltaTime);
Transform 缩放和看向
缩放
//相对于世界坐标系
print(this.transform.lossyScale);
//相对于本地坐标系(父对象)
print(this.transform.localScale);
注意:1.同样缩放不能只改xyz,要一起改(相对于世界坐标系的大小,只能得不能改)
所以一般改缩放大小 都是改 相对于父对象的
this.transform.localScale = new Vector3(3,3,3);
2.Unity没有提供关于缩放的API,只能自己写
this.transform.localScale += Vector3.one * Time.deltaTime;
看向
让一个对象的面朝向, 可以一直看某一点或某一对象
//看向一个点 相对于世界坐标系
this.transform.LookAt(Vector3.zero);
//看向一个对象
public Transform lookAtObj;
...
this.transform.LookAt(lookAtObj);
Transform父子关系
获取和设置父对象
//获取父对象
print(this.transform.parent.name);
//设置父对象,断绝父子关系
this.transform.parent = null;
//设置父对象,认爸爸
this.transform.parent = GameObject.Find("father2").transform;
//通过API来进行父子关系设置
this.transform.SetParent(null);
this.transform.SetParent(GameObject.Find("father2").transform);
//参数一:我的父亲
//参数二:是否保留世界坐标的 位置 角度 缩放 信息
//true 会保留 世界坐标下的状态 和 父对象 进行计算 得到本地坐标系的信息
// 子对象position*父对象scale + 父对象 position = 原世界坐标position 大概?
// 子对象scale * 父对象 scale = 原scale
//其实就是子对象的信息转换到以父对象为标准的坐标系下
//false 不会保留 会直接把世界坐标系下的 位置 角度 缩放 直接赋值到本地坐标系下
this.transform.SetParent(GameObject.Find("father2").transform, true);
抛妻弃子
//就是和所有儿子断绝关系 , 但儿子和孙子之间的关系不影响
this.transform.DetachChildren();
获取子对象
//按名字查找儿子
//找到儿子的 transform信息
//Find能找到失活对象,GameObject相关的不能找到
print(this.transform.Find("Cube(1)").name);
//只能找到自己的儿子,不能找到孙子
//虽然效率比 GameObject.Find相关 要高, 但前提需要知道父对象
//遍历儿子
//如何得到有多少个儿子
//1.失活的儿子也算数量
//2.找不到孙子,孙子不会算数量
print(this.transform.childCount);
//通过索引号 去得到相应的儿子 [0,this.transform.childCount)
//如果编号 超出了儿子数量 会报错
//返回值是 transform
this.transform.GetChile(0);
儿子的操作
//判断自己的爸爸是谁
//一个对象 判断自己是不是 另一个对象 的儿子
public Transform son;
...
//son.transform.IsChildOf(this.transform)
if(son.IsChildOf(this.transform)){...}
//得到自己作为儿子的编号
print(son.GetSiblingIndex());
//把自己设置为第一个儿子
son.SetAsFirstSibling();
//把自己设置为最后一个儿子
son.SetAsLastSibling();
//把自己设置为指定个儿子
//就算 填的数量 超出范围(负数 或 更大的数) 也会直接放到最后
son.SetSiblingIndex(15);
Transform坐标转换
世界坐标转本地坐标
//世界坐标系 转 本地坐标系 可以帮助我们大概判断一个相对位置
//世界坐标系的点 转换为 相对本地坐标系的点
//受到缩放影响
print("转换后的点" + this.transform.InverseTransformPoint(Vector3.forward));
//世界坐标系的方向 转换为 相对本地坐标系的方向
//把方向向量 移到本地原点位置
//不受缩放影响
print("转换后的方向" + this.transform.InverseTransformDirection(Vector3.forward));
//受缩放影响
print("转换后的方向(受缩放影响)" + this.transform.InverseTransformVector(Vector3.forward));
本地坐标转世界坐标(重要)
//本地坐标系点 转换为 相对世界坐标系点 (受缩放影响)
print("本地 转 世界 点" + this.transform.TransformPoint(Vector3.forward));
//本地坐标系的方向 转换为 世界坐标系的方向
//不受缩放影响
print("本地 转 世界 方向" + this.transform.TransformDirection(Vector3.forward));
//受缩放影响
print("本地 转 世界 方向(受缩放影响)" + this.transform.TransformVector(Vector3.forward));
最重要的是 本地坐标系点转换为世界坐标系的点
比如 玩家 再 自己面前 n个单位 放一团火, 就不用关心世界坐标系。
Input鼠标键盘输入
输入相关内容肯定写在Update当中
鼠标在屏幕位置
//屏幕坐标的原点 在 屏幕左下角 往右 是x正方向 往上 是y正方向
//返回值Vector3 但是只有 x 和 y ,z一直是0,因为屏幕是2d的
print(Input.mousePosition);
检测鼠标输入
作用:
1.可以做 发射子弹
2.可以控制摄像机 转动
//鼠标按下一瞬间 进入
//0左键 1右键 2中键
if(Input.GetMouseButtonDown(0)){
print("鼠标某一个键按下了");
}
//鼠标抬起一瞬间
//0左键 1右键 2中键
if(Input.GetMouseButtonUp(0)){
print("鼠标某一个键抬起了");
}
//鼠标长按按下抬起都会进入
if(Input.GetMouseButton(0)){
print("左键按下");
}
//中键滚动
//返回值的 y -1下滚 0无滚 1上滚
//它的返回值 是Vector的值 滚动鼠标中键 会改变y的值
print(Input.mouseScrollDelta);
检测键盘输入
//KeyCode 为枚举
if(Input.GetKeyDown(KeyCode.W))
{
print("W键按下");
}
传入字符串的重载
//传入的 字符串 不能为大写 不然会报错
//但是大小写都能识别
if(Input.GetKeyDown("q"))
{
print("Q键按下")
}
键盘抬起和长按
if(Input.GetKeyUp(KeyCode.W))
{
print("W键抬起");
}
if(Input.GetKey(KeyCode.W))
{
print("W键长按");
}
检测默认轴输入
我们学习鼠标 键盘输入 主要用于控制玩家 比如 旋转 位移
Unity为此提供了更方便的方法
//具体名可以看 project setting 中的 input manager
//键盘AD按下 返回 -1 到 1 的变换
// 相当于 得到的这个值就是 我们的左右方向 可以用它控制左右移动 旋转
print(Input.GetAxis("Horizontal"));
//键盘SW按下 返回 -1 到 1 的变换
print(Input.GetAxis("Vertical"));
//鼠标横向移动 返回 -1 到 1 的变换
print(Input.GetAxis("Mouse X"));
//鼠标竖向移动 返回 -1 到 1 的变换
print(Input.GetAxis("Mouse Y"));
//GetAxisRaw方法 和 GetAxis使用方式相同
//只不过 它的返回值 只会是 -1 0 1 不会有中间值
Input触摸手柄陀螺仪
//是否有任意键或鼠标长按
if(Input.anyKey)
{
...
}
//是否有任意键或鼠标长按
if(Input.anyKeyDown)
{
...
}
//这一帧的键盘输入 可有上面2个结合使用
print(Input.inputString);
//手柄输入相关
//得到连接的手柄的所有按钮名字
string[] strs = Input.GetJoystickNames();
//某一手柄键按下
if(Input.GetButtonDown("Jump")){...}
//某一手柄键抬起
if(Input.GetButtonUp("Jump")){...}
//某一手柄键长按
if(Input.GetButton("Jump")){...}
//移动设备触摸相关
if(Input.touchCount > 0)
{
Touch t1 = Input.touches[0];
//位置
print(t1.position);
//相对上次位置的变化
print(t1.deltaPosition);
}
//是否启用多点触控
Input.multiTouchEnabled = false;
//陀螺仪(重力感应)
//开启才能正常使用
Input.gyro.enabled = true;
//重力加速度向量
print(Input.gyro.gravity);
//旋转速度
print(Input.gyro.rotationRate);
//陀螺仪 当前的旋转四元数
//比如 用这个角度信息 来控制 场景上的一个3D物体受重力影响
//手机怎么动 它怎么动
print(Input.gyro.attitude);
屏幕相关Screen
静态属性
常用
//当前屏幕分辨率
Resolution r = Screen.currentResolution;
print("当前屏幕分辨率的宽" + r.width + "高" + r.height);
//屏幕窗口当前宽高 对应game窗口的宽高
//一般写代码 要用窗口宽高 做计算时 就用他们
print(Screen.width);
print(Screen.height);
//屏幕休眠模式
Screen.sleepTimeout = SleepTimeout.NeverSleep;
不常用
//运行时是否全屏模式
Screen.fullScreen = true;
//窗口模式
//独占全屏 FullScreenMode.ExclusiveFullScreen
//全屏窗口 FullScreenMode.FullScreenWindow
//最大化窗口 FullScreenMode.MaximizedWindow
//窗口模式 FullScreenMode.Windowed
Screen.fullScreenMode = FullScreenMode.ExclusiveFullScreen
//移动设备屏幕转向相关
//允许自动旋转为左横向 Home键在左
Screen.autorotateToLandscapeLeft = true;
//允许自动旋转为右横向 Home键在右
Screen.autorotateToLandscapeRight = true;
//允许自动旋转为纵向 Home键在下
Screen.autorotateToPortrait = true;
//允许自动旋转为纵向倒着看 Home键在上
Screen.autorotateToPortraitUpsideDown = true;
//指定屏幕显示方向
Screen.orientation = ScreenOrientation.Landscape;
静态方法
//设置分辨率 一般移动设备不使用 true表示全屏
Screen.SetResolution(1920,1080,false);
Camera可编辑参数说明
Clear Flags
skybox 天空盒(3D) Solid Color 颜色填充(2D)
Depth only 只画该层,背景透明(于Dept配合使用) Don‘t Clear 不移除 覆盖渲染
深入讲解:在Unity中使用多个相机 - 及其重要性 - 简书 (jianshu.com)
Culling Mask
选择性渲染部分层级(可以指定只渲染对应层级的对象)
Projection
Clipping Planes
裁剪平面距离
Depth
渲染顺序上的深度 值越小越先被渲染 后渲染的内容可以挡住先渲染的内容
可以做到让场景中的多个摄像机叠加显示(Depth only)
Target Texture
渲染纹理:
可以把摄像机画面渲染到一张图上(用于制作小地图)
在Project右键创建 Render Texture
Occlusion Culling
是否启用剔除遮挡(会让被挡住的不被渲染)
其它
Camera代码相关
重要静态成员
//1.获取摄像机
//如果用之前的知识 来获取摄像机 定义公共变量,拖进来
//主摄像机的获取
//若想要通过这种方式,必须要有MainCamera的tag
print(Camera.main.name);
//获取摄像机的数量
print(Camera.allCamerasCount);
//得到所有摄像机
Camera[] allCamera = Camera.allCameras;
print(allCamera.Length);
//2.渲染相关委托
//摄像机剔除前处理的委托函数
Camera.onPreCull += (c) =>
{
};
//摄像机渲染前处理的委托函数
Camera.onPreRender += (c) =>
{
};
//摄像机渲染后处理的委托函数
Camera.onPostRender += (c) =>
{
};
重要成员
//1.界面上的参数 都可以在Camera中获取到
//比如 下面是得到 主摄像机对象 上的深度
Camera.main.depth = 10;
//2.世界坐标转屏幕坐标
//xy对应屏幕坐标 v对应离摄像机有多远
//我们会用来做的最多的功能 头顶血条
Vector3 v = Camera.main.WorldToScreenPoint(this.transform.position);
//3.屏幕坐标转世界坐标
//之所以改变z轴 是因为 不改 默认为0
//转换过去的世界坐标的点 永远是一个点 可以理解为 视口 相交的焦点
//若改变z 则相当于距摄像机前方多少单位的横截面上的点
Vector3 v = Input.mousePosition;
v.z = 5;
obj.position = Camera.main.ScreenToWorldPoint(Input.mousePosition);
核心系统
光源组件
面板参数
Type
Color & Mode & Intensity
Shadow Type
Cookie
投影遮罩(光图形贴图)
Draw Halo & Flare
Draw halo(光晕效果) Flare(耀斑效果,摄像机要加上Flare layer脚本)
Culling Mask
剔除遮罩层,决定哪些层会受到光源影响
其他
代码控制
直接改面板上的值就够用
light.intensity = 0.5f;
光相关面板
Window->Rendering->Lighting Settings
Environment
OtherSettings
碰撞检测
刚体
碰撞产生的必要条件:两个物体都有碰撞器 至少一个物体有刚体(加入RigidBody脚本)
注意点:
物体有刚体脚本才能受到力的作用
RigidBody组件信息
碰撞器
碰撞器表示物体的体积(形状) 刚体会利用体积进行碰撞计算 模拟真实的碰撞效果,产生力的作用
3D碰撞器种类
- 盒状
- 球状
- 胶囊
- 网格
- 轮胎
- 地形
共同参数
常用碰撞器
异形物体使用多种碰撞器组合
刚体对象的子对象碰撞器信息 参与碰撞检测
给父对象加上刚体, 子对象也会参与碰撞
不常用碰撞器
物理材质
创建物理材质
Project + -> Physic Material
物理材质参数
碰撞检测函数
碰撞和触发响应函数 属于 特殊的生命周期函数(在物理帧更新和逻辑帧更新之间) 也是通过反射调用
物理碰撞检测响应函数
//碰撞触发接触时会 自动执行这个函数
private void OnCollisionEnter(Collision collision)
{
//Collision类型的参数 包含了 碰到自己的对象的相关信息
//关键参数
//1.碰撞到的对象碰撞器的信息
//collision.collider
//2.碰撞对象的依附对象(GameObject)
//collision.gameObject
//3.碰撞对象的依附对象的位置信息
//collision.transform
//4.触碰点数相关
//collision.contactCount
//接触点 具体的坐标
ContactPoint[] pos = collision.contacts;
//只要得到了 碰撞到的对象的 任意一个信息 就可以得到它的所有信息
print(this.name + "被" + collision.gameObject.name + "撞到了");
}
//碰撞结束分离时 会自动执行的函数
private void OnCollisionExit(Collision collision)
{
print(this.name + "被" + collision.gameObject.name + "结束碰撞了");
}
//两个物体相互接触摩擦时 会不停的调用该函数
private void OnCollisionStay(Collision collision)
{
print(this.name + "一直在和" + collision.gameObject.name + "接触");
}
触发器检测响应函数
//触发开始的函数 当第一次接触时 会自动调用
private void OnTriggerEnter(Collider other)
{
print(this.name + "被" + other.gameObject.name + "触发了");
}
//触发结束的函数 当水乳相融的状态结束时 会自动调用
private void OnTriggerExit(Collider other)
{
print(this.name + "和" + other.gameObject.name + "结束水乳相融的状态了");
}
//当两个对象 水乳相融时 会不停调用
private void OnTriggerStay(Collider other)
{
print(this.name + "和" + other.gameObject.name + "正在水乳相融");
}
要明确什么时候会响应函数
1. 只要挂载的对象 能和别的物体产生碰撞或触发 那么对应的6个函数就能被响应
2. 6个函数一般根据需求来选择书写
3. 如果是一个异形物体,刚体在父对象上, 如果想通过子对象上挂脚本检测碰撞是不行的,必须挂载在这个刚体父对象上
4. 要明确 物理碰撞和触发器响应的区别
碰撞和触发器函数都可以写成虚函数 在子类去重写逻辑
碰撞和触发器函数不需要我们主动调用,所以基本是private或protected
一般会把想要重写的 碰撞和触发函数 写成保护类型的 没有必要写成public 都是通过Unity通过反射帮助我们自动调用的
刚体加力
刚体自带添加力的方法
给刚体加力的目标就是让其具有一个速度 朝向某一个方向移动
Rigidbody rigidBody;
...
//1.首先应该获取刚体组件
rigidBody = this.GetComponent<Rigidbody>();
//2.添加力
//相对世界坐标
//世界坐标 z轴正方向加了一个力
//加力后 对象是否停止移动 是由阻力决定而读(Start中)
//若阻力为0 那么不会停止移动
//若想要一直动 放在Update中一直推
rigidBody.AddForce(Vector3.forward * 10);
//若想要在世界坐标系中 让对象 相对于自己面朝向动
rigidBody.AddForce(this.transform.forward * 10);
//相对本地坐标系
rigidBody.AddRelativeForce(Vector3.forward * 10);
//3.添加扭矩力 让其旋转
//相对世界坐标
rigidBody.AddTorque(Vector3.up * 10);
//相对本地坐标系
rigidBody.AddRelativeTorque(Vector3.up * 10);
//4.直接改变速度
//这个速度方向相对于世界坐标系
rigidBody.velocity = Vector3.forward * 5;
//5.模拟爆炸效果
//只对调用这个方法的对象起作用
//模拟爆炸的力 一定是 所有希望产生爆炸效果影响的对象
//都需要得到他们的刚体 来执行这个方法 才能都有效果
//力 中心 半径
rigidBody.AddExplosionForce(10, Vector3.zero, 10);
力的几种模式
//第二个参数 力的模式 主要的作用 就是 计算方式不同
//由于4种计算方式不同 最终移动速度不同
rigidBody.AddForce(Vector3.forward * 10, ForceMode.Acceleration);
//动量定理
//Ft = mv
// v = Ft/m
//1.Acceleration
//给物体增加一个持续的速度, 忽略其质量
//v = Ft/m
//F:(0,0,10)
//t:0.02s 物体帧时间
//m:默认为1
//v = 10 * 0.02 / 1 = 0.2m/s
//每物理帧移动 0.2m/s * 0.02s = 0.004m
//2.Force
//给物体添加一个持续的力, 与物体的质量有关
//m:2kg
//v = 10 * 0.02 / 2 = 0.1m/s
//每物理帧移动 0.1m/s * 0.02s = 0.002m
//3.Impulse
//给物体添加一个瞬间的力 ,与物体质量有关 忽略时间 默认为1
//t:1s
//v = 10 * 1 / 2 = 5m/s
//每物理帧移动 5m/s * 0.02s = 0.1m
//4.VelocityChange
//给物体添加一个瞬时速度 忽略质量 忽略时间
//v = 10 * 1 / 1 = 10m/s
//每物理帧移动 10m/s * 0.02s = 0.2m
力场脚本
Constant Force 脚本
控制持续的力和旋转
刚体的休眠
Unity为了节约性能,会在某些情况刚体休眠
//获取刚体是否处于休眠状态
if(rigidBody.IsSleeping())
{
//若是 则唤醒
rigidBody.WakeUp();
}
音效系统
音频文件导入
音频源和音频监听脚本
添加音频源脚本 Audio Source
音频监听脚本AudioListener
有AudioListener才能听到声音,场景中只用一个该脚本(主视角摄像机)
代码控制音频源
代码控制播放停止
AudioSource audioSource;
...
audioSource = this.GetComponent<AudioSourcce>();
...
if(Input.GetKeyDown(KeyCode.P))
{
//播放
audioSouce.Play();
//延迟播放 5s
audioSouce.PlayDelayed(5);
}
if(Input.GetKeyDown(KeyCode.S))
{
//停止
audioSouce.Stop();
}
if(Input.GetKeyDown(KeyCode.Space))
{
//暂停
audioSouce.Pause();
}
if(Input.GetKeyDown(KeyCode.X))
{
//停止暂停,和暂停后play效果一样
audioSouce.UnPause();
}
如何检测音效播放完毕
//Update中不断检测
if(audioSource.isPlaying)
{
print("播放中");
}
else
{
print("播放结束");
}
如何动态控制音效播放
1.直接在要播放的音效的对象上挂载脚本 控制播放
2.实例化挂载了音效源脚本的对象
创建一个挂载音效脚本的预设体 playOnWake
其实用的比较少,因为还要控制销毁对象
public GameObject obj;
Instantiate(obj);
3.用一个AudioSource来控制播放不同的音效
AudioSource audioSource;
publc AudioClip clip;
...
AudioSource aus = this.gameObject.AddComponent<AudioSource>();
aus.clip = clip;
aus.Play();
//潜在知识点
//一个GameObject可以挂载多个 音效源脚本AudioSource
//使用时要注意 如果要挂载多个 一定要管理他们 控制播放 停止
//不然没办法获取谁是谁
麦克风输入相关
获取设备麦克风信息
string [] strs = Microphone.devices;
for(int i = 0; i < strs.Length; i++)
{
print(strs[i]);
}
开始录制
private AudioClip clip;
//参数一 设备名 传空使用默认设备
//参数二 超过录制长度后 是否从头录制
//参数三 录制时长 s
//参数四 采样率
if(Input.GetKeyDown(KeyCode.Space))
{
clip = Microphone.Start(null, false, 10, 44100);
}
// 返回 AudioClip
结束录制
if(Input.GetKeyUp(KeyCode.Space))
{
Microphone.End(null);
//第一次获取 没有才添加
AudioSource s = this.GetComponent<AudioSource>();
if( s == null )
s = this.gameObject.AddComponent<AudioSource>();
s.clip = clip;
s.Play();
}
获取音频数据用于存储或传输
//规则 用于存储数组数据长度 用 声道数 * 剪辑长度
float[] f = new float[clip.channels * clip.samples];
clip.GetData(f,0); //第二参数为偏移位置
//输出采样率
print(f.Length);
完结😊