unity基础知识补充

339 阅读13分钟

1.视图

默认是透视视图perspective,也可以切换正交视图ISO,它可以忽略近大远小视觉差的效果,可以让我们更好的调节物体的对齐。正交视图切换透视视图可以按住shift+点击小方块就能回到透视视图了,如下:

image.png

2、fbx模型

一个FBX模型文件中,一般包含:

  1. Mesh网格,定义物体的形状
  2. Material材质,定义表面的光学特性(一个fbx模型可以对应多个材质,也就是在模型上建立对应的materials材质数组)后缀名为.mat
  3. Texture贴图,定义表面的像素颜色(贴图文件的路径是约定好的,与fbx相同路径,或者同级Textures目录也可以找到对应的贴图)

注意:材质替换操作,有的fbx模型是内部定义好了材质,我们一开始是无法修改它的默认材质的,这时候我们可以点击fbx模型,在materials中选择use embeded materials,这时候在fbx模型同级目录下回创建一个materials文件夹,里面就是一开始的材质球了,这时候可以进行替换或者修改当前材质球,点击apply应用即可。

3.meta文件

每一个文件和文件夹都有一个meta文件,meta就是描述的意思,里面记录的是对应资源的一些相关信息

4.父子物体

子物体相对于父物体而言,子物体的坐标是相对坐标

5.世界(Global)和本地(Local)

image.png

Position和localposition的区别: localPosition是相对于自身的坐标,而position要分情况,如果当前物体有父物体,则当前物体的position计算的是当前物体的坐标和父物体的坐标进行计算加法,如果当前物体没有父物体,则输出的就是本地的坐标

public class LocalPostionAndPosition : MonoBehaviour
{
    void Start()
    {
        print("本地坐标" + this.transform.localPosition.ToString("F2"));
        print("相对于父物体的坐标" + this.transform.position.ToString("F2"));
    }
}

transform.Translate()可以实现物体的移动,前三个参数是一个float类型的参数,第四个参数是设置当前物体是世界坐标移动还是当前物体的自身坐标移动。

float distance = 2*Time.deltaTime;
this.transform.Translate(0,0,distance,Space.World);//相对于世界坐标移动
this.transform.Translate(0,0,distance,Space.Self);//相对于自身坐标移动

this.transform.lookAt(物体)方法可以使当前物体看向目标物体注意的是,如果目标物体和本身物体不处在同一个平面的话,这时候lookAt会让当前物体起飞~

6.Pivot和Center

Pivot是轴心点模式,unity默认是Pivot轴心点模式,Center是当前物体的几何中心点模式,具体开发选择要看开发需求来决定,大部分使用的是Pivot模式。 注意:当同时选择两个物体的时候,Pivot轴心点模式是以当前两个物体分别的轴心点进行选择,而Center模式是unity计算两者之间的中心点,有可能中心点是不在物体上的,所以这也是两者很大的区别。

image.png 参考文档:blog.csdn.net/alwaysyxl/a…

7.组件

Mesh Filter网格过滤器组件,就是用来加载网格数据的,(来自fbx,里面包含点线面数据) Mesh Renderer网格渲染器,是用来渲染物体的。 transform组件,如果当前物体存在父物体,当前物体的position来说就是相对坐标,如果当前物体不存在父物体,则当前物体的position就是世界坐标的概念。

8.帧更新

Time.time:游戏时间,也就是unity运行的时候开始计时,单位以秒,第一次输出的是0,并且每次时间间隔是不一样的。 Time.deltaTime:距离上次更新的时间差

注意:所有游戏引擎都不能固定的去更新帧率的,游戏引擎是一个程序,一个程序运行在你的系统之上,它不是独占的去运行的,没有办法独占式的去占用操作系统。Unity会尽快的更新 Unity不支持固定帧率,但是可以设定一个近似帧率

Application.targetFrameRate = 60;//尽量以每秒60帧去更新游戏

9.物体的旋转

unity三种旋转方式localEulerAnglesrotationRotate的区别

  1. 使用localEulerAngles进行旋转的时候,我们要使用transform.localEulerAngles = new Vector3(x, y,z); 其中,new Vector(x,y,z)为游戏物体最终旋转到的目标角度,x.y,z的值分别都是以0为基准,假设游戏物体的初始角度为(x1,y1,z1),则游戏物体从(x1,y1,z1)旋转到(x,y,z),同时也以自身坐标系为参考,而不是世界坐标系。 2. transform.rotation 在开发中,直接操作四元素进行旋转往往是不太方便的,往往通过代码来改变旋转一般不使用这个方法来直接改变旋转。
//将欧拉旋转角转换为四元素旋转角
Quaternion rotation= Quaternion.Euler(new Vector3(x, y,z));
Transform.rotation=rotation;

new Vector(x,y,z)为游戏物体最终旋转到的目标角度,x.y,z的值分别都是以0为基准,假设游戏物体的初始角度为(x1,y1,z1),则游戏物体从(x1,y1,z1)旋转到(x,y,z),,同时也以自身坐标系为参考,而不是世界坐标系。

3.transform.Rotate(参数) transform.Rotate(x,y,z):以自身坐标系为参考,而不是世界坐标系,分别以x度y度z度绕X轴、Y轴、Z轴匀速旋转

transform.Rotate(轴,Space.Self):以自身坐标系为参考
Transform.Rotate(轴,Space.World):以世界坐标系为参考

4.物体的移动

在实际项目开发中,我们会发现transform。Translate()方法对于碰撞检测不严谨,如果玩家移动速度过快,可能还会忽略碰撞检测,直接飞出去,如果我们采用Rigidbody刚体的velocity方法来实现玩家移动,无论速度多大都不会飞出去,所有有的人会说,如果你需要使用物体系统的话,也就是添加物理碰撞组件的话,是不建议使用translate方法的,这是要特别注意的点。

image.png

5.addForce和velocity的区别

Rigidbody.velocity

瞬间给物体一个恒定的速度,将物体提升至该速度

Rigidbody.addForce

瞬间给物体一个规定好的力

我的理解就是,当你想要做一个2D的跳跃游戏,在这个游戏里我希望我按下跳跃键的时候,游戏物体的跳跃高度是恒定的。如果使用addForce方法去实现跳跃的话,如果连续按下跳跃键,它的速度是会叠加去产生的,而使用velocity方法去实现的话,无论连续按下多少次跳跃键,它的速度都是恒定的。综上所述,物体的质量不变,所以addforce相当于是给物体一个持续不变的加速度,而velocity就是赋予物体一个持续不变的速度

10.脚本的运行

1.unity脚本如何运行

unity在运行的时候,首先是分析这个场景,因为它所有的起点都是来自这个场景,场景中会有很多个节点,每个节点是游戏物体或者游戏对象。首先会根据这个层级树,把树里面的每一个节点都创建一遍,也就是new对象。节点下可以挂载很多个组件,接下来会把每一个组件都实例化一遍,实例完成后,把实例挂载到这个节点的下面。最后看用户有没有自定义的组件(脚本组件),存在的话同样也是实例化组件,将组件挂载到游戏对象或者游戏物体上。之后就开始调用脚本的事件函数,首先会调用start方法进行初始化,之后调用帧更新update方法,这些由我们的unity框架自动来调用,unity本质上也是一个纯粹的面向对象的框架,对象的创建都是由框架来接管的。

2.生命周期函数

Awake第一阶段初始化,start第二阶段初始化,awake先于start调用,并且awake总是调用,不管组件有没有被禁用,start只执行一次,第一次启动的时候调用。同理,脚本禁用的状态下,start跟update是不会执行的。

生命周期函数图:

image.png

3.脚本执行顺序

在unity中,脚本之间是没有明确的执行顺序的,所有脚本的执行优先级都是0,但是你可以手动的设置某一个脚本的优先级,当然这种在开发中是不怎么采取的。

image.png

补充知识:

update与lateupdate都是每帧调用。但执行的顺序不一样。

比如说,有两个脚本。一个脚本里有update函数。另外一个脚本里有update与lateupdate函数。lateupdate函数要等两个脚本的update函数执行完(无论有多少个update函数,都要等update函数执行完)才能再执行lateupdate。

Lateupdate一般放相机处理

update放画面控制逻辑

update在渲染每一帧的时候调用。但是fixupdate一般是在固定的时间频率调用。这个时间不一定是每一帧的时间。fixupdate调用不受帧率的影响

4.脚本参数赋值

参数的最终赋值结果如下:

在面板中参数赋值 << awake方法赋值 << start方法赋值 << update方法

参数类型:分为值类型和引用类型

1.值类型

  • 值类型:如int,float,double,bool,这些值类型本质上都是结构体类型
  • 值类型(struct):如Vector3,Color
  • 引用类型:(class):如GameObject,Transform,MeshRenderer,string本质上是class类型,但属于引用类型

2.引用类型

  • 节点,GameObject
  • 组件,如Transform、MeshRenderer、AudioSource
  • 资源,如Material、Texture、AudioClip
  • 数组类型

11.消息调用

SendMessage的内部执行(发射机制)

  1. 找到target节点下的所有组件
  2. 在组件下寻找methodName这个函数,若存在,则调用它,若不存在,则继续查找,若最终无法匹配,则报错。

在实际项目开发中,我们采取这种方式来调用,可以使用单例模式或者观察者模式来实现

12.物体的获取

1. 按名称/路径获取物体(不推荐),路径最好指定全路径

  1. 全局查找参数名称游戏物体;
  2. 不对禁用(隐藏)物体进行查找;
  3. 若有同名物体时根据层级关系进行查找。(查找顺序是:“自身”(挂载脚本的物体) --> 和自身同层级上面物体 --> 和自身同层级下面物体 --> 自身子物体 --> 自身父物体)
    GameObject.Find();

缺点:执行效率低,并且不能自适应变化,名字改变后会找不到

2.引用获取,在检查器引用目标物体

    public GameObject wingNode

优点:名字改变了,引用的物体自动也会改过名字过来

3.Tag标签查找(单个)

    方法优点:不需要手动拖拽物体进行识别;当代码运行的时候,如果找到对于物体即可自行加载识别该物体。比名字识别更加方便。
缺点:也只能单个物体识别(当多个物体都设置为同一个标签时候,只会识别程序运行后第一个获取到的物体)

  //单独通过物体的标签查找
  public  GameObject player;
  player=GameObject.FindWithTag("xxxx");//填写自己为物体设置的标签

4.## 通过Tag标签查找(多个)

    方法优点:不需要手动拖拽物体进行识别;当代码运行的时候,如果找到对于物体即可自行加载识别该物体。支持多个物体的标签查找,该方法可以识别出当前标签下的所有物体,并可以保存到指定数组中。
缺点:也只能单个物体识别(当多个物体都设置为同一个标签时候,只会识别程序运行后第一个获取到的物体)

     //查找所有此类标签的物体
  public GameObject[] player;
  player=GameObject.FindGameObjectWithTag("xxxx"); 

5.根据类型来查找

  • 查找不到禁用物体,使用时需确认要查找的物体是启用(显示)状态
  • 查找场景中不存在类型时会返回null,不会报错;
    GameObject.FindObjectOfType<类型>(); :根据类型(组件/自定义脚本)查找并返回这个类

13.获取物体的子级

1.foreach循环遍历父物体

    foreach(Transform child in transform)
    {
        print(child.name);
    }

2.Getchild(索引值)

    Transform child1 = this.transform.Getchild(0);
    print(chilld1.name);

3.transform.Find(),按照名称来查找子项(如果要找多级,需要写全路径来进行查找)

  • 只能找其子物体,不能找其同级或更高层级物体
  • 找子物体时不考虑是否被禁用(隐藏)
  • 找多层子物体时需写全路径(否则即使存在也找不到)
    Transform aa = transform.Find("aa");
    print(aa.name);

综上所述:如果对于隐藏的物体,需要通过,transform.Find()才能查找到。GameObject.Find()、FindObjectWithTag()、FindObjectWithType()等三种是找不到隐藏物体的。

14.物体的操作

1.设置父物体

    this.transform.setParent(设置为父物体的目标物体);

设置为一级节点

    this.transform.setParent(null);//设置为null表示设置为一级节点

2.显示隐藏(GameObject.SetActive(bool值))

    this.gameobject.SetActive(false);
    this.gameobject.activeSelf()   //判断物体是否是激活的状态,返回bool值

3.GameObject.activeSelf,.activeInHierarchy,.SetActive的区别和关联

1.activeInHierarchy表示物体在层次中是否是active的。也就是说要使这个值为true,这个物体及其所有父物体(及祖先物体)的activeself状态都为true

2.activeSelf表示物体本身的active状态,对应于其在inspector中的checkbox是否被勾选

一个物体要在场景中是可见的(不是隐藏的),那么不仅仅其本身的activeSelf要为true,其所有父物体(及祖先物体)的activeself状态都要为true。

总结:

/*activeSelf(read only只读):物体本身的active状态,对应于其在inspector中的checkbox是否被勾选,激活自己
activeInHierarchy(read only只读):物体在层次中是否是active的。也就是说要使这个值为true,这个物体及其所有父物体(及祖先物体)的activeself状态都为true。
activeInHierarchy状态代表物体在场景中的实际的active状态。实际上代表的是物体及其所有祖先物体的activeSelf状态。而activeSelf对应于其在inspector中的checkbox是否被勾选

activeSelf状态代表物体自身的activeSelf状态,所以当物体本身activeSelf为true,而其所有祖先物体的activeSelf状态不全为true时,这个物体的activeInHierarchy状态为false。

activeSelf==物体自身
activeInHierarchy==物体自身及其所有祖先物体==物体在场景中实际上是否激活

至于SetActive,改变的是物体自身的activeSelf状态,所以,对一个物体SetActive时,其在场景中可能不会被激活,因为其祖先物体可能存在未被激活的。
SetActiveRecursively,改变物体自身及其所有子物体的activeSelf状态??,相当于对物体自身及其所有子物体调用SetActive

15.音频组件

1.音频组件AudioSource,在学习中遇到了两个不熟悉播放音频的方法,也就是

(1) Play()  //播放背景音乐(只能播放在同一实际内播放一种音乐),想要播放其他音效,只能切换音频源
(2) PlayOnShot()  //播放多种音效,是为了解决播放多种和多次音效的问题而生的,当前音效在播的话,另一个音效要播进来,多个音效可以同时进行播放

16.定时与线程

注意:start()、update()、以及定时调用,是在同一个线程,也就是unity是单线程,不存在互斥、并发、线程问题。同时我们也可以获取当前的线程号来验证我们的观点:

    using System.Threading;
    int threadId = Thread.CurrentThread.ManagedThreadLd;

每次调用,其实就是unity内部增加一个调度进行执行,并且多个相同或者不同的调度是相互独立的,互不影响。可以采用isInvoking()方法来判断当前函数是否在调度队列中。

17.向量

1.获取向量长度

    Vector3 v = new Vector3(3,0,4);
    float len = v.magnitude;

2.向量标准化normalized

缩放一个向量,使其长度为1

    Vector3 v = new Vector3(3,0,4);
    float len = v.normalized;

3.向量的距离计算

可以通过Vector3.Distance()来计算出两个向量之间的距离,本质上还是调用magnitude方法来计算的。注意的是使用这些方法计算向量距离差的时候,要观察当前的物体的轴心点在哪,轴心点在物体的中心底部是最好的,如果轴心点在中间的话,会导致有距离误差。