Unity如何連線伺服器: 一個簡單的例子
Unity3D本身是用來做客戶端的通用遊戲引擎, 要建立網路連線的話, 其實需要使用的是C#本身的網路和執行緒模組, 即System.Net.Sockets & System.Threading. 本文中我做了一個簡單的例子, 適合那些需要做Unity客戶端連線伺服器功能的人入門.
整體專案
客戶端專案地址: https://share.weiyun.com/5M9jp6c
伺服器專案下載: https://share.weiyun.com/5TMCQYP
客戶端: 我做的專案主要是一個簡單的Demo, 畫面上只有三個按鈕和兩個輸入框, 通過點選按鈕可以實現相應的操作.
服務端: 服務端是一個Python寫的伺服器. 這個部分不是我本文的重點, 大家可以參考別的網上文章, 瞭解如何寫一個C++, Python或者Java伺服器, 無論什麼語言寫的伺服器都是可以與Unity進行互動的.
下載專案後, 使用Unity匯入, 可以看到Scripts資料夾中有六個指令碼, 其中NetworkCore和UIManager是主要的指令碼, Json開頭的指令碼不是重點, 他們只是Json編碼解碼相關的一個庫(文中我是直接使用的https://github.com/gering/Tiny-JSON這個老外寫的純C#版本Json Parser), Json的編碼和解析也不是本文重點, 只要找到一個庫能用即可.
後續補充: Json的工具庫現在推薦使用Newtonsoft出品的json.NET
. 下載地址https://github.com/JamesNK/Newtonsoft.Json/releases, 在Unity2018.1中, 請使用其中的Bin\net20\Newtonsoft.Json.dll
這個大小513KB的DLL(此處我也在微雲存了一個供大家快速下載https://share.weiyun.com/5pky2k3), 由於Unity2018用的還是.NET2.0版本, 因此要用老的.
學習步驟
下載客戶端和服務端, 執行起來. 之後主要學習NetworkCore.cs和UIManager.cs這兩個指令碼的內容(兩個指令碼並不複雜), 最關鍵的部分是如何建立連線, 建立後臺執行緒, 傳送和接收資料, 以及Json相關的字典操作.
指令碼1: NetworkCore.cs
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
using Tiny;
public class NetworkCore : MonoBehaviour {
public string serverAddress = "127.0.0.1";
public int serverPort = 5000;
public string username = "chen";
public string password = "123";
private TcpClient _client;
private NetworkStream _stream; // C#中採用NetworkStream的方式, 可以類比於python網路程式設計中的socket
private Thread _thread;
private byte[] _buffer = new byte[1024]; // 接收訊息的buffer
private string receiveMsg = "";
private bool isConnected = false;
void Start() {
}
public void OnApplicationQuit() {
Dictionary<string, string> dict = new Dictionary<string, string>()
{
{"code", "exit"}
};
SendData(Encode(dict)); // 退出的時候先發一個退出的訊號給伺服器, 使得連線被正確關閉
Debug.Log("exit sent!");
CloseConnection ();
}
// --------------------public--------------------
public void Login() {
SetupConnection();
Dictionary<string, string> dict = new Dictionary<string, string>()
{
{"code", "login"},
{"username", username},
{"password", password}
};
SendData(Encode(dict));
Debug.Log("start!");
}
public void SendGameData(int score, int health) {
Dictionary<string, string> dict = new Dictionary<string, string>()
{
{"code", "gds"},
{"score", score.ToString()},
{"health", health.ToString()}
};
SendData(Encode(dict));
}
// -----------------------private---------------------
private void SetupConnection() {
try {
_thread = new Thread(ReceiveData); // 傳入函式ReceiveData作為thread的任務
_thread.IsBackground = true;
_client = new TcpClient(serverAddress, serverPort);
_stream = _client.GetStream();
_thread.Start(); // background thread starts working while loop
isConnected = true;
} catch (Exception e) {
Debug.Log (e.ToString());
CloseConnection ();
}
}
private void ReceiveData() { // 這個函式被後臺執行緒執行, 不斷地在while迴圈中跑著
Debug.Log ("Entered ReceiveData function...");
if (!isConnected) // stop the thread
return;
int numberOfBytesRead = 0;
while (isConnected && _stream.CanRead) {
try {
numberOfBytesRead = _stream.Read(_buffer, 0, _buffer.Length);
receiveMsg = Encoding.ASCII.GetString(_buffer, 0, numberOfBytesRead);
_stream.Flush();
Debug.Log(receiveMsg);
receiveMsg = "";
} catch (Exception e) {
Debug.Log (e.ToString ());
CloseConnection ();
}
}
}
private void SendData(String msgToSend)
{
byte[] bytesToSend = Encoding.ASCII.GetBytes(msgToSend);
if (_stream.CanWrite)
{
_stream.Write(bytesToSend, 0, bytesToSend.Length);
}
}
private void CloseConnection() {
if (isConnected) {
_thread.Interrupt (); // 這個其實是多餘的, 因為isConnected = false後, 執行緒while條件為假自動停止
_stream.Close ();
_client.Close ();
isConnected = false;
receiveMsg = "";
}
}
// ---------------------util----------------------
// encode dict to to json and wrap it with \r\n as delimiter
string Encode(Dictionary<string, string> dict)
{
string json = Json.Encode(dict);
string header = "\r\n" + json.Length.ToString() + "\r\n";
string result = header + json;
Debug.Log("encode result:" + result);
return result;
}
// decode data, 注意要解決粘包的問題, 這個程式寫法同GameLobby中的相應模組一模一樣
// 參考 https://github.com/imcheney/GameLobby/blob/master/server/util.py
Dictionary<string, string> Decode(string raw)
{
string payload_str = "";
string raw_leftover = raw;
if (raw.Substring(0, 2).Equals("\r\n"))
{
int index = raw.IndexOf("\r\n", 2);
int payload_length = int.Parse(raw.Substring(2, index - 2 + 1)); // 注意, C#'s substring takes start and length as args
if (raw.Length >= index + 2 + payload_length)
{
payload_str = raw.Substring(index + 2, payload_length);
raw_leftover = raw.Substring(index + 2 + payload_length);
}
}
return Json.Decode<Dictionary<string, string>>(payload_str);
}
}
指令碼2: UIManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; //using 關鍵字用於在程式中包含名稱空間。一個程式可以包含多個 using 語句。
public class UIManager : MonoBehaviour {
public InputField scoreInputField;
public InputField healthInputField;
NetworkCore networkCore;
// Use this for initialization
void Start () {
networkCore = GetComponent<NetworkCore>();
}
// Update is called once per frame
void Update () {
}
public void OnLoginButton() {
networkCore.Login();
}
public void OnSendButton() {
int score = int.Parse(scoreInputField.text);
int health = int.Parse(healthInputField.text);
networkCore.SendGameData(score, health);
}
public void OnQuitButton()
{
int score = int.Parse(scoreInputField.text);
int health = int.Parse(healthInputField.text);
networkCore.SendGameData(score, health);
Application.Quit();
}
}
後續持續開發優化建議
Unity客戶端網路應該是使用佇列模式(生產者消費者), 可以參見我的SurvivalShooterServer中客戶端的NetworkMaster的程式碼https://github.com/imcheney/SurvivalShooterServer/blob/master/client/Scripts/Network/NetworkMaster.cs
相關文章
- spring 簡單的使用 Hikari連線池 和 jdbc連線mysql 的一個簡單例子SpringJDBCMySql單例
- 一個簡單的反射連線程式反射線程
- 擼一個簡單的MVVM例子MVVM
- 一個最簡單的 Github workflow 例子Github
- 一個簡單的觀察者模式例子模式
- 一個簡單的Ajax請求例子
- JUnit概述及一個簡單例子單例
- WebRTC:一個視訊聊天的簡單例子Web單例
- 一個簡單的例子教會您使用javapJava
- 一個簡單的spring-boot例子Springboot
- 一個簡單的例子帶你理解HashmapHashMap
- 一個閉包函式的簡單例子函式單例
- 尋struts連oracle簡單例子Oracle單例
- 一個簡單的netty通訊的例子Netty
- spring攔截器的一個簡單例子Spring單例
- 轉一篇OpenSSL的例子:簡單的TLS伺服器TLS伺服器
- Java多型的一個簡單入門的例子Java多型
- Spring-Context之一:一個簡單的例子SpringContext
- php mysql 一個查詢優化的簡單例子PHPMySql優化單例
- linux c 一個autotools的最簡單例子Linux單例
- 一個簡單的oracle函式返回陣列的例子Oracle函式陣列
- 一個不錯的JDBC連線池教程(帶具體例子)JDBC
- 一個簡單的MySQL引數導致的連線問題解惑MySql
- 一個關於SQL隱碼攻擊的簡單例子SQL單例
- C++ Boost 之Python(一個簡單的例子) (轉)C++Python
- go語言如何入門?從一個簡單例子開始學起Go單例
- 軟體開發中的矛盾——一個簡單的例子 (轉)
- 教你如何寫一個簡單的折線圖控制元件控制元件
- XPATH的簡單例子單例
- SAP MM採購定價過程的一個簡單例子單例
- 一個簡單例子教會你C++動態庫的用法單例C++
- 【xiaosonl】一個極其簡單的線上C#IDE例子C#IDE
- 通過一個簡單的例子,瞭解 Cypress 的執行原理
- 用java實現一個簡單的序列化的例子(轉)Java
- 用java實現一個簡單的序列化的例子 (轉)Java
- 簡單的php連線mysql類PHPMySql
- 常用遠端連線伺服器的軟體,常用遠端連線伺服器的軟體是哪個?如何連線使用?伺服器
- 資料庫連線 系列一:laravel框架如何連線兩個資料庫(不同伺服器)資料庫Laravel框架伺服器