協同程式(協同或者協程)
線程、程序
一個應用程式就是一個程序,籠統的說就是一個.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是接收傳回資訊的位元組流數組,方法傳回位元組數組的長度。
String message = Encoding.UTF8.GetString(buffer,0,bufcount);//讀取位元組數組,注意編碼一定要和服務端傳回的一樣。
}
}
C#服務端
Using内容相同
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
public class fuwuqi : MonoBehaviour {
Socket servers;//建立socket連結
string ip = "192.168.2.2";
int port = 5555;
void Start () {
servers = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);//同用戶端設定
IPEndPoint ends = new IPEndPoint(IPAddress.Parse(ip),port);//IPAddress.Parse(ip)将ip轉化成IP格式。通過ip端口組合成一個IPEndPoint結構
servers.Bind(ends);//綁定ip位址和端口,用來接收對應端口的消息
servers.Listen(10);//最多監聽10個連結
Thread acceptThread = new Thread(Accepts);//開啟一個線程接收用戶端資訊
acceptThread.Start();
}
void Accepts() {
Debug.Log("連接配接成功,等待用戶端!");
while (true) {
Socket client = servers.Accept();//監聽其他源主機的請求,如果有資料發送過來,就會從堵塞形态放開,傳回值就是連接配接進來的用戶端的socket
if (client!=null) {
Debug.Log("有一個用戶端進入連接配接!");
}
string message = "你好啊!";
byte[] buffer = Encoding.UTF8.GetBytes(message);
client.Send(buffer);//向用戶端發送資料
}
}
}
完整的網絡連結
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 i = 0;i < obj.Length;i++) {
Debug.Log(obj[i].name);
}
//方法2:
GameObject g = all.LoadAsset<GameObject>("[email protected]");//擷取指定資源,填寫名稱
Instantiate<GameObject>(g);
//從記憶體中加載
AssetBundle all = AssetBundle.LoadFromMemory(File.ReadAllBytes("ABS/assetbundles.ab"));//從記憶體中同步加載資源包,參數必須為位元組流
all.LoadAsset<GameObject>("[email protected]");
//從記憶體中異步加載
IEnumerator Start () {
AssetBundleCreateRequest all = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes("ABS/assetbundles.ab"));//從記憶體中異步加載資源包,參數必須為位元組流
yield return all;
//使用
all.assetBundle.LoadAsset("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 all;
//使用
GameObject g = all.assetBundle.LoadAsset<GameObject>("[email protected]");
Instantiate<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>("[email protected]");
Instantiate<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,場景切換的時候