协同程序(协同或者协程)

线程、进程

一个应用程序就是一个进程,笼统的说就是一个.exe程序

一个进程中至少一个线程叫主线程。

一个或多个线程组合成了进程。

协程

在unity中为了在主线程中模拟多线程而出现的辅助工具。

协程不是多线程,还是单线程,是在操作系统中的空间中模拟多线程。

启动协程:

StartCoroutine(返回IEnumerator类型返回值的方法名);//开启协程,

注意:返回IEnumerator类型返回值的方法名可以有参数,但是不能带有ref和out形式的参数

例如:

StartCoroutine(“Fuck”);
IEnumerator Fuck(){ 
yield return new WaitForSeconds(3); //每三秒执行一次本行之后的代码
Debug.log(“1231233”);
}

IEnumerator是一个迭代器类型的对象,迭代器是用来迭代遍历用的,例如foreach。

一次又一次的循环将其他元素取出来付给同一个元素就叫迭代。

yield return 是方法返回迭代器的方式,等同普通方法的return

 

开启一个协程(遇到yield return之前),从上到下执行,和普通方法一样

遇到yield return,程序挂起,返回协程开启的地方继续执行主线程,从此跟主线程没有任何关系。

yield return之后的代码属于协程,进行主线程的同时,迭代器会反复查看(每帧)yield return后面的条件是否满足,满足后会执行协程中yield return后的代码,此时代码跟主线程无关(例如协程内死循环,跟主线程无关,但协程也不能死循环,因为死循环会导致协程无法跳出,过多无法跳出会卡死电脑)。

协程如果需要死循环,可以在死循环中通过yield return null(等待一帧)的方式来避免卡死,这种方式相当于一帧只执行一次,例如update,对电脑负荷小,不会卡死。

例如:

IEnumerator Fuck(){ 
yield return new WaitForSeconds(3); //每三秒执行一次本行之后的代码
while(true){
Debug.log(“1231233”);
yield return null;
}
}

上述情况不会卡死是因为yield return null是等待一帧的意思,此时并不是跳出了死循环,而是每执行一次循环体,就等待一帧时间,这样会一直每帧输出一次,但不会卡死,不写的话,相当于每帧执行无限次,所以才会卡死。

yield return 协程的方式来开协程,等待的是协程全部执行完毕后才返回。

例如 yield return StartCoroutine(“You”)等待新协程you执行完毕后再继续。

本质上来说,死循环的协程就是一个单独的update

Yield return 可以等待多种:

yield return 时间/新协程/www(是unity的一个类)/秒

yield break;跳出协程,不能用return。还可以通过代码Stop Coroutine(“You”)停止协程,但停止协程会在协程这一帧执行完才会停止。yield break;马上跳出

 

停止协程

协程是和gameobject挂钩的(与协程所在的脚本无关),但可以通过脚本来开启协程,而不能通过游戏物体来开启协程(获取脚本组件,通过.的方式开启协程)

如果游戏物体失活或者销毁,协程都会终止,再激活也不会执行。

如果不能保证本游戏对象不会被销毁,那么这个协程要开启在一个不会被销毁的物体身上,而代码写在自己的逻辑中,使用方式就是上面所说的脚本组件.开启协程。

StopCoroutine()停止协程,如果协程使用字符串方式开启的,能用字符串方式停止,但如果用方法名加()的方式开启的,用字符串停不住,用方法名加()也停不住,只能用StopAll Coroutine ()的方式停止,但StopAllCoroutine ()只会停止当前类对象下开启的协程,不能停止其他物体的协程。

想要停止其他物体的协程,必须使用脚本. StopAllCoroutine ().

谁开启的协程,谁来停止,其他对象停止不了。

延迟函数

Invoke(方法名字符串,float 秒) 延迟多少秒执行函数。

CancelInvoke(参数方法名,可不写);取消延迟函数。

不写参数,取消本脚本内所有延迟函数,加参数,取消指定参数

延迟方法无论游戏物体和脚本失活都会执行,除非关闭或销毁,否则不能停下来。

1、 可以用其他的对象开启延迟函数,但要保证谁开启的延迟函数,对应的函数体就必须在对应游戏物体身上。

2、 无论谁开启的,只要函数在该物体的内部,该物体就可以停止延迟函数。

Invoke只能开启一次,多次开启使用invokeRepeating

invokeRepeating(方法名,第一次延迟秒数,第一次之后每隔多少秒执行一次);

invokeRepeating取消方式和invoke一样

 

Invoke是一个委托,协程是一个迭代,unity中不可以用线程,因为多个线程帧同步会出问题。(网络游戏中接包可能需要使用)非要用就得做帧同步,把线程的间隔时间和主线程的帧进行同步(通过time.deltatime)。

 

 

 

自动寻路

NavMesh

Agent Radius : 边缘的半径,不是寻路区域的。是寻路区域到边缘之间的非寻路区域的半径。

Agent height: 烘焙人物的高度。决定可到路径的高度

Max slope: 烘焙坡度

Step height:烘焙高度,由人物高度决定

Generated off mesh links:跳跃相关的内容,设置跳跃的距离。

烘焙完成后,还需要在对应角色身上加上寻路组件,navMeshAgent。

障碍必须是静态的才能影响路径,

空物体添加nav mesh obstacle组件也可以称为障碍物,该组件类似碰撞体

不同的路径可以添加Areas模块,分别选中对应路径进行烘焙。新建区域后,在Object中指定路径选中烘焙区域。然后在navMeshAgent组件中再指定玩家的路线。

Off mesh link 跳跃障碍物的组件

插件属性

代码获取该组件需要引用unityEngine.AI.

MeshAgent.SetDestination(目标点);

游戏开发的MVC架构

Model 数据层  view 显示层  controller 控制层

核心思想:将数据层和显示层分开,做到高内聚低耦合。

玩家在显示层进行输入,显示层会将输入的内容通过控制层传递给数据层

文件操作

Directory:对文件夹的操作

File :对文件的操作

FileStream :以字节流对文件操作

计算机最小单位是位,8位一字节,但位太小,字节是文件的基本处理单位。

具体操作参考C#面向对象中文件操作类。

Unity中不再使用C#的方法,unity自己提供了一个文件类。

TextAsset类

创建pulibc TextAsset textasset;可用常规方式加载

textasset.text;读取所有文本

Asset的路径:Application.dataPath+@”/(必须加斜杠)路径(必须加后缀名)”

Unity json

Unity自带jsonUility类。

Json是一种轻量级的数据交互格式,易于机器解析和生成。

Json的格式:<名称/值>

名称要使用字符串的格式,值可以是多种类型。名称对之间用逗号来分割。

{“id”:1,“name”:”ccc”}

类和类内的属性字段如果不是public的,就无法序列化成json.

Sring json = JsonUtility.ToJson(对象);

对象 = JsonUtility.FromJson<类名>(json);//json名和转化的类的字段名称必须相同。

Unity的Json大括号之间不能加逗号{“id”:1,“name”:”ccc”},{“id”:1,“name”:”ccc”}

配合文件读取函数使用:

str = File.ReadAllText(Application.dataPath+@”/(必须加斜杠)路径(必须加后缀名)”

);返回字符串,读取所有文件内容为str

string[] strs = File.ReadAllLines(Application.dataPath+@”/(必须加斜杠)路径(必须加后缀名)”

);返回字符串数组,每一行为数组的一个元素,读取文件内容的每一行为string数组

 

Unity网络

网络模型五大结构:应用层(应用程序),传输层(主要用来实现端到端的通讯,在传输层定义了不同服务质量的协议(TCP传输控制协议,高质量传输,会检查有没有收到,没收到会再发,例如HTTP请求。UDP数据报协议,不检查有没有收到,直接传输,例如直播))。

网络层:用来决定网络消息在发送过程中走哪条线。

数据链路层:确定物理地址,源地址等问题,短途传输,例如局域网

物理层:光纤等物理媒介

协议:不同语言的网络传输为了便于通讯,共同遵守的规则

IP,port(IP和端口):网络发送的目标。

在绑定端口号时,端口号分为几种:

1、 公认端口,0-1023,常用服务绑定端口,通常这些端口已经明确表明了一些服务协议。例如80http通讯协议,自己用时,不要用1023之前的。

2、 注册端口:从1024到49151.建议大家在写自己程序时使用此类

3、 动态或私有端口:49152到65535,理论上,不为服务分配此类端口

65535是计算机所能开启的最大线程数量,但实际的最大限制于计算机性能。

Socket链接

每一个tcp/udp都有对应的ip地址,每一组ip地址和端口统称为一个Socket(套接字),通常也叫做一个连接服务。一个Socket套接字能确定连接双方的ip,端口。

通常把一个socket定义成一个点到点的通讯,socket在客户端和服务端连接上之后,就可以发送多条消息,不会自动停止,在没有消息发送时,也会连接,但此时会发送链路消息包,俗称心跳包,否则服务器会自动结束socket。所以行业中通常将socket当做长连接。

 

TCP链接:

TCP建立链接需要三次握手:

1、 第一次,源主机给服务器发送一个同步标志位(SYN),同时会发送一个ISN作为初始序号(ISN是随时间变化的随机值)

2、 第二次握手,目标服务器返回一个确认数据段,会将SYN加1,并且ISN也加1,f返回给源主机。

3、 源主机再发送一个数据段,同样带有递增的发送序号和确定序号。

三次握手之后,才开始发送真正需要发送的内容(仅一次内容,想要再次发送必须要重新三次握手)。

发送完内容后,服务器返回信息,信息返回后会自动断掉链接。

所以httprequest通过TCP协议的链接又称为短连接。

http是被动传输。只有有客户端发送,才能回。

 

http超文本传输协议

互联网应用最广泛的网络传输协议。客户端和服务端请求应答的标准(TCP)。一般来说用在webrequest,通过webhtml或者网络爬虫和web服务器进行通讯。

http是被动传输。只有有客户端发送,才能回。

Unity使用(www类)

WebRequest是一个抽象类,使用时WebRequest.Create(url);就会返回一个Webrequest的对象。
WebResponse response = request.GetResponse();
Stream stream = response.GetResponseStream();
StreamReader sr = new StreamReader(stream,system.text.Encoding.UTF-8);读流的方式读取网络返回的流
String str = Sr.ReadToEnd();//读流读到结束
 
IEnumerator start(){//将start改成一个范围值为迭代的方法,unity内置机制会开启一个协程。之所以要用协程,是因为内容可能没有加载好,直接访问会报错。
WWW www = new WWW(url);
Yield return www;
tu = www.texture;
}

Socket使用(客户端链接)

Using system.Net.Sockets
Using system.Net
Using system.Threading;多线程的命名空间
Socket ss;
Bytes[] buffer = new byte[1024*1024];
Void start(){
//连接服务器
Ss = new Socket(AddressFamily.Internetwork,SocketType.Stream,ProtocolType.Tcp);通过socket的构造函数设定socket类型,参数:协议族,传输方式,链接方式
IPAddress ip = IPAddtrss.Parse(“127.0.0.1”);//链接本机,如果是服务器,就使用服务器主机ip
ss.Connect(ip,12345);//ip和端口,此处端口必须是服务器监听的端口
Thread recive = new Thread(Recives);开启一个线程去接收服务器返回的信息,参数是入口函数名称,该函数在下面自己定义
recive.Start();//开启线程
} 
Void Recives(){
线程运行完,会直接关闭,为了保持长连接,不能让线程结束,所以要使用死循环
While(true){
If(ss==null){//如果链接断了,就跳出死循环,结束线程
Break;
}
Int bufcount = ss.Receive(buffer);//该接收方法是堵塞性方法,接收不到会一直卡在这里,buffer是接收返回信息的字节流数组,方法返回字节数组的长度。
读取字节数组,注意编码一定要和服务端返回的一样。
} 
}

C#服务端

Using内容相同

using
using
using
using

public class fuwuqi : MonoBehaviour
Socket servers;//创建socket链接
string ip =  "192.168.2.2";
int
void Start
new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);//同客户端设置
IPEndPoint ends =  new IPEndPoint(IPAddress.Parse(ip),port);//IPAddress.Parse(ip)将ip转化成IP格式。通过ip端口组合成一个IPEndPoint结构
//绑定ip地址和端口,用来接收对应端口的消息
//最多监听10个链接
Thread acceptThread = new Thread(Accepts);//开启一个线程接收客户端信息
        acceptThread.Start();
}

void
Debug.Log("连接成功,等待客户端!");
while (true) {
Socket client = servers.Accept();//监听其他源主机的请求,如果有数据发送过来,就会从堵塞形态放开,返回值就是连接进来的客户端的socket
if (client!=null) {
Debug.Log("有一个客户端进入连接!");
            }
string message = 你好啊!";
byte[] buffer =  Encoding.UTF8.GetBytes(message);
//向客户端发送数据
        }
    }
}

完整的网络链接

AssetBundle学习

AssetBundles打包

1、设置打包内容

 

每个资源的右下角都有AssetBundle的打包选项,默认none不打包,否则就是打包的包名称。

此处填写包名称(支持路径)和包的后缀名(后缀名随意)。填写路径时,会自动在文件夹下创建对应的路径文件夹。

2、依赖打包

在打包过程中,为了节省空间,减少包的大小,需要将多个物体共同使用的资源单独来打包,然后让对应物体都使用这一个包的资源,例如材质和贴图。

具体操作方式:(以材质贴图为例,其实不需要人为操作什么,unity会自己处理)

1、将材质贴图单独打包。

2、其他物体打包时,unity会自动寻找该物体所用到的材质贴图,如果发现材质贴图已经被打包了,就会自动依赖贴图材质的包。

3、打包代码

[MenuItem("Assets/Bulid AssetBundles")]

将该静态函数添加到Assets目录下,作为可以直接执行的功能,但前提必须为静态方法,并且该代码必须在Editor文件夹(编辑器扩展文件夹)中。

static void BulidAllAsset() {//定义静态函数,必须是静态的,否则不能在菜单栏中显示
string dir ="abs";
if (!Directory.Exists(dir)) {//判断是否存在这个路径,不存在就创建一个,使用该函数必须using system.IO;
注意:路径名不能和AssetBundle的打包名称相同
Directory.CreateDirectory(dir);
        }
//BuildPipeline类下的BuildAssetBundles(路径,打包的参数,打包的目标平台);
BuildPipeline.BuildAssetBundles(dir, BuildAssetBundleOptions.None,BuildTarget.StandaloneWindows64);
  }

4、BuildAssetBundleOptions参数

BuildAssetBundleOptions.None:使用LZMA算法压缩,压缩的包更小,但是加载时间更长。使用之前需要整体解压。一旦被解压,这个包会使用LZ4重新压缩。使用资源的时候不需要整体解压。在下载的时候可以使用LZMA算法,一旦它被下载了之后,它会使用LZ4算法保存到本地上。( 相当于只有第一次加载会比较慢,随后就会变成lz4压缩。)

BuildAssetBundleOptions.UncompressedAssetBundle:不压缩,包大,加载快

BuildAssetBundleOptions.ChunkBasedCompression:使用LZ4压缩,压缩率没有LZMA高,但是我们可以加载指定资源而不用解压全部。

注意使用LZ4压缩,可以获得可以跟不压缩想媲美的加载速度,而且比不压缩文件要小。

AssetBundles读取

1、本地读取

AssetBundle all =  AssetBundle.LoadFromFile("ABS/assetbundles.ab");//从文件中同步加载资源,返回值类型AssetBundle
//方法1:
Object[] obj = all.LoadAllAssets();//加载包中所有的资源,返回obj格式的
for (int
Debug.Log(obj[i].name);
}
//方法2:
GameObject g = all.LoadAsset<GameObject>("Lux@attack1");//获取指定资源,填写名称
Instantiate<GameObject>(g);
//从内存中加载
AssetBundle all =  AssetBundle.LoadFromMemory(File.ReadAllBytes("ABS/assetbundles.ab"));//从内存中同步加载资源包,参数必须为字节流
all.LoadAsset<GameObject>("Lux@attack1");
//从内存中异步加载
IEnumerator Start
AssetBundleCreateRequest all = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes("ABS/assetbundles.ab"));//从内存中异步加载资源包,参数必须为字节流
yield return
//使用
"ccc");
 }

说明:

如果存在依赖关系,必须加载所依赖的包(即使原本就有也不行,必须加载依赖包),加载的顺序没有影响,只要在使用之前加载,加载出来即可,不需要做其他操作,系统会自动寻找依赖资源。

2、服务器读取方式

1、www加载

IEnumerator Start
//使用www加载资源,该方法已被弃用,不推荐使用。
while (Caching.ready != true) {//因为www把内容下载到缓存中,所以使用www之前需要先判断一下缓存是否准备好
yield return null;
        }
WWW all =  WWW.LoadFromCacheOrDownload(@"file://D:\unity\AssetBundle\abs\assetbundles.ab", 1);//路径说明,www的路径最好是网络路径,本地路径必须加上file:// 或者 file:///,1是版本号
if (!string.IsNullOrEmpty(all.error)) {//www如果出错是不会报错的,所以需要人为的进行错误判断
Debug.Log(all.error);
yield break;
        }
yield return
//使用
GameObject g = all.assetBundle.LoadAsset<GameObject>("Lux@attack1");
GameObject>(g);
}
2、通过UnityWebRequest方式加载对应的资源
IEnumerator Start() {
//使用unityRequest方法加载远程资源
string uri =  @"file://D:\unity\AssetBundle\abs\assetbundles.ab";
UnityWebRequest request =  UnityWebRequest.GetAssetBundle(uri);//创建链接对象
yield return request.Send();//开始下载,并等待下载完成request.Send()返回AsyncOperation类型,和常规的异步加载一样。
AssetBundle all =  DownloadHandlerAssetBundle.GetContent(request);//从request中获取对应assetbundle资源
//使用
GameObject g = all.LoadAsset<GameObject>("Lux@attack1");
GameObject>(g);
}

通过Manifest文件加载assetbundle文件的依赖包

系统在打包assetbundle时会在对应目录下生成一个和目录同名的包,该包没有对应资源,但是它的manifest文件会记录文件夹下所有包的名称和每个包的依赖文件,所以可以通过该文件获取依赖包,也可以通过每个包的manifest文件来获取。

AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);
AssetBundleManifest manifest = 
assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
string[] dependencies = manifest.GetAllDependencies("assetBundle"); //Pass the name of the bundle you want the dependencies for.
foreach(string dependency in dependencies)
{
    AssetBundle.LoadFromFile(Path.Combine(assetBundlePath, dependency));
}

AssetBundle卸载

卸载有两个方面

1,减少内存使用

2,有可能导致丢失

所以什么时候去卸载资源

AssetBundle.Unload(true)卸载所有资源,即使有资源被使用着

1,在关切切换、场景切换

2,资源没被用的时候

AssetBundle.Unload(false)卸载所有没用被使用的资源

个别资源怎么卸载:

1,通过 Resources.UnloadUnusedAssets.

2,场景切换的时候