Unity Sockect實(shí)現(xiàn)畫(huà)面實(shí)時(shí)傳輸案例原理解析
前言
提示:這里可以添加本文要記錄的大概內(nèi)容:
例如:隨著人工智能的不斷發(fā)展,機(jī)器學(xué)習(xí)這門(mén)技術(shù)也越來(lái)越重要,很多人都開(kāi)啟了學(xué)習(xí)機(jī)器學(xué)習(xí),本文就介紹了機(jī)器學(xué)習(xí)的基礎(chǔ)內(nèi)容。
提示:以下是本篇文章正文內(nèi)容,下面案例可供參考
一、Socket通信原理
Socket是比較常用的一種通信方式。有關(guān)介紹可以點(diǎn)擊查看Socket通信原理
二、畫(huà)面?zhèn)鬏斣O(shè)計(jì)
1.邏輯設(shè)計(jì)圖
2.Unity服務(wù)端
首先創(chuàng)建一個(gè)Unity工程,然后新建Server場(chǎng)景,用于接受數(shù)據(jù),展示畫(huà)面。
然后再場(chǎng)景中創(chuàng)建一個(gè)RawImage并設(shè)置為全屏。
如圖:
然后創(chuàng)建一個(gè)腳本,命名為UnityServer,再創(chuàng)建一個(gè)UnityServer.cs
在Start函數(shù)中創(chuàng)建Socket服務(wù)器,并開(kāi)啟一個(gè)線程用于接受數(shù)據(jù)。
這里要注意一點(diǎn),不能在接受數(shù)據(jù)線程中處理數(shù)據(jù),需要在主線程中進(jìn)行處理。
因?yàn)閁nity主線程里面的資源不允許其他線程進(jìn)行訪問(wèn)。
在Update函數(shù)中處理數(shù)據(jù),并展示圖片。
UnityServer .cs代碼如下:
using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Sockets; using System.Threading; using UnityEngine; using UnityEngine.UI; public class UnityServer : MonoBehaviour { Socket socket = null; Thread thread = null; byte[] buffer = null; bool receState = true; int readTimes = 0; public RawImage rawImage; private Queue<byte[]> datas; void Start () { buffer = new byte[1024 * 1024 * 10]; // 創(chuàng)建服務(wù)器, 以Tcp的方式 socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Connect(IPAddress.Parse("192.168.1.87"), 10002); // 開(kāi)啟一個(gè)線程, 用于接受數(shù)據(jù) thread = new Thread(new ThreadStart(Receive)); thread.Start(); datas = new Queue<byte[]>(); } private void Receive() { while (thread.ThreadState == ThreadState.Running && socket.Connected) { // 接受數(shù)據(jù)Buffer count是數(shù)據(jù)的長(zhǎng)度 int count = socket.Receive(buffer); if (receState && count > 0) { receState = false; BytesToImage(count, buffer); } } } MemoryStream ms = null; public void BytesToImage(int count, byte[] bytes) { try { ms = new MemoryStream(bytes, 0, count); datas.Enqueue(ms.ToArray()); // 將數(shù)據(jù)存儲(chǔ)在一個(gè)隊(duì)列中,在主線程中解析數(shù)據(jù)。這是一個(gè)多線程的處理。 readTimes++; if (readTimes > 5000) { readTimes = 0; GC.Collect(2); // 達(dá)到一定次數(shù)的時(shí)候,開(kāi)啟GC,釋放內(nèi)存 } } catch { } receState = true; } void Update() { if (datas.Count > 0) { // 處理紋理數(shù)據(jù),并顯示 Texture2D texture2D = new Texture2D(Screen.width, Screen.height); texture2D.LoadImage(datas.Dequeue()); rawImage.texture = texture2D; } } void OnDestroy() { try { if (socket != null) { socket.Shutdown(SocketShutdown.Both); } } catch { } try { if (thread != null) { thread.Abort(); } } catch { } datas.Clear(); } }
然后在場(chǎng)景中創(chuàng)建一個(gè)GameObject,將腳本掛載上,并將創(chuàng)建的RawImage拖拽到Inspector面板上對(duì)應(yīng)的位置。
如圖:
3.Unity客戶端
然后我們創(chuàng)建一個(gè)客戶端工程,創(chuàng)建一個(gè)Client場(chǎng)景。
選中Main Camera,用Ctrl+D復(fù)制一個(gè)攝像機(jī),放在Main Camera下面。
設(shè)置localPosition 和 localRotation為零。
這個(gè)相機(jī)的主要作用抓取屏幕渲染紋理。
如圖:
然后再創(chuàng)建一個(gè)腳本,命名為UnityClient.cs腳本。在Start中開(kāi)啟Socket,然后開(kāi)啟一個(gè)線程發(fā)送數(shù)據(jù)。
將其掛載在Main Camera上面,并將渲染攝像機(jī)拖拽到相應(yīng)的位置。
UnityClient.cs代碼如下:
using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Threading; using UnityEngine; public class UnityClient : MonoBehaviour { public Camera cam; public int port = 10002; RenderTexture cameraView = null; Socket socket = null; Thread thread = null; bool success = true; Dictionary<string, Client> clients = new Dictionary<string, Client>(); Vector3 old_position; // 舊位置 Quaternion old_rotation; // 舊旋轉(zhuǎn) void Start () { cameraView = new RenderTexture(Screen.width, Screen.height, 24); cameraView.enableRandomWrite = true; cam.targetTexture = cameraView; old_position = transform.position; old_rotation = transform.rotation; // 開(kāi)啟Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Bind(new IPEndPoint(IPAddress.Parse("192.168.1.87"), port)); socket.Listen(100); // 開(kāi)啟一個(gè)線程發(fā)送渲染數(shù)據(jù) thread = new Thread(new ThreadStart(OnStart)); thread.Start(); } int isNewAdd = 0; void OnStart() { Debug.Log("Socket創(chuàng)建成功"); while (thread.ThreadState == ThreadState.Running) { Socket _socket = socket.Accept(); if (clients.ContainsKey(_socket.RemoteEndPoint.ToString())) { try { clients[_socket.RemoteEndPoint.ToString()].socket.Shutdown(SocketShutdown.Both); } catch { } clients.Remove(_socket.RemoteEndPoint.ToString()); } Client client = new Client { socket = _socket }; clients.Add(_socket.RemoteEndPoint.ToString(), client); isNewAdd = 1; } } void Update() { if (success && clients.Count > 0) { success = false; SendTexture(); } if (isNewAdd > 0) { isNewAdd = 0; SendTexture(1); } } void OnGUI() { GUI.DrawTexture(new Rect(10, 10, 240, 135), cameraView, ScaleMode.StretchToFill); } void OnApplicationQuit() { try { socket.Shutdown(SocketShutdown.Both); } catch { } try { thread.Abort(); } catch { } } Texture2D screenShot = null; int gc_count = 0; void SendTexture(int isInt = 0) { if ((!old_position.Equals(transform.position) || !old_rotation.Equals(transform.rotation)) || isInt == 1) { if (null == screenShot) { screenShot = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false); } // 讀取屏幕像素進(jìn)行渲染 RenderTexture.active = cameraView; screenShot.ReadPixels(new Rect(0, 0, cameraView.width, cameraView.height), 0, 0); RenderTexture.active = null; byte[] bytes = screenShot.EncodeToJPG(100); foreach (var val in clients.Values) { try { val.socket.Send(bytes); } catch { if (!val.socket.Connected) { clients.Remove(val.socket.RemoteEndPoint.ToString()); } } } gc_count++; if (gc_count > 5000) { gc_count = 0; GC.Collect(2); } Debug.Log("發(fā)送數(shù)據(jù):" + (float)bytes.Length / 1024f + "KB"); old_position = cam.transform.position; old_rotation = cam.transform.rotation; } success = true; } void OnDestroy() { try { socket.Shutdown(SocketShutdown.Both); } catch { } try { thread.Abort(); } catch { } } } class Client { public Socket socket = null; }
4.最終效果
到此這篇關(guān)于Unity Sockect實(shí)現(xiàn)畫(huà)面實(shí)時(shí)傳輸?shù)奈恼戮徒榻B到這了,更多相關(guān)Unity Sockect畫(huà)面實(shí)時(shí)傳輸內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#使用GZipStream實(shí)現(xiàn)文件的壓縮與解壓
這篇文章主要為大家詳細(xì)介紹了C#使用GZipStream實(shí)現(xiàn)文件的壓縮與解壓,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10利用MySqlBulkLoader實(shí)現(xiàn)批量插入數(shù)據(jù)的示例詳解
MySQLBulkLoader是MySQL?Connector/Net類(lèi)中的一個(gè)類(lèi),用于包裝MySQL語(yǔ)句。本文將利用MySqlBulkLoader實(shí)現(xiàn)批量插入數(shù)據(jù)功能,感興趣的可以了解一下2022-06-06C#實(shí)現(xiàn)TFTP客戶端的項(xiàng)目實(shí)踐
TFTP不僅有斷點(diǎn)續(xù)傳,多用戶級(jí)別限制等功能,本文主要介紹了C#實(shí)現(xiàn)TFTP客戶端的項(xiàng)目實(shí)踐,具有一定的參考價(jià)值,感興趣的可以了解一下2024-04-04C#實(shí)現(xiàn)帶引導(dǎo)窗體的窗體設(shè)計(jì)操作流程
很多時(shí)候,我們的窗體設(shè)計(jì)需要一個(gè)引導(dǎo)窗體,當(dāng)打開(kāi)一個(gè)項(xiàng)目的窗體時(shí),默認(rèn)的是先打開(kāi)一個(gè)歡迎或介紹項(xiàng)目信息的引導(dǎo)窗體,幾秒鐘后再打開(kāi)項(xiàng)目的主窗體,本文給大家介紹了C#實(shí)現(xiàn)帶引導(dǎo)窗體的窗體設(shè)計(jì)操作流程,感興趣的朋友可以參考下2024-04-04C#解決SQlite并發(fā)異常問(wèn)題的方法(使用讀寫(xiě)鎖)
這篇文章主要介紹了C#解決SQlite并發(fā)異常問(wèn)題的方法,通過(guò)使用讀寫(xiě)鎖達(dá)到多線程安全訪問(wèn),進(jìn)而解決SQLite并發(fā)異常的問(wèn)題,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07C#中FileSystemWatcher類(lèi)實(shí)現(xiàn)監(jiān)控文件夾
在C#中,如果你想要監(jiān)控一個(gè)文件夾內(nèi)文件的變動(dòng)情況,比如文件的創(chuàng)建、刪除、修改等,你可以使用FileSystemWatcher類(lèi),下面就來(lái)介紹一下FileSystemWatcher監(jiān)控的使用,感興趣的可以了解一下2024-03-03C#控制臺(tái)程序如何發(fā)布到服務(wù)器Linux上運(yùn)行
這篇文章主要給大家介紹了關(guān)于C#控制臺(tái)程序如何發(fā)布到服務(wù)器Linux上運(yùn)行的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2021-11-11