转 Unity知识点0001(Yanlz+协程+List+MeshRender+对象池+链条关节+PlayerPrefs+脚本生命周期+LOD+)

6 篇文章 0 订阅

https://blog.csdn.net/VRunSoftYanlz/article/details/80302012
Unity知识点0001(Yanlz+协程+List+MeshRender+对象池+链条关节+PlayerPrefs+脚本生命周期+LOD+)
Unity知识点》
##《Unity知识点》发布说明:

++++这是立钻哥哥对Unity知识点的一个梳理和拓展,打造最权威的参考。

++++这里我们碰到了一个问题,那就是从什么地方入手比较合适,作为这个问题,通常立钻哥哥就顺其自然了,所以,我们这边就没有特别的顺序和章节归类。

##Unity知识点目录:

#知识点0001:什么是协同程序?

#知识点0002:ArrayList和List的区别?

#知识点0003:MeshRender中material和sharedmaterial的区别?

#知识点0004:对象池(Object Pool)技术。

#知识点0005:链条关节(Hinge Joint)

#知识点0006:PlayerPrefs

#知识点0007:Unity3d脚本生命周期

#知识点0008:LOD技术

#知识点0001:什么是协同程序?
#知识点0001:什么是协同程序?
#知识点0001:什么是协同程序?

++++public Coroutine StartCoroutine(IEnumerator routine);

++++public extern void StopCoroutine(string methodName);

++++IEnumerator:

namespace System.Collections{

public interface IEnumerator{

    object Current{    get;    }    //Properties

    bool MoveNext();    //Methods

    void Reset();

}

}

++++协程称为协同程序,即在主线程运行的同时开启另一段逻辑,来协助当前程序的执行,StartCoroutine是开启协程,StopCoroutine是关闭协程,协程的返回值是IEnumerator类型,而在协程内部必须有yield return **。

++++IEnumerator用来记录执行到yield return的位置,每次执行协程时均是从当前位置向后执行,而不是从协程的最开始位置执行,除非只有一个yield return。

++++在一个主程序中开启多个协程。(开启协程就是开启一个线程,可以用来控制运动、序列以及对象的行为。)

++++关于协程:方法的返回值必须为IEnumerator。(协程不是多线程。)(协程中可以yield return各种值,其中包括开启另一个协程。)(yield return 0,null,n的效果一样,不是等待多少帧。)

++++什么是协程:Unity的协程系统是基于C#的一个简单而强大的接口。(协程就是可以把一个方法拆分成多次执行的一种接口。)

++++协程简单示例:

IEnumerator ShowTime(){

Debug.Log(“立钻哥哥Print: First Frame”);    //第一帧执行

yield return 0;    //等待下一帧

Debug.Log(“立钻哥哥Print: Second Frame”);    //第二帧执行

yield return 0;    //等待下一帧

Debug.Log(“立钻哥哥Print: Third Frame”);    //第三帧执行

}

++++如何开启协程:

StartCoroutine(ShowTime()); //通过传入方法开启协程

StartCoroutine(“ShowTime”); //通过传入字符串类型的方式名称开启协程

++++如何停止协程:

StartCoroutine(“ShowTime”);

StopCoroutine(“ShowTime”); //停止协程

++++注意:StopCoroutine只能停止以字符串方式开启的协程。

++++Yield:当我们“yield”一个方法时,相当于:“现在停止这个方法,然后在下一帧中从这里继续开始!”(用0或者null来yield的意思是告诉协程等待下一帧,直到继续执行为止。)

++++利用协程模拟Update功能:

//在Update调用后,每帧调用一次

IEnumerator MyUpdate(){

//死循环

while(true){

    Debug.Log(“立钻哥哥Print: MyUpdate”);

    //方法在这个地方挂起,等下一帧来了,方法从这个位置继续往下执行。

    yield return 0;

}

}

++++yield之后还可以加一些其他有意思的东西:

–yield return new WaitForSecond(2f);

–yield return StartCoroutine(otherIEnumerator());

–…

++++利用协程制作定时器:

//时间间隔

float timeInterval = 2;

IEnumerator MyTimer(){

while(true){

    yield return new WaitForSeconds(timeInterval);

    Debug.Log(“立钻哥哥Print: 延迟2秒”);

}

}

++++协程注意事项:

–注意1:在程序中调用StopCoroutine()方法只能终止以字符串形式启动(开始)的协程。

–注意2:多个协程可以同时运行,它们会根据各自的启动顺序来更新。

–注意3:协程可以嵌套任意多层。

–注意4:协程不是多线程(尽管它们看上去是这样的),它们运行在同一线程中,跟普通的脚本一样。

–注意5:IEnumerator类型的方法不能带ref或者out类型的参数,但可以带被传递的引用。

++++协程,线程的区别:线程拥有自己独立的栈和共享的堆(共享堆,不共享栈),线程由操作系统调度。(协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。)(协程避免了无意义的调度,由此可以提高性能,但程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力。)

++++协程优缺点:

–优点1:跨平台。

–优点2:跨体系架构。

–优点3:无需线程上下文切换的开销。

–优点4:无需原子操作锁定及同步的开销。

–优点5:方便切换控制流,简化编程模型。

–优点6:高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题,所以很适合用于高并发处理。

–缺点1:无法利用多核资源:协程的本质是个单线程,它不能同时将单个CPU的多个核用上,协程需要和进程配合才能运行在多CPU上。(当然我们日常所编写的绝大部分应用都没有这个必要,除非是CPU密集型应用。)

–缺点2:进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序:这一点和事件驱动一样,可以使用异步IO操作来解决。

++++Unity协程执行原理:unity中协程执行过程中,通过yield return XXX,将程序挂起,去执行接下来的内容,注意协程不是线程,在为遇到yield return XXX语句之前,协程的方法和一般的方法是相同的,也就是程序在执行到yield return XXX语句之后,接着才会执行的是StartCoroutine()方法之后的程序,走的还是单线程模式,仅仅是将yield return XXX语句之后的内容暂时挂起,等到特定的时间才执行。那么挂起的程序什么时候才执行,这就要看MonoBehavior的生命周期了:

++Unity3D的协程和C#线程之间的区别是什么?

++++多线程程序同时运行多个线程,而在任一指定时刻只有一个协程在运行,并且这个正在运行的协同程序只在必要时才被挂起。(除主线程之外的线程无法访问Unity3D的对象、组件、方法。)

++++Unity3d没有多线程的概念,不过Unity也给我们提供了StartCoroutine(协同程序)和LoadLevelAsync(异步加载关卡)后台加载场景的方法。(LoadLevelAsync则允许我们在后台加载新资源和场景,再利用前台loading条或动画提示玩家游戏未卡死,同时后台协同处理加载的事宜asynchronous.synchronous同步。(加载的进度条。))

++++StartCoroutine为什么叫协同程序呢?所谓协同,就是当我们在StartCoroutine的函数体里处理一段代码时,利用yield语句等待执行结果,这期间不影响主程序的继续执行,可以协同工作。

++Unity3D是否支持写成多线程程序?如果支持的话需要注意什么?

++++Unity支持多线程,如果同时处理很多事情或者与Unity的对象互动小可以用thread,否则使用coroutine。

++++注意1:虽然支持多线程,但是仅能从主线程中访问Unity3D的组件,对象和Unity3D系统调用,所以如果使用的话需要把组件中的数值传到开启的新线程中。

++++注意2:C#中有lock这个关键字,以确保只有一个线程可以在特定时间内访问特定的对象。

++Unity协程原理与线程的区别?

++++进程拥有自己的独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。

++++线程拥有自己独立的栈和共享堆。(共享堆,不共享栈。)(线程亦由操作系统调度。)

++++协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。

++++一个应用程序一般对应一个进程,一个进程一般有一个主线程,还有若干个辅助线程,线程之间是平行运行的,在线程里面可以开启协程,让程序在特定的时间内运行。

++++协程和线程的区别是:协程避免了无意义的调度,由此可以提高性能,但程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力。

++++Unity协程执行原理:Unity中协程执行过程中,通过yield return XXX,将程序挂起,去执行接下来的内容。

++Unity协程使用指南

++++协程是程序组件来生成非抢占式多任务子函数,生成的子函数允许在程序里挂起和唤醒操作。

++++通常协程可以很方便实现延时操作,以及异步加载操作。

++++使用场景举例1:延时操作

void Start(){

StartCoroutine(MyIEWait());

}

IEnumerator MyIEWait(){

Debug.Log(“立钻哥哥Print: start time: ” + Time.time);

yield return new WaitForSeconds(1);



Debug.Log(“Second time: ” + Time.time);

yield return new WaitForSeconds(2);



Debug.Log(“Third time:” + Time.time);

}

++++使用场景举例2:异步加载资源

void Start(){

System.Action<string> myCallBack = delegate(string text){

    Debug.Log(text);

};



StartCoroutine(MyIELoadRes(myCallBack));

}

IEnumerator MyIELoadRes(System.Action callBack){

WWW myWww = new WWW(“http://www.VRunSoft.com”);

yield return myWww;



if(string.IsNullOrEmpty(myWww.error)){

    callBack(myWww.text);

    Debug.Log(“立钻哥哥Print: load success!”);

}else{

    Debug.Log(“立钻哥哥Print: load failed!”);

}

}

#知识点0002:ArrayList和List的区别?
#知识点0002:ArrayList和List的区别?
#知识点0002:ArrayList和List的区别?

++++ArrayList:

namespace System.Collections{

public class ArrayList : IList, ICollections, IEnumerable, ICloneable{

    ....

    public ArrayList(int capacity);

    public ArrayList();

    internal ArrayList(bool trash);

    public ArrayList(ICollection c);

    ....

}

}

++++List:

namespace System.Collections.Generic{

public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable{

    ....

    public List();

    public List(int capacity);

    public List(IEnumerable<T> collection);

    ....

}

}

++++ArrayList存在不安全类型的(ArrayList会把所有插入其中的数据都当做Object来处理)装箱拆箱操作。(List是泛型,可以指定特定的类型,避免过多的装箱拆箱操作,减少对内存的消耗。)

++++ArrayList是非泛型列表,存储数据时把所有的数据都当成object类型存储,存在装箱问题,取出来使用的时候存在拆箱问题,装箱拆箱会使性能变差,而且存在数据安全问题,但是优点在于可以让值类型和引用类型相互转换。(List是泛型列表,在使用的时候才去定义数类型,泛型避免了拆箱装箱的问题,存入读取速度较快,类型也更安全。)

++++数组是一种高效的但是不太方便的数据存储方式,因为固定长度无法修改。(为了充分利用内存,就有了动态数组(ArrayList)的概念。)

++++C#中动态数组的实现就是集合接口IList。(ArrayList和List都继承了接口IList)

++++ArrayList对类型没有要求,是因为ArrayList中存储的类型都是object类型,而其他类型与object类型进行转换的过程就会产生拆箱装箱。(拆箱装箱是一笔不小的开销。)

++++List泛型在声明时就已经限制了存储内容的数据类型,所以不存在跟object类型的转换也就没有了装箱和拆箱的操作,并且是类型安全的。(平时使用时,如果只是为了使用动态数组,就不要考虑ArrayList了,推荐使用List)

++++List类表示可通过索引访问的对象的强类型列表,提供用于对列表进行搜索、排序和操作的方法。

++ArrayList动态数组:

++++1、动态地增加和减少元素。

++++2、实现了ICollection和IList和IEnumerable接口。

++++3、灵活地设置数组的大小。

++++4、不安全的集合数组。

++++5、其元素为值类型时,效率不高(装箱和拆箱耗性能)。

++++ArrayList常用方法与描述:

–Add():将对象添加到ArrayList的结尾处。

–Insert():将元素插入ArrayList的指定索引处。

–Remove():从ArrayList中移除特定对象的第一个匹配项。

–RemoveAt():移除ArrayList的指定索引处的元素。

–Reverse():将整个ArrayList当中元素的顺序反转。

–Contains():确定某元素是否在ArrayList中。

–Clear():从ArrayList中移除所有元素。

++List泛型动态数组:

++++1、List类是ArrayList类的泛型等效类。

++++2、同样继承了IList接口,IEnumerator接口和ICollection。

++++3、与ArrayList不同的是,声明集合时需要声明集合内部的数据类型,即T的类型。

++++4、安全的集合类型。

++++5、在处理值类型时处理速度比ArrayList快的多。

++++List常用方法与描述:

–Add():将对象添加到List的结尾处。

–Insert():将元素插入List的指定索引处。

–Remove():从List中移除指定对象的第一个匹配项。

–RemoveAt():移除List指定索引处的元素。

–Reverse():将整个List当中元素的顺序反转。

–Contains():确定某元素是否在List中。

–Clear():从List中移除所有元素。

–IndexOf(T):搜索指定对象,并返回整个List中第一个匹配项的从0开始的索引。

++++List:

namespace System.Collections.Generic{

public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable{

    ....

    public int Capacity{    get;    set;    }    //Properties

    public int Count{    get;    }

    ....

    public T this[]{    get;    set;    }    //Indexer

    ....

    public List();    //Constructors

    public List(int capacity);

    public List(IEnumerable<T> collection);

    ....

    public void Add(T item);    //Methods

    public void AddRange(IEnumerable<T> collection);

    public void Clear();

    public bool Contains(T item);

    public void CopyTo(T[] array);

    public void CopyTo(int index, T[] array, int arrayIndex, int count);

    public void CopyTo(T[] array, int arrayIndex);

    public T Find(Predicate<T> match);

    public int FindIndex(int startIndex, int count, Predicate<T> match);

    public int FindIndex(int startIndex, Predicate<T> match);

    public int FindIndex(Predicate<T> match);

    public List<T> GetRange(int index, int count);

    public int IndexOf(T item, int index, int count);

    public int IndexOf(T item);

    public int IndexOf(T item, int index);

    public void Insert(int index, object item);

    public void InsertRange(int index, IEnumerable<T> collection);

    public int LastIndexOf(T item);

    public int LastIndexOf(T item, int index);

    public int LastIndexOf(T item, int index, int count);

    public int Remove(T item);

    public int RemoveAll(Predicate<T> match);

    public void RemoveAt(int index);

    public void RemoveRange(int index, int count);

    public void Reverse();

    public void Reverse(int index, int count);

    public void Sort(int index, int count, IComparer<T> comparer);

    public void Sort(IComparer<T> comparer);

    public void Sort();

    public T[] ToArray();

    ....

}

}

++++Predicate:

namespace System{

public delegate bool Predicate<T>(T obj);

}

++List拓展:下列代码在运行中会发生什么问题?如何避免?

List ls = new List(new int[]{ 1, 2, 3, 4, 5});

foreach(int item in ls){

Console.WriteLine(item * item);

ls.Remove(item);

}

++++产生运行时错误,在ls.Remove(item)这行,因为foreach是只读的。不能一边遍历一边修改。

++List(链表)和数组的区别在哪里?

++++从逻辑结构来看:

–数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费;数组可以根据下标字节存取。

–链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其他数据项,非常繁琐),链表必须根据next指针找到下一个元素。

++++从内存存储来看:

–(静态)数组从栈中分配空间,对于程序员方便快速,但是自由度小。

–链表从堆中分配空间,自由度大但是申请管理比较麻烦。

++++如果需要快速访问数据,很少或不插入和删除元素,就应该用数组。(如果需要经常插入和删除元素就需要用链表数据结构了。)

++常见的集合

++++常见的集合包括:非泛型集合和泛型集合。

++++非泛型集合(各种常用的System.Collection命名空间)的类:

–动态数组(ArrayList):它代表了可被单独索引的对象的有序结合。它基本上可以替代一个数组。但是,与数组不同的是,我们可以使用索引在指定的位置添加和移除项目,动态数组会自动重新调整它的大小。它也允许在列表中进行动态内存分配、增加、搜索、排序各项。

–堆栈(Stack):它代表了一个后进先出的对象集合。当我们需要对各项进行后进先出的访问时,则使用堆栈。(当我们在列表中添加一项,称为推入元素,当我们从列表中移除一项,称为弹出元素。)

–队列(Queue):它代表了一个先进先出的对象集合。当我们需要对各项进行先进先出的访问时,则使用队列。(当我们在列表中添加一项,称为入队,当我们从列表中移除一项时,称为出队。)

–哈希表(HashTable):它使用键来访问集合中的元素。当我们使用键访问元素时,则使用哈希表,而且我们可以识别一个有用的键值。(哈希表中的每一项都有一个键/值对。键用于访问集合中的项目。)

++++泛型集合(各种常用的System.Collection.Generic命名空间)的类:

–Dictionary<TKey, Tvalue>:同哈希表一样,标示根据键进行的键值对的集合,不同的是对键值进行了类型限定。

–List:同ArrayList一样,标示通过索引访问对象的列表,不同的是对存储的对象进行了类型限定。

–Stack:同Stack一样,不同的是对存储的对象进行了类型限定。

–Queue:同Queue一样,不同的是对存储的对象进行了类型限定。

++++常见集合和列表实现接口:

–IEnumerator:这个接口定义了方法GetEnumerator(),返回一个实现了IEnumerator接口的枚举。如果将foreach语句用于集合,就需要实现该接口。

–ICollection:ICollection接口由泛型集合类实现。使用这个接口可以获取集合中的元素个数(count),把集合复制到数组中(CopyTo()),还可以添加和删除元素。

–IList:IList接口用于可通过位置访问其中的元素列表,这个接口定义了一个索引器,可以在集合指定位置插入或删除某些项。(IList接口派生自ICollection接口。)

–IDictionary<TKey,TValue>:IDictionary<TKey,TValue>接口由包含键和值的泛型集合类实现。使用这个接口可以访问所有的键和值。使用键类型的索引器可以访问某些项,还可以添加或删除某些项。

++List简单实例:请用C#实现一个函数,取出整型数组中的重复元素,相同的元素只保留一个。

++++代码参考:

int[] array = new int[]{ 6, 7, 8, 1, 8, 9, 5, 2, 4, 4, 7 };

List myList = new List();

for(int i = 0; i < array.Lenght; i++){

if(myList.Contains(array[i])){

    continue;

}

myList.Add(array[i]);

}

++List简单实例:在一个int列表中插入1~10,10个整数,然后删除其中的9,5,2,7四个数。

++++代码参考:

List myList = new List(new int[10]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10});

List deleList = new List(new int[4]{9, 5, 2, 7});

myList.RemoveAll(delegate(int num){

return deleList.Contains(num);

});

++ArrayList的用法

++++ArrayList就是传说中的动态数组,就是Array的复杂版本,它提供了:动态的增加和减少元素;实现了ICollection和IList接口;灵活的设置数组的大小。

++++ArrayList例子:

ArrayList myArrList = new ArrayList();

for(int i = 0; i < 10; i++){

myArrList.Add(i);    //给数组增加10个Int元素

}

myArrList.RemoveAt(5); //立钻哥哥:将第6个元素移除

for(int i = 0; i < 3; i++){

myArrList.Add(i + 20);    //再增加3个元素

}

//返回ArrayList包含的数组

Int32[] values = (Int32[])myArrList.ToArray(typeof(Int32));

++用List做动态数组

++++有时我们需要一个动态数组,例如希望string[]能够动态添加数据。(这时可以使用List集合,List集合可以动态添加元素,最后使用List.toArray()方法转成string[])

List myToolNameList = new List();

foreach(MyTool item in EquToolsList){

//根据条件筛选元素

if(item.type == “立钻哥哥”){

    myToolNameList.Add(item.Name.ToString());

}

}

string[] toolNames = myToolNameList.ToArray();

#知识点0003:MeshRender中material和sharedmaterial的区别?
#知识点0003:MeshRender中material和sharedmaterial的区别?
#知识点0003:MeshRender中material和sharedmaterial的区别?

++++修改sharedMeterial将改变所有物体使用这个材质的外观,并且也改变存储在工程里的材质设置。不推荐修改由sharedMaterial返回的材质。(如果我们想修改渲染器的材质,使用material替代。)

++++通过GetComponent.material.color来改变颜色(材质的参数)。

++++通过GetComponent.sharedmaterial.color来改变颜色(改变材质)。

++++MeshRenderer(网格渲染器)从MeshFiler(网格过滤器)获得几何形状,并根据Mesh进行渲染,而渲染所需要的贴图信息就来自于Material。

++++MeshRenderer的Material类型的变量有两个:material和sharedMaterial。

++++sharedMaterial是公用Material,所有用到这个材质的MeshRenderer都会引用这个Material。(改变sharedMaterial的属性也会改变mat文件。)

++++material是独立的Material,改变material的属性不会影响到其他对象,也不会影响mat文件。

++++当只修改材质的参数的时候,使用material属性,确保其他对象不会受影响。(当需要修改材质的时候,直接赋值给sharedMaterial,否则赋值给material会产生内存泄露。)

++++当使用Renderer.material的时候,每次调用都会生成一个新的material都内存中去,这在销毁物体的时候需要我们手动去销毁该material,否则会一直存在内存中。(可以在场景替换的时候使用Resources.UnloadUnusedAssets去统一释放内存。)

++++当使用Render.sharedMaterial的时候并不会生成新的material,而是直接在原material上修改,并且修改后的设置就会被保存到项目工程中。(一般不推荐使用这个去修改,当某个材质球只被一个gameObject使用的时候可以使用这个去修改,并且最好在修改之前把原属性设置保存,当使用完毕后立即恢复原设置,防止下次加载后的gameObject上还会残留之前的设置信息。)

++++如果是玩家主角这一类gameObject身上需要修改材质的属性或者shared属性比较多的时候,可以第一次使用material,这样可以动态的生成一个material的实例,然后再使用sharedMaterial,动态地修改这个新生成的material,而且不会创建新的material。

++MeshRender中material和Shader的区别?

++++MeshRender是模型渲染的组件,由此组件物体才能显示出来。

++++Material是材质球,实际就是shader的实例,并进行赋值,贴图、纹理、颜色等。

++++Shader是着色器,实际上是一段程序,还可以用来实现一些仅靠贴图不容易实现的效果,如玻璃。

++++Shader大致分为:1、表面着色器;2、顶点和片元着色器;3、固定功能着色器。

++Render的作用?描述MeshRender和SkinnedMeshRender的关系与不同?

++++Render是渲染器,渲染器可以使物体显示在屏幕上。

++++MeshRender是网格渲染,SkinnedMeshRender是蒙皮网格渲染器。

++++Mesh就是指模型的网格(同名组件是用于调整网格属性的),MeshFilter一般是用于获得模型网格的组件,而MeshRender是用于把网格渲染出来的组件。

++Unity里的Mesh属性

++++Mesh是Unity内的一个组件,称为网格组件。

++++Mesh网格:是指模型的网格,建模就是建网格。(细看Mesh,可以知道Mesh的主要属性内容包括顶点坐标,法线,纹理坐标,三角形绘制序列等其他有用的属性和功能。)(建网格,就是画三角形;画三角形就是定位三个点。)

++++Mesh Filter网格过滤器:内包含一个Mesh组件,可以根据MeshFilter获得模型网格的组件,也可以为MeshFliter设置Mesh内容。

++++Mesh Render网格渲染器:是用于把网格渲染出来的组件。(MeshFilter的作用就是把Mesh扔给MeshRender将模型或者说是几何体绘制显示出来。)

++++Mesh、Mesh Filter、Mesh Render之间的关系大概就是:Unity中的对象就是GameObject,每个GameObject都可以有一个MeshFilter组件(也可以没有),该组件又有Mesh属性(这个一定有),而该属性又有顶点坐标,法线等属性。(而如果GameObject里有MeshFilter,则必须要Mesh Render才能将此网格渲染出来,不然是看不见该网格的。)

++++Mesh的属性:顶点坐标(vertex)、法线(normal)、纹理坐标(uv)、三角形序列(triangle)

–【顶点坐标(Vertex)】:顶点坐标数组存放Mesh的每个顶点的空间坐标,假设某mesh有n个顶点,则vertex的size为n。

–【法线(normal)】:法线数组存放mesh每个顶点的法线,大小与顶点坐标对应,normal[i]对应顶点vertex[i]的法线。

–【纹理坐标(uv)】:它定义了图片上每个点的位置的信息,这些点与3D模型是相互联系的,以决定表面纹理贴图的位置,UV就是将图像上每一个点精确对应到模型物体的表面。(Ui[i]对应vertex[i])

–【三角形序列(triangle)】:每个mesh都由若干个三角形组成,而三角形的三个点就是顶点坐标里的点,三角形的数组的size=三角形个数*3。

++Unity3D中MeshRenderer的使用

++++任何一个模型都是由许多网格面组成的,而面又是由许多三角形组成的。

++++创建网格面的核心就是为其添加2个组件:Mesh Renderer(网格渲染器)和Mesh Filter(网格过滤器)。

++++手动添加:选择一个游戏对象,然后【Component】=>【Mesh】=>【Mesh Filter】/【Mesh Renderer】

++++通过脚本实现:

gameObject.AddComponent(); //添加MeshFilter

gameObject.AddComponent(); //添加MeshRenderer

mesh = GetComponent().mesh; //获得Mesh

++++mesh示例:

gameObject.AddComponent(); //添加MeshFilter

gameObject.AddComponent(); //添加MeshRenderer

list = new List(); //new一个链表

mesh = GetComponent().mesh; //获得Mesh

GetComponent().material.color = Color.green; //修改Mesh的颜色

//选择Mesh中的Shader

GetComponent().material.shader = Shader.Find(“Transparent/Diffuse”);

mesh.Clear(); //清空所有点,用于初始化

++SkinnedMeshRenderer

++++相比MeshRenderer组件,SkinnedMeshRenderer多了bones组件。(MeshRenderer不支持骨骼动画,而SkinnedMeshRenderer支持骨骼动画。)

++++Mesh:指定mesh。

++++RootBone:指定哪个是根节点。

++Unity换装系统

++++每一个模型都有一个SkinnedMeshRenderer组件,改变该组件的材质,我们就可以实现对特定部位的换装。

++++所谓的换装,表面上就是换掉mesh,但如果只是简单地替换mesh,就会出错。

++++SkinnedMeshRenderer示例:

public class ChangeFoot : MonoBehaviour{

private SkinnedMeshRenderer oldSmr = null;

private SkinnedMeshRenderer newSmr = null;

....



void ChangeFeet(){

    //加载替换对象的资源文件

    newObj = Resource.Load(“Prefab/newFoot”);

    newInstance = Instantiate(newObj) as GameObject;



    oldSmr = gameObject.GetComponentInChildren<SkinnedMeshRenderer>();

    newSmr = newInstance.GetComponentInChildren<SkinnedMeshRenderer>();



    ....



    //替换Mesh数据

    oldSmr.bones = bones.ToArray();

    oldSmr.sharedMesh = newSmr.sharedMesh;

    oldSmr.sharedMaterial = newSmr.sharedMaterial;



    //删除无用的对象

    GameObject.DestroyImmediate(newInstance);

    GameObject.DestroyImmediate(newSmr);

}

}

++++SkinnedMeshRenderer换装示例:

public class ChangeSkin : MonoBehaviour{

public Texture2D[] TextureEyes;    //眼睛贴图

public Texture2D[] TextureFace1;    //面部贴图-前

public Texture2D[] TextureFace2;    //面部贴图-后



//与贴图对应的SkinnedMeshRenderer

SkinnedMeshRenderer MeshEyes;

SkinnedMeshRenderer MeshFace1;

SkinnedMeshRenderer MeshFace2;



void Start(){

    //获取SkinnedMeshRenderer

    MeshEyes = transform.Find(“eyes”).GetComponent<SkinnedMeshRenderer>();

    MeshFace1 = transform.Find(“face-1”).GetComponent<SkinnedMeshRenderer>();

    MeshFace2 = transform.Find(“face-2”).GetComponent<SkinnedMeshRenderer>();

}



void OnGUI(){

    if(GUILayout.Button(“显示外装1”, GUILayout.Height(30))){

        SetSkin(MeshEyes, TextureEyes[0]);

        SetSkin(MeshFace1, TextureFace1[0]);

        SetSkin(MeshFace2, TextureFace2[0]);

    }



    if(GUILayout.Button(“显示外装2”, GUILayout.Height(30))){

        SetSkin(MeshEyes, TextureEyes[1]);

        SetSkin(MeshFace1, TextureFace1[1]);

        SetSkin(MeshFace2, TextureFace2[1])

    }

}



private void SetSkin(SkinnedMeshRenderer mRenderer, Texture2D mTexture){

    mRenderer.material.mainTexture = mTexture;

}

}

–提供两套外装,把脚本拖放到模型上,然后编辑贴图数组:

#知识点0004:对象池(Object Pool)技术。
#知识点0004:对象池(Object Pool)技术。
#知识点0004:对象池技术。

++++有一个池子里都是对象。

++++对象池就是存放需要被反复调用资源的一个空间,当一个对象会大量生产的时候如果每次都销毁创建会浪费时间,通过对象池把暂时不用的对象放到一个池中(也就是一个集合),当下次要重新生成这个对象的时候先去池中查找一下是否有可用的对象,如果有的话就直接拿出来使用,不需要再创建,如果池中没有可用的对象,才需要重新创建,利用空间换时间来达到游戏的高速运行效果。

++++在FPS游戏中常被大量复制的对象包括:子弹、敌人、粒子等。

++++当游戏中需要频繁创建一个物体对象时,需要做一个pool,游戏开始时预先实例化足够的数量,然后用的时候取,不用的时候收回。

++++对象池的优点:复用池中的对象,没有分配内存和创建堆中对象的开销,没有释放内存和销毁堆中对象的开销,进而减少垃圾收集器的负担,避免内存抖动;不必重复初始化对象状态,对于比较耗时的构造和析构来说非常合适。

++++对象池的缺点:由于池中的对象的数量有限,势必成为一个可伸缩性瓶颈。(很难正确地设定对象池的大小,如果太小则起不到作用,如果过大,则占用内存资源高。)(设计和使用对象池容易出错,设计上需要注意状态同步这是个难点,使用上可能存在忘记归还。)

++Unity对象池理解与简单应用

++++对象池用于减少内存开销,其原理就是把可能用到的对象,先存放在一个池中,用的时候就调出来,不用就放回去。(而不是要用的时候创建,不用的时候销毁。)

++++子弹对象池类(class BulletsPool)示例:

//立钻哥哥:对象池简单应用:子弹对象池类

using System.Collections.Generic;

using UnityEngine;

public class BulletsPool : MonoBehaviour{

public static BulletsPool bulletsPoolInstance;    //子弹池单例

public GameObject bullectObj;    //子弹perfabs

public int pooledAmout = 5;    //子弹池初始大小

public bool lockPoolSize = false;    //是否锁定子弹池大小



private List<GameObject> pooledObjects;    //子弹池链表

private int currentIndex = 0;    //当前指向链表位置索引



void Awake(){

    bulletsPoolInstance = this;    //把本对象作为实例

}



void Start(){

    pooledObjects = new List<GameObject>();    //初始化链表

    for(int i = 0;  i < pooledAmout; ++i){

        GameObject obj = Instantiate(bulletObj);    //创建子弹对象

        obj.SetActive(false);    //设置子弹无效

        pooledObjects.Add(obj);    //把子弹添加到链表(对象池)

    }

}





//立钻哥哥:获取对象池中可以使用的子弹

public GameObject GetPooledObject(){

    //把对象池遍历一遍

    for(int i = 0;  i < pooledObjects.Count;  ++i){

        //这里简单优化了一下:每一次遍历都是从上一次被使用的子弹的下一个,而不是每次遍历从0开始:例如上一次获取了第4个子弹,currentIndex就为5,这里从索引5开始遍历,这是一种贪心算法

        int temI = (currentIndex + i) % pooledObjects.Count;



        //判断该子弹是否在场景中激活

        if(!pooledObjects[temI].activeInHierarchy){

            currentIndex = (temI + 1) % pooledObjects.Count;

            return pooledObjects[temI];    //找到没有被激活的子弹并返回

        }

    }



    //如果遍历完一遍子弹库发现没有可用的,就新创建一个

    if(!lockPoolSize){

        GameObject obj = Instantiate(bullectObj);

        pooledObjects.Add(obj);

        return obj;

    }



    //如果遍历完没有而且锁定了对象池大小,返回空

    return null;

}

}

++++自动发射子弹类(class AutoFire)代码参考:

//立钻哥哥:自动发射子弹类

using UnityEngine;

pubic class AutoFire : MonoBehaviour{

//public GameObject shotObj;    //传统创建子弹方法需要的子弹perfabs



public GameObject shotSpawn;    //子弹发射的初始化位置

public float fireRate = 0.2f;    //每次发射子弹时间间隔

private float nextFire;    //下一次发射子弹的时间



void Update(){

    //可以发射子弹

    if(Time.time > nextFire){

        nextFire = Time.time + fireRate;



        //传统创建子弹方法

        //Instantiate(shotObj, shotSpawn.transform.position, shotSpawn.transform.rotation);



        //获取对象池中的子弹

        GameObject bullet = BulletsPool.bulletsPoolInstance.GetPooledObject();

        if(bullect != null){

            bullect.SetActive(true);    //激活子弹并初始化子弹的位置

            bullect.transform.position = shotSpawn.transform.position;

        }

    }

}

}

++++子弹失效,回收子弹类(class DestroyByBoundary):

//立钻哥哥:子弹失效,回收子弹类

using UnityEngine;

public class DestroyByBoundary : MonoBehaviour{

//判断是否出界,这个类放在场景的一个长方体里

void OnTriggerExit(Collider other){

    //Destroy(other.gameObject);    //传统方法,直接删除子弹

    other.gameObject.SetActive(false);    //对象池方法,把子弹失效就好了

}

}

++简单易懂的对象池

++++立钻哥哥:对象池,就是一个池子里都是对象。

++++对象池ObjectPool

public static Dictionary<string, GameObject> dic = new Dictionary<string, GameObject>();

–创建一个Dictionary用来保存所有的对象(Dictionary只是一个用于存储的键值对)

++++1、运行过程中获取对象:

GameObject obj = dic[objectstring]; //立钻哥哥:获取存放的对象

++++2、将对象实例化出来并激活:

Instantiate(obj, position) as GameObject;

++++3、将实例化出来的对象从对象池中移除:

dic.RemoveAt(ObjectIndex);

++++4、如果对象在场景中被销毁,则会重新添加至对象池中.

++Unity对象池(单例对象池和泛型对象池)

++++立钻哥哥:对象池的意义是:将游戏中反复创建销毁的对象进行多次利用,从而避免大量对象的销毁与创建而造成CPU的负担。

++++对象池的缺点:占用了更多的内存。(在移动平台上针对游戏的优化基本偏向于牺牲空间换取时间。)

++++1、泛型非单例对象池(class YanlzObjPool)

using UnityEngine;

using System.Collections;

using System;

using System.Collections.Generic;

//立钻哥哥:泛型非单例池(代码简洁,通用性强)

public class YanlzObjPool where T : class{

private Action<T> mReset;    //重置对象的委托

private Func<T> nNew;    //创建新对象的委托

private Stack<T> stack;  //存放对象的池子,用List等动态数组也可以,推荐泛型数组



public YanlzObjPool(Func<T> mNew, Action<T> mReset = null){

    this.mNew = mNew;

    this.mReset = mReset;

    stack = new Stack<T>();

}



//立钻哥哥:从池子中获取对象的方法(思路:若池子的数量为0,则调用创建新对象委托创建一个对象返回,否则从池子中拿出一个对象并返回)

pubic T New(){

    if(stack.Count == 0){

        T t = mNew();

        return t;



    }else{

        T t = stack.Pop();

        if(mReset != null){

            mReset(t);

        }



        return t;

    }

}



//立钻哥哥:将销毁的对象存入池子

public void Store(T t){

    stack.Push(t);

}



//立钻哥哥:清空池子

public void Clear(){

    stack.Clear();

}

}

++++2、泛型对象池应用(YanlzObjPoolTest)

using UnityEngine;

using System.Collections;

//立钻哥哥:让我们来测试一下这个泛型对象池

public class MyObjPoolTest : MonoBehaviour{

public GameObject bullet;    //子弹的预设体

private YanlzObjPool<GameObject> pool;



void Start(){

    pool = new YanlzObjPool<GameObject>(NewBullet, Reset);

}



//立钻哥哥:实例化新子弹

private GameObject NewBullet(){

    GameObject go = Instantiate(bullet) as GameObject;    //实例化新子弹

    return go;

}



//立钻哥哥:重置对象的方法

private void Reset(GameObject go){

    go.transform.position = Vector3.zero;

    go.transform.rotation = Quaternion.identity;

    go.SetActive(true);    //从池子中取出后将物体设为可见

}



//立钻哥哥:销毁对象

private void Destroy(GameObject go){

    go.SetActive(false);    //放入池子前将物体设为不可见

    pool.Store(go);

}

}

++++3、单例对象池(YanlzSingletonPool)

using UnityEngine;

using System.Collections;

using System.Collections.Generic;

using System;

//立钻哥哥:单例对象池

public class YanlzSingletonPool{

#region 单例

private static YanlzSingletonPool instance = null;

private YanlzSingletonPool(){  }

public static YanlzSingletonPool GetInstance(){

    if(instance == null){

        instance = new YanlzSingletonPool();

    }

    return instance;

}

#endregion



private Dictionary<string, List<GameObject>> poolDic;    //存放池子的字典

private Action<GameObject> mReset;    //立钻哥哥:重置对象的委托

private Func<GameObject> mNew;    //立钻哥哥:创建新对象的委托



//立钻哥哥:从对应字符串池子中取出对象

public GameObject New(string str, Func<GameObject> mNew, Action<GameObject> mReset = null){

    //立钻哥哥:如果字典存在该字符串,取出该池

    if(poolDic.ContainsKey(str)){

        //如果池子里有对象则取出一个对象并返回

        if(poolDic[str].Count > 0){

            GameObject go = poolDic[str][0];

            poolDic[str].Remove(go);

            if(mReset != null){

                mRest(go);

            }

            return go;



         }else{

            //立钻哥哥:如果池子对象则返回一个新创建的对象

            return mNew();

        }

    }else{

        //立钻哥哥:如果字典不存在该字符串则新创建一个池子,并返回对象

        poolDic.Add(str, new List<GameObject>());

        return mNew();

    }

}



//立钻哥哥:销毁的对象存入池子

public void Store(string str, GameObject go){

    if(poolDic.ContainsKey(str)){

        poolDic[str].Add(go);

    }

}



//立钻哥哥:销毁对象池的对象

public void DestroyPool(stirng str){

    if(poolDic.ContainsKey(str)){

        poolDic.Remove(str);

    }

}

}

#知识点0005:链条关节(Hinge Joint)
#知识点0005:链条关节(Hinge Joint)
#知识点0005:链条关节(Hinge Joint)

++++链条关节(Hinge Joint):可以模拟两个物体间用一根链条连接在一起的情况,能保持两个物体在一个固定距离内部相互移动而不产生作用力,但是达到固定距离后就会产生拉力。

++++Joint属于Unity3D里面的一种物理组件(Component):是模拟物体与物体之间的一种连接关系。

++++说明1:使用“Break Force”可设置关节断裂的力,一旦力超过它,关节将会断裂。(断裂时,通过onjointbreakforce方法可监听相关事件。)

++Unity3d关节物体连接方式

++++立钻哥哥:Joint连接方式:Hinge Joint(链条连接)、Fixed Joint(固定连接)、spring Joint(弹簧连接)、Character Joint(角色关节连接)、Configurable Joint(可配置连接)等。

++++【Hinge Joint(链条连接)】:可以模拟两个物体用一根链条连接在一起的情况,能保持两个物体在一个固定距离内部相互移动而不产生作用力,但是达到固定距离后就会产生拉力。(两个物体间连根链条。)(立钻哥哥:将两个物体以链条的形式绑在一起,当力量大于链条的固定力矩时,两个物体就会产生相互的拉力,同时铰链关节是由两个刚体组成,约束它们像连在一个铰链上一样运动,适用于:门。)

++++【Fixed Joint(固定连接)】:模拟两个物体间存在一根杆子,固定了两个物体的相对位置和相对朝向。(立钻哥哥:将两个物体永远以相对的位置固定在一起,即使发生物理改变,它们之间的相对位置也将不变,同时固定关节基于另一个物体来限制一个物体的运动。效果类似于父子关系,但不通过层级变换,而通过物理实现的。适用场景:让两个没有父子关系的物体一起运动。)

++++【spring Joint(弹簧连接)】:模拟两个物体间有一根弹簧,大于或者小于固定位置的时候产生相对弹力,根据弹性系数距离偏移越大作用力越大。(立钻哥哥:将两个物体以弹簧的形式绑定在一起,挤压它们会得到向外的力,拉伸它们将得到向里的力,像被弹簧连接着一起运动。)

++++【Character Joint(角色关节连接)】:模拟人体骨头间的关系连接,就是两个物体能根据一个关键点自由的朝一个方向旋转,但固定在一个相对距离,而且可以设置关节的控制。(可以用在蒙皮骨骼模型上做活动关节,这样就可以做到很多游戏引擎里那种各种自由姿势的死法了。)(立钻哥哥:角色关节主要用于实现布娃娃效果。角色关节是扩展的球关节,可以用于限制关节在不同旋转轴下的旋转角度。)

++++【Configurable Joint(可配置连接)】:万能连接方式,通过配置非常多的参数和限制,我们可以做到能想到的任何物体与物体间的连接方式,包括上面所有的连接方式,当然配置起来是比较复杂的。(立钻哥哥:可以模拟任意关节的效果,同时可配置关节将PhysX引擎中所有与关节相关的属性都设置为可配置的,因此可以用此组件创造出与其他关节类型行为相关的关节。)

++++Unity Joint关节的简单示例代码:

//立钻哥哥:Joint关节的简单示例

using UnityEngine;

using System.Collections;

public class YanlzDemoJoint : MonoBehaviour{

Component jointComponent = null;    //立钻哥哥:关节组件

Rigidbody connectObjComponent = null;    //要连接物体的刚体组件

Rigidbody rigidbodyComponent = null;    //自身的刚体组件



void Start(){

    connectObjComponent = GameObject.FindWithTag(“ConnectObj”).rigidbody;

    rigidbodyComponent = gameObject.rigidbody;

    connectObjComponent.useGravity = false;

    rigidbodyComponent.useGravigy = false;

}



//立钻哥哥:GUI事件

void OnGUI(){

    if(GUILayout.Button(“添加链条关节”)){

        YanlzResetJoint();



        jointComponent = gameObject.AddComponent(“HingeJoint”);

        HingeJoint hjoint = (HingeJoint)jointComponent;

        hjoint.connectedBody = connectedObjComponent;

        connectedObjComponent.useGravity = true;

        rigidbodyComponent.useGravity = true;

    }



    if(GUILayout.Button(“添加固定关节”)){

        YanlzResetJoint();



        jointComponent = gameObject.AddComponent(“FixedJoint”);

        FixedJoint fjoint = (FixedJoint)jointComponent;

        connectedObjComponent.useGravity = true;

        rigidbodyComponent.useGravity = true;

        fjoint.connectedBody = connectedObjComponent;

    }



    if(GUILayout.Button(“添加弹簧关节”)){

        YanlzResetJoint();



        jointComponent = gameObject.AddComponent(“SpringJoint”);

        SpringJoint sjoint = (SpringJoint)jointComponent;

        connectedObjComponent.useGravity = true;

        rigidbodyComponent.useGravity = true;

        sjoint.connectedBody = connectedObjComponent;

    }



    if(GUILayout.Button(“添加角色关节”)){

        YanlzResetJoint();

        jointComponent = gameObject.AddComponent(“CharacterJoint”);

        CharacterJoint cjoint = (CharacterJoint)jointComponent;

        connectedObjComponent.useGravity = true;

        rigidbodyComponent.useGravaity = true;

        cjoint.connectedBody = connectedObjComponent;

    }



    if(GUILayout.Button(“添加可配置关节”)){

        YanlzResetJoint();

        jointComponent = gameObject.AddComponent(“ConfigurableJoint”);

        ConfigurableJoint cojoint = (ConfigurableJoint)jointComponent;

        connectedObjComponent.useGravity = true;

        rigidbodyComponent.usGravity = true;

        cojoint.connectedBody = connectedObjComponent;

    }

}



//立钻哥哥:重置关节

void YanlzResetJoint(){

    //立钻哥哥:销毁之前添加的关节组件。

    //亦可以:jointComponent.active = false.

    Destroy(jointComponent);



    this.transform.position = new Vector3(2.431f, 8.388f, -7.374f);    //位置复原

    connectedObjComponent.transform.position = new Vector3(0.604f, 7.388f, -7.374f);

    connectedObjComponent.useGravity = false;    //取消使用重力

    rigidbodyComponent.useGravity = false;

}

}

++Unity3D铰链关节的简单示例

++++立钻哥哥:在Unity物理引擎中,刚体关节组件可以把两个刚体连接起来。(例如:把刚体车轮与动力学汽车的刚体底盘连接起来以实现一起移动的目的。)

++++铰链关节Hinge Joint的使用:现实世界中铰链关节常见应用有门上的折页,通过折页可以把门固定在墙上或门框上,而门通过折页上的轴进行旋转。

++++选择组件Component的Physics的Hinge Joint来添加相应关节(会自动添加刚体属性,因为铰链关节需添加在一个刚体上。)

++++常用属性:Connected Body、Anchor、Motor、Limits、Break Force、Spring等:

–【Connected Body】:设置关节所连接的刚体,在选中其他刚体后,其他刚体将控制关节及底下的刚体的运动。(此刚体会连接在设置的刚体上)(如果此项不选择对象,则关节以及关节下刚体都将连接在世界物体上。)

–【Anchor】:锚点,设置关节在刚体中的位置。

–【Motor】:动力引擎,可添加力Force和速度Target Velocity。

–【Limits】:限制项。

–【Break Force】:最大力。

–【Break Torque】:最大扭矩。

–【Spring】:相当于在刚体中加弹簧。

++铰链关节(Hinge Joint)(Unity物理引擎)

++++铰链关节由两个刚体组成。(该关节会对刚体进行约束。使得它们好像被连接在一个铰链上那样运动。)(铰链关节由两个刚体组成,约束它们像连在一个铰链上一样运动,适合于门。)

++++铰链关节检视面板:

++++【Connected Body】:连接刚体(连接体)。(用于为关节指定要连接的刚体,若不指定则该关节将与世界相连。)(为刚体指定的关节连接物,如不设定,则与世界相连。)

++++【Anchor】:锚点。(刚体可以围绕锚点进行摆动,这里可以设置锚点的位置,该值应用于局部坐标系。)(主体摇摆围绕的锚点坐标,基于本地坐标系。)

++++【Axis】:轴(坐标轴)。(定义了刚体移动的方向,该值应用于局部坐标系。)(摆动方向的坐标,基于本地坐标系。)

++++【Use Spring】:使用弹簧(是否使用弹簧)。(勾选该项,则弹簧会使得刚体与其连接的主体形成一个特定的角度。)(弹簧使刚体相对它连接的主体达到一个特定的角度。)

--Spring:弹簧。(用于如激活Use Spring的弹簧属性。)

{

    Spring:弹簧力。(维持对象移动到一定位置的力。)

    Damper:阻尼。(值越大,对象移动越慢)

    Target Position:目标角度。(弹簧的目标角度。弹簧拉向这个角度,以角为单位。)

}

++++【Use Motor】:使用马达(是否使用马达)。(Motor使对象旋转。)

--Motor:马达。(用于如激活Use Motor的马达属性。)

{

    Target Velocity:目标速度。(对象设法达到的速度。)

    Force:作用力。(用于达到目标速度的力。)

    Free Spin:自由转动。(如果启用,Motor永远不会破坏旋转,只会加速。)

}

++++【Use Limit】:使用限制。(如果启用,铰链的角度将被限制在最大和最小之间。)

--Limit:限制。(用于如启用Use Limits的边界属性。)

{

    Min:最小值。(rotation能达到的最小角度。)

    Max:最大值。(rotation能达到的最大角度。)

    Min Bounce:最小弹跳。(物体碰触最小限制时的弹跳值。)

    Max Bounce:最大弹跳。(物体碰触最大限制时的弹跳值。)

}

++++【Break Force】:破坏力。(允许这个铰链破损的力。)

++++【Break Torque】:破坏扭矩。(允许这个铰链破损的扭矩。)

++++细节1:单独的铰链关节要连在游戏对象上。(铰链会绕着Anchor属性指定的点,沿着指定的Axis属性方向移动。)(不需要给关节的Connected Body属性分配游戏对象。只有希望关节的Transform依赖附加对象的Transform时,才需要分配游戏对象给Connected Body属性。)

++++细节2:门的铰链如何工作。(Axis在这种情况下是向上的,沿Y轴正向。)(Anchor在门和墙交点的某处。)(不需要指定墙给Connected Body,因为关节默认会和世界相连。)

++++细节3:狗门铰链。(狗门的Axis应该是侧向的,沿着X轴的正向。)(正门应该指定为Connected Body,这样狗门的铰链即依赖于正门的RigidBody。)

++++链条(Chains):多个铰链关节也可以串联来变成一条链条。(给链条的每一环添加一个关节,并附加下一环为其Connected Body。)

++++提示1:不需要指派Connected Body来使关节运转。

++++提示2:用Break Force来制作动态破坏的系统。(这确实很酷,可以让玩家用火箭发射器爆破或者用疾驰的汽车撞,来把门从铰链处弄碎。)

++++提示3:调整Spring、Motor和Limits属性可以改变关节的作用。

++铰链使用的一些问题:

++++问题1:添加铰链组件,最好铰链的anchor不要设置在collider内部,否则铰链的行为会不正常。(比如:在一个柜子(box collider)里的一个物体添加configurableJoint(anchor在柜子boxcollider里),只允许一个轴的limited,其他5个locked,在VR中用手柄去拉物体,铰链的约束会跳动。(诡异:如果去掉外部的box collider,则正常。))

#知识点0006:PlayerPrefs
#知识点0006:PlayerPrefs
#知识点0006:PlayerPrefs

++++PlayerPrefs:Unity3d提供了一个用于本地持久化保存与读取的类。(工作原理非常简单,以键值对的形式将数据保存在文件中,然后程序可以根据这个名称取出上次保存的数值。)

++++PlayerPrefs的存储机制是“Key-Value”。(可存储类型:int、float、string)

++++PlayerPrefs类支持3种数据类型的保存和读取:整型、浮点型、字符串型。

–整型:SetInt()、GetInt();

–浮点型:SetFloat()、GetFloat();

–字符串型:SetString()、GetString();

++++PlayerPrefs类提供方法:DeleteKey(string key)、DeleteAll()、HasKey(string key)等。

–PlayerPrefs.DeleteKey(string key):删除指定数据;

–PlayerPrefs.DeleteAll():删除全部键;

–PlayerPrefs.HasKey(string key):判断数据是否存在。

++++namespace UnityEngine{ public sealed class PlayerPrefs{ } }

//立钻哥哥:PlayePrefs类信息

using System;

namespace UnityEngine{

public sealed class PlayerPrefs{

    //Constructors

    public PlayerPrefs();



    //Static Methods

    public static extern void DeleteAll();    //删除所有的key(立钻哥哥:谨慎使用)

    public static extern void DeleteKey(string key);    //删除对应的key

    public static float GetFloat(string key);

    public static extern float GetFloat(string key, float defaultValue);  

    public static extern int GetInt(string key, int defaultValue);

    public static int GetInt(string key);    //返回key对应的值

    public static extern string GetString(string key, string defaultValue);

    public static string GetString(string key);

    public static extern bool HasKey(string key);    //判断是否存在key

    public static extern void Save();

    public static void SetFloat(string key, float value);    //设置由key确定的参数值

    public static void SetInt(string key, int value);    //设置由key确定的参数值

    public static void SetString(string key, string value);    

    private static extern bool TrySetFloat(string key, float value);

    private static extern bool TrySetInt(string key, int value);

    private static extern bool TrySetSetString(string key, string value);

}

}

++++PlayerPrefs实例:

void YanlzPlayerPrefsExample(){

PlayerPrefs.SetFloat(“Player Score”, 10.0F);

print(PlayerPrefs.GetFloat(“Player Score”));

}

++++PlayerPrefs保存数据:

--立钻哥哥:Unity3D中的数据持久化是以键值的形式存储的,可以看作是一个字典。

–PlayerPrefs.SetInt(“Age”, mAge);

–PlayerPrefs.SetFloat(“Grade”, mGrade);

–PlayerPrefs.SetString(“Name”, mName);

++++PlayerPrefs读取数据:

--立钻哥哥:Unity3D中的值是通过键名来读取的,当值不存在时,返回默认值。

–mAge = PlayerPrefs.GetInt(“Age”, 0);

–mGrade = PlayerPrefs.GetFloat(“Grade”, 0F);

–mName = PlayerPrefs.GetString(“Name”, “DefaultValue”);

#知识点0007:Unity3d脚本生命周期
#知识点0007:Unity3d脚本生命周期
#知识点0007:Unity3d脚本生命周期

++++Unity3d脚本常用生命周期:【Awake()】=>【OnEnable()】=>【Start()】=>【FixedUpdate()】=>【Update()】=>【LateUpdate()】=>【OnGUI()】=>【OnDisable()】=>【OnDestroy()】

++++Unity3d脚本完整生命周期:

=>【Editor编辑器:Reset()、】

=>【Initialization初始化:Awake()、OnEnable()、Start()、】

=>【Physics物理系统:FixedUpdate()、yield WaitForFixedUpdate()、Internal physics update()、OnTriggerXXX()、OnCollisionXXX()、】

=>【Input events输入事件:OnMouseXXX()、】

=>【Game logic游戏逻辑:Update()、yield null、yield WaitForSeconds()、yield WWW()、yield StartCoroutine()、Internal animation update()、LateUpdate()、】

=>【Scene rendering场景渲染:OnWillRenderObject()、OnPreCull()、OnBecameVisible()、OnBecameInvisible()、OnPreRender()、OnRenderObject()、OnPostRender()、OnRenderImage()、】

=>【Gizmo rendering:OnDrawGizmos()、】

=>【GUI rendering(GUI渲染):OnGUI()、】

=>【End of frame:yield WaitForEndOfFrame()、】

=>【Pausing:OnApplicationPause()、】

=>【Disable/enable(物体激活或禁用):OnDisable()、】

=>【Decommissioning:OnDestroy()、OnApplicationQuit()、】

++++【Editor:Reset():】:Reset是在用户点击检视面板的Reset按钮或者首次添加该组件时被调用。此函数只在编辑模式下被调用。

++++【Initialization:Awake():】:

++++【Initialization:OnEnable():】:

++++【Initialization:Start():】:Start仅在Update函数第一次被调用前调用。

++++【Physics:FixedUpdate():】:如果固定时间步长小于实际帧更新时间,那么每一帧物理周期将可能发生不止一次;

++++【Physics:yield WaitForFixedUpdate():】:

++++【Physics:Internal physics update():】:

++++【Physics:OnTriggerXXX():】:

++++【Physics:OnCollisionXXX():】:

++++【Input events:OnMouseXXX():】

++++【Game logic:Update():】:

++++【Game logic:yield null:】:

++++【Game logic:yield WaitForSeconds():】:如果一个协程之前已经yield了,但是现在由于恢复了,那么将执行剩下的部分。

++++【Game logic:yield WWW():】:

++++【Game logic:yield StartCoroutine():】:

++++【Game logic:Internal animation update():】:

++++【Game logic:LateUpdate():】:

++++【Scene rendering:OnWillRenderObject():】:如果对象可见,则为每个相机调用一次此函数。

++++【Scene rendering:OnPreCull():】:在相机剔除场景之前调用此函数。相机可见的对象取决于剔除。OnPreCull函数调用发生在剔除之前。

++++【Scene rendering:OnBecameVisible():】:在对象对于相机可见时调用此函数。

++++【Scene rendering:OnBecameInvisible():】:在对象对于相机不可见时调用此函数。

++++【Scene rendering:OnPreRender():】:在相机开始渲染场景之前调用此函数。

++++【Scene rendering:OnRenderObject():】:在完成所有常规场景渲染后调用此函数。此时,可使用GL类或Graphics.DrawMeshNow绘制自定义几何图形。

++++【Scene rendering:OnPostRender():】:在相机完成场景渲染后调用此函数。

++++【Scene rendering:OnRenderImage():】:(仅限专业版):在完成场景渲染后调用此函数,以便对屏幕图像进行后处理。

++++【Gizmo rendering:OnDrawGizmos():】:OnDrawGizmos只在编辑模式下被调用。(用于在场景视图中绘制小图示(Gizmos),以实现可视化目的。)

++++【GUI rendering:OnGUI():】:OnGUI在每一帧更新时调用多次。(在每帧上多次调用此函数,以响应GUI事件。程序首先将处理Layout和Repaint事件,然后再处理每个输入事件的Layout和keyboard/鼠标事件。)

++++【End of frame:yield WaitForEndOfFrame():】:

++++【Pausing:OnApplicationPause():】:OnApplicationPause在程序检测到暂停时,会在帧的结尾处被调用。

++++【Disable/enable:OnDisable():】:OnDisable在脚本失效时被调用,被销毁时也会被调用。如果再启用的话,OnEnable()会再一次被调用。

++++【Decommissioning:OnDestroy():】:

++++【Decommissioning:OnApplicationQuit():】:

++Unity3d脚本9个常用生命周期的回调方法

++++1、void Awake();:用于在游戏开始之前初始化变量或游戏状态。在脚本整个生命周期内它仅被调用一次。Awake()在所有对象被初始化之后调用,所以可以安全的与其他对象对话或用诸如GameObject.FindWithTag()这样的函数搜索它们。每个游戏物体上的Awake()以随机的顺序被调用。因此,应该用Awake()来设置脚本间的引用,并用Start()来传递信息,Awake()总是在Start()之前被调用。它不能用来执行协同程序。

++++2、void OnEnable();

++++3、void Start();:仅在Update函数第一次被调用前调用。Start()在behaviour的生命周期中只被调用一次。Start()和Awake()的不同是Start()只在脚本实例被启用时调用。可以按需求调整延迟初始化代码。Awake()总是在Start()之前执行。这允许协调初始化顺序。在所有脚本实例中,Start()函数总是在Awake()函数之后调用。

++++4、void Fixedupdate(); :物理引擎计算:固定帧更新,在Unity导航菜单栏中,点击【Edit】=>【Project Setting】=>【Time】菜单项后,右侧的Inspector视图将弹出时间管理器,其中“Fixed Timestep”选项用于设置FixedUpdate()的更新频率,更新频率默认为0.02s。

++++5、void Update(); :刷新:正常帧更新,用于更新逻辑。每一帧都执行,处理Rigidbody时,需要用FixedUpdate代替Update。(例如,给刚体加一个作用力,必须应用作用力在FixedUpdate里的固定帧,而不是Update中的帧。)(两者帧长不同)FixedUpdate(),每固定帧绘制时执行一次,和Update()不同的是Fixedupdate是渲染帧执行,如果渲染效率低的时候FixedUpdate调用次数就会跟着下降。FixedUpdate比较适合用于物理引擎的计算,因为是跟每帧渲染有关。Update就比较适合做控制。

++++6、void LateUpdate(); :摄像机跟随:在所有Update函数调用后被调用,和FixedUpdate一样都是每一帧都被调用,这可用于调整脚本执行顺序。(例如,当物体在Update里移动时,跟随物体的相机可以在LateUpdate里实现。)LateUpdate,在每帧Update执行完毕调用,他是在所有Update()结束后才调用,比较适合用于命令脚本的执行。(官网上的摄像机跟随,都是在所有Update()操作完才跟进摄像机,不然就有可能出现摄像机已经推进了,但视角里还没有角色的空帧出现。)

++++7、void OnGUI();:在渲染和处理GUI事件时调用。(比如,画一个button或label时常用)这意味着OnGUI()也是每帧执行一次。

++++8、void OnDisable();:当物体被销毁时OnDisable()将被调用,并且可用于任意清理代码。脚本被卸载时,OnDisable()将被调用,OnEnable()在脚本被载入后调用。(立钻哥哥:OnDisable不能用于协同程序。)

++++9、void OnDestroy();:当MonoBehaviour将被销毁时,这个函数被调用。OnDestroy()只会在预先已经被激活的游戏物体上被调用。(立钻哥哥:OnDestroy()不能用于协同程序。)

++Unity脚本常用生命周期

++++初始阶段:Awake()、OnEnable()、Start()、

++++物理阶段:FixedUpdate()、

++++游戏逻辑:Update()、LateUpdate()、

++++场景渲染:OnGUI()、OnBecameVisible()、OnBecameInvisible()、

++++结束阶段:OnDisable()、OnDestroy()、OnApplicationQuit()、

++++【初始阶段:Awake():唤醒】:当物体载入时立即调用1次,常用于在游戏开始前进行初始化,可以判断当满足某种条件执行此脚本 this.enable = true。

++++【初始阶段:OnEnable():当可用】:每当脚本对象启用时调用。

++++【初始阶段:Start():开始】:物体载入且脚本对象启用时被调用1次。常用于数据或游戏逻辑初始化,执行时机晚于Awake()。

++++【物理阶段:FixedUpdate():固定更新】:脚本启用后,固定时间被调用,适用于对游戏对象做物理操作,例如移动等。(设置更新频率:【Edit】=>【Project Setting】=>【Time】=>【Fixed Timestep】值,默认为0.02s)

++++【游戏逻辑:Update():更新】:脚本启用后,每次渲染场景时调用,频率与设备性能及渲染量有关。

++++【游戏逻辑:LateUpdate():延迟更新】:在Update函数被调用后执行,适用于跟随逻辑。

++++【场景渲染:OnGUI():渲染】:渲染和处理GUI事件时调用。

++++【场景渲染:OnBecameVisible():当可见】:当Mesh Renderer在任何相机上可见时调用。

++++【场景渲染:OnBecameInVisible():当不可见】:当Mesh Renderer在任何相机上不可见时调用。

++++【结束阶段:OnDisable():当不可见】:对象变为不可用和附属游戏对象非激活状态时此函数被调用。

++++【结束阶段:OnDestroy():当销毁】:当脚本销毁或附属的游戏对象被销毁时调用。

++++【结束阶段:OnApplicationQuit():当程序结束】:应用程序退出时被调用。

++Unity脚本事件函数

++++Unity脚本函数涉及:编辑器、初始化、物理、输入、游戏逻辑、渲染、应用程序、协程、其他等。

++++编辑器相关函数:Reset()、OnValidate()、

–【Reset()】:当脚本附加到GameObject上或脚本的右键菜单项里选择了“Reset”命令的时候调用此函数。

–【OnValidate()】:脚本被加载或值被改变,在inspector中时调用。

++++初始化函数:Awake()、OnEnable()、Start()、

–【Awake()】:此函数在脚本附加到GameObject对象上且此对象是被激活的时候调用。并且是在所有Start()函数调用之前调用。(立钻哥哥:就算挂载的脚本没有被启用此函数也会被调用。)

–【OnEnable()】:脚本对象被激活的时候。

–【Start()】:在所有的Update()函数调用之前调用。

++++物理相关函数:FixedUpdate()、OnTriggerEnter()、OnTriggerEnter2D()、OnTriggerExit()、OnTriggerExit2D()、OnTriggerStay()、OnTriggerStay2D()、OnCollisionEnter()、OnCollisionEnter2D()、OnCollisionExit()、OnCollisionExit2D()、OnCollisionStay()、OnCollisionStay2D()、

++++输入相关函数:OnMouseDown()、OnMouseDrag()、OnMouseEnter()、OnMouseExit()、OnMouseOver()、OnMouseUp()、OnMouseUpAsButton()、

++++游戏逻辑相关函数:Update()、LateUpdate()、

–【Update()】:游戏逻辑的更新函数。

–【LateUpdate()】:所有Update函数调用后调用的更新函数。

++++渲染相关函数:OnPreCull()、OnBecameVisible()、OnBecameInvisible()、OnWillRenderObject()、OnPreRender()、OnRenderObject()、OnPostRender()、OnRenderImage()、OnGUI()、OnDrawGizmos()、OnDrawGizmosSelected()、

–【OnPreCull()】:在执行剔除之前执行的函数。

–【OnBecameVisible()】:此对象从隐藏到显示时调用。

–【OnBecameInVisible()】:此对象从显示到隐藏时调用。

–【OnPreRender()】:渲染场景之前。

–【OnRenderObject()】:所有常规的渲染结束后调用。

–【OnRenderImage()】:场景渲染完后调用。

–【OnGUI()】:执行UI的渲染,一帧可能调用几次。

–【OnDrawGizmos()】:执行场景视图中对象的标示图标的绘制。

–【OnDrawGizmosSelected()】:执行场景视图中选中场景的标示图标的绘制。

++++应用程序相关函数:OnApplicationFocus()、OnApplicationPause()、OnApplicationQuit()、

–【OnApplicationFocus()】:应用程序成为当前的应用时调用。

–【OnApplicationPause()】:当应用程序切入后台暂停时调用。

–【OnApplicationQuit()】:应用程序退出时调用。

++++协程相关:yield()、yield WaitForSeconds()、yield WaitForFixedUpdate()、yied WWW()、yield StartCoroutine()、

–【yield()】:延缓到下一帧执行。

–【yield WaitForSeconds()】:这一帧执行完后,等待指定的延迟到。

–【yield WaitForFixedUpdate()】:所有的固定更新完成后。

–【yield WWW()】:www下载完成。

–【yield StartCoroutine()】:等待一个自定义的函数完成。

++++其他函数:OnDisable()、OnDestroy()、OnLevelWasLoaded()、

–【OnDisable()】:脚本对象被禁用时调用。

–【OnDestroy()】:脚本对象被销毁的时候调用。

–【OnLevelWasLoaded()】:一个新的场景加载时调用。

++物理更新一般放在哪个系统函数里?

++++FixedUpdate(),每固定帧绘制时执行一次,和Update()不同的是FixedUpdate()是渲染帧执行,如果渲染效率低下的时候,FixedUpdate()调用次数就会跟着下降。

++++FixedUpdate()比较适用于物理引擎的计算,因为是跟每帧渲染有关。(Update()就比较适合做控制。)

#知识点0008:LOD技术
#知识点0008:LOD技术

#立钻哥哥的Unity学习空间:http://blog.csdn.net/VRunSoftYanlz/

++立钻哥哥推荐的拓展学习链接(Link_Url):

++++立钻哥哥Unity 学习空间: http://blog.csdn.net/VRunSoftYanlz/

++++设计模式简单整理:https://blog.csdn.net/vrunsoftyanlz/article/details/79839641

++++U3D小项目参考:https://blog.csdn.net/vrunsoftyanlz/article/details/80141811

++++UML类图:https://blog.csdn.net/vrunsoftyanlz/article/details/80289461

++++Unity知识点0001:https://blog.csdn.net/vrunsoftyanlz/article/details/80302012

++++U3D_Shader编程(第一篇:快速入门篇):https://blog.csdn.net/vrunsoftyanlz/article/details/80372071

++++U3D_Shader编程(第二篇:基础夯实篇):https://blog.csdn.net/vrunsoftyanlz/article/details/80372628

++++Unity引擎基础:https://blog.csdn.net/vrunsoftyanlz/article/details/78881685

++++Unity面向组件开发:https://blog.csdn.net/vrunsoftyanlz/article/details/78881752

++++Unity物理系统:https://blog.csdn.net/vrunsoftyanlz/article/details/78881879

++++Unity2D平台开发:https://blog.csdn.net/vrunsoftyanlz/article/details/78882034

++++UGUI基础:https://blog.csdn.net/vrunsoftyanlz/article/details/78884693

++++UGUI进阶:https://blog.csdn.net/vrunsoftyanlz/article/details/78884882

++++UGUI综合:https://blog.csdn.net/vrunsoftyanlz/article/details/78885013

++++Unity动画系统基础:https://blog.csdn.net/vrunsoftyanlz/article/details/78886068

++++Unity动画系统进阶:https://blog.csdn.net/vrunsoftyanlz/article/details/78886198

++++Navigation导航系统:https://blog.csdn.net/vrunsoftyanlz/article/details/78886281

++++Unity特效渲染:https://blog.csdn.net/vrunsoftyanlz/article/details/78886403

++++Unity数据存储:https://blog.csdn.net/vrunsoftyanlz/article/details/79251273

++++Unity中Sqlite数据库:https://blog.csdn.net/vrunsoftyanlz/article/details/79254162

++++WWW类和协程:https://blog.csdn.net/vrunsoftyanlz/article/details/79254559

++++Unity网络:https://blog.csdn.net/vrunsoftyanlz/article/details/79254902

++++C#事件:https://blog.csdn.net/vrunsoftyanlz/article/details/78631267

++++C#委托:https://blog.csdn.net/vrunsoftyanlz/article/details/78631183

++++C#集合:https://blog.csdn.net/vrunsoftyanlz/article/details/78631175

++++C#泛型:https://blog.csdn.net/vrunsoftyanlz/article/details/78631141

++++C#接口:https://blog.csdn.net/vrunsoftyanlz/article/details/78631122

++++C#静态类:https://blog.csdn.net/vrunsoftyanlz/article/details/78630979

++++C#中System.String类:https://blog.csdn.net/vrunsoftyanlz/article/details/78630945

++++C#数据类型:https://blog.csdn.net/vrunsoftyanlz/article/details/78630913

++++Unity3D默认的快捷键:https://blog.csdn.net/vrunsoftyanlz/article/details/78630838

++++游戏相关缩写:https://blog.csdn.net/vrunsoftyanlz/article/details/78630687

++++立钻哥哥Unity 学习空间: http://blog.csdn.net/VRunSoftYanlz/

–VRunSoft : lovezuanzuan–

作者:VRunSoftYanlz
来源:CSDN
原文:https://blog.csdn.net/VRunSoftYanlz/article/details/80302012
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值