概述
在Unity中我们常常需要后端提供的接口,这是一种网络请求,增删改查数据库中的数据,而Unity为我们提供了一个强大的类:UnityWebRequest来帮助我们.
世界观
但在讲这个类之前我们需要掌握一些基本知识,才能更好的理解我们到底在干什么.也就说我先简单讲一下后端在干什么,然后再讲使用Unity和后端对接.
现在数据一般是放在数据库中的,使用SQL语句可以查询数据库,但是现代开发往往是模块化开发,降低耦合,所以又实现了一个中间层来对接数据库(譬如C#使用asp.net core ,java使用spring),Unity其实是通过Http协议和这个中间层打交道,Unity调用中间层提供的接口,中间层再去调用数据库(当然这也涉及了很多技术,但不是我们讨论的重点),这样Unity最终接触的是一个个接口,只要调用这个接口就可以实现对数据的增删改查,大大简化了彼此的依赖.现在我们以接口为切入点向下展开.
URL 和 Uri 的区别和联系
接口,其实就是一个个url或者uri.
1 URL(统一资源定位符:Uniform Resource Locator):
是一种具体的 Uri,它不仅标识资源,还包含访问该资源的方法(如 http://example.com)。
URL 包含协议(如 http、https)、域名、路径、查询参数等。
2 Uri(统一资源标识符:Uniform Resource Identifier):
是一个更广泛的概念,用于标识任何资源。Uri 可以是 URL,也可以是 URN(统一资源名称)。
Uri 通常由协议、主机、路径、查询和片段组成。
在UnityWebRequest中使用两者(往往两者就是该类的构造函数的参数)
URL:
URL 是一个字符串,代表目标资源的地址。可以是 web 地址(如 http://example.com)或本地文件路径(如 file://path/to/file)。
Uri:
Uri 是一个类,代表一个统一资源标识符,可以包含更详细的结构化信息。
可以通过构造函数或静态方法来创建 Uri 对象。
一般来说url更简单(因为就是一个地址字符串),但是uri和url差别并不是太大,往往后端会给你提供一个个url,可以直接使用.
HTTP协议:
HTTP 是一个应用层协议,主要用于传输超文本(如 HTML),但也可以传输图片、视频、JSON(在Unity中我们往往使用的是json) 数据等。HTTP 协议运行在 TCP/IP 之上,确保数据传输的可靠性。客户端和服务端往往都是通过请求响应式方式进行交互(譬如使用浏览器,输入一个网址进行请求,服务器接受到后解析然后返回内容).
HTTP 请求和响应
HTTP 请求:
由客户端(如浏览器或应用程序)发送到服务器,包含请求行、请求头和请求体。
请求行包括 HTTP 方法(如 GET、POST)、请求的 URL 和 HTTP 版本。
HTTP 响应:
由服务器返回给客户端,包含状态行、响应头和响应体。
状态行包括 HTTP 版本、状态码(如 200、404)和状态描述。
HTTP 请求和响应的详细组成(选读)
一 HTTP 请求
HTTP 请求是由客户端发送到服务器的消息,它由三个主要部分组成:请求行、请求头和请求体。
1请求行 (Request Line):
HTTP 方法: 指定要对资源执行的操作(例如 GET、POST、PUT、DELETE)。
请求的 URL: 资源的路径,通常包括路径和查询参数。
HTTP 版本: 指定使用的 HTTP 协议版本(例如 HTTP/1.1)。
示例:
GET /index.html HTTP/1.1
2请求头 (Request Headers):
包含关于客户端环境、请求体内容以及其他元数据的信息。
常见的请求头包括:
Host: 服务器的主机名和端口号。
User-Agent: 客户端的软件信息(例如浏览器类型和版本)。
Accept: 客户端希望接收的响应内容类型。
Content-Type: 请求体的媒体类型(用于 POST 或 PUT 请求)。
Authorization: 用于认证的凭证信息。
示例:
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
3请求体 (Request Body):
包含需要发送到服务器的数据。GET 请求通常没有请求体,而 POST 和 PUT 请求则通常包含请求体。
请求体的数据格式通常由 Content-Type 头指定(例如 application/json, application/x-www-form-urlencoded)。
示例:
{
"name": "John Doe",
"age": 30
}
二 HTTP 响应
HTTP 响应是服务器返回给客户端的消息,它也由三个主要部分组成:状态行、响应头和响应体。
1状态行 (Status Line):
HTTP 版本: 指定使用的 HTTP 协议版本(例如 HTTP/1.1)。
状态码: 指示请求的处理结果(例如 200, 404, 500)。
状态描述: 对状态码的简短描述(例如 OK, Not Found, Internal Server Error)。
示例:
HTTP/1.1 200 OK
2响应头 (Response Headers):
包含关于服务器、响应体内容以及其他元数据的信息。
常见的响应头包括:
Content-Type: 响应体的媒体类型。
Content-Length: 响应体的字节长度。
Date: 服务器生成响应的日期和时间。
Server: 服务器的软件信息。
Set-Cookie: 服务器发送的 Cookie 信息。
示例:
Content-Type: text/html; charset=UTF-8
Content-Length: 1234
Date: Mon, 31 May 2024 12:34:56 GMT
Server: Apache/2.4.41 (Ubuntu)
3响应体 (Response Body):
包含服务器返回给客户端的实际数据。可以是 HTML、JSON、XML、图片、视频等。
响应体的数据格式通常由 Content-Type 头指定。
示例:
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
</head>
<body>
<h1>Hello, world!</h1>
</body>
</html>
三 完整示例:完整的 HTTP 请求和响应
HTTP 请求示例
一个典型的 GET 请求可能如下所示:
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
HTTP 响应示例
服务器的响应可能如下所示:
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 138
Date: Mon, 31 May 2024 12:34:56 GMT
Server: Apache/2.4.41 (Ubuntu)
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
</head>
<body>
<h1>Hello, world!</h1>
</body>
</html>
我们平常使用浏览器,输入一个网址并不接触这些东西,但是这些过程的确发生了,并不会显示给用户看(没有这个必要).下面我们简单剖析这个过程:
在浏览器中输入一个地址并按下回车时,发生了一系列的过程。这个过程中,浏览器会构造一个HTTP请求发送到服务器。我们可以通过分析一个具体的URL来说明这一过程:
假设输入了:https://www.example.com/products?category=books&price=low
分析这个地址:
协议:https
这表明使用的是HTTPS协议,即HTTP的安全版本,数据传输会进行加密。
域名:www.example.com
这是服务器的地址,浏览器通过这个域名找到相应的服务器。
路径:/products
这指明了在服务器上资源的具体位置,通常对应服务器上的一个文件夹或由服务器动态生成的内容。
查询字符串:category=books&price=low
这部分包含了要传送给服务器的额外数据,用于告诉服务器用户想要查看哪种类别的产品,以及产品的价格范围是如何的。
构造的HTTP请求:
当输入并提交这个URL后,浏览器会发送一个HTTP GET请求到服务器。这个请求可能看起来如下:
请求行:
GET /products?category=books&price=low HTTP/1.1
请求头:
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/58.0.3029.110 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
在这个例子中:
请求行明确了使用的方法(GET),请求的资源(包括路径和查询字符串),以及HTTP协议的版本。
请求头包含了服务器需要的或对请求处理有帮助的附加信息,如哪种浏览器正在发起请求,客户端可以接收哪些类型的数据,以及连接类型等。
通过这个例子可以看到,即使是简单地在浏览器中输入一个地址,背后也有一系列复杂的步骤和数据交换过程,以确保可以查看请求的网页。
总结
请求行和状态行:分别包含请求的基本信息和响应的基本信息。
请求头和响应头:包含元数据,描述请求和响应的细节。
请求体和响应体:包含实际的数据内容,根据请求或响应的类型不同而变化。
常见的 HTTP 方法
HTTP 方法(也称为动词)定义了客户端希望对资源执行的操作。笔者所应用的场景多数都是和后端交换一些json,所以多数使用GET和POST,这也是最常用的两种.以下是几种常见的 HTTP 方法:
GET:
用于从服务器请求资源。GET 请求应该是幂等的(对同一资源的多次请求不会产生不同的效果)。
POST:
用于向服务器提交数据,通常用于表单提交或上传文件。POST 请求可以改变服务器上的资源状态。
PUT:
用于更新服务器上的资源,通常是替换资源的全部内容。PUT 请求应该是幂等的。
DELETE:
用于删除服务器上的资源。DELETE 请求应该是幂等的。
HEAD:
与 GET 类似,但服务器只返回响应头,不返回响应体。通常用于检查资源的元数据(如大小、类型)。
OPTIONS:
用于获取服务器支持的 HTTP 方法和其他通信选项。
方法论
UnityWebRequest 的不同构造函数及其使用示例:
1. 使用默认构造函数
UnityWebRequest request = new UnityWebRequest();
2. 使用 URL 作为字符串
string url = "http://example.com";
UnityWebRequest request = new UnityWebRequest(url);
3. 使用 Uri 对象
Uri uri = new Uri("http://example.com");
UnityWebRequest request = new UnityWebRequest(uri);
4. 使用 Uri 对象和 HTTP 方法
Uri uri = new Uri("http://example.com");
UnityWebRequest request = new UnityWebRequest(uri, "POST");
5. 使用 Uri 对象、HTTP 方法、下载处理器和上传处理器
Uri uri = new Uri("http://example.com");
DownloadHandler downloadHandler = new DownloadHandlerBuffer();
UploadHandler uploadHandler = new UploadHandlerRaw(new byte[] { });
UnityWebRequest request = new UnityWebRequest(uri, "POST", downloadHandler, uploadHandler);
总结
URL 是 Uri 的一种特定形式,包含访问资源的方法。
Uri 是一个更广泛的标识符,可以表示任何资源。
UnityWebRequest 的构造函数可以接受 URL 字符串或 Uri 对象来指定请求目标。
在选择构造函数时,可以根据需要选择合适的参数类型和数量。
UnityWebRequest 作用
UnityWebRequest 作为一个工具,用于在 Unity 中实现和使用 HTTP 协议。HTTP(HyperText Transfer Protocol,超文本传输协议)是用于在万维网(WWW)上进行数据通信的协议。它是一种无状态、面向连接的协议,基于请求-响应模式。
使用 UnityWebRequest 执行上述 HTTP 方法的示例:
GET 请求
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
public class GetRequestExample : MonoBehaviour
{
private void Start()
{
StartCoroutine(GetRequest("https://jsonplaceholder.typicode.com/posts/1"));
}
IEnumerator GetRequest(string uri)
{
//这里使用静态Get方法快速生成了一个可以发生GET请求的UnityWebRequest实例
using (UnityWebRequest webRequest = UnityWebRequest.Get(uri))
{ //发送网络请求,使用协程等待该异步操作
yield return webRequest.SendWebRequest();
if (webRequest.result == UnityWebRequest.Result.Success)
Debug.Log(webRequest.downloadHandler.text);
else Debug.LogError(webRequest.error);
}
}/*为什么使用协程呢?因为网络请求要花费较长的时间,
协程的异步操作可以避免卡死主线程
为什么使用using?因为先使用Get方法
生成了一个可以发生网络请求的UnityWebRequest 实例,
这是一个需要被释放的资源,使用using可以优雅的
在花括号结尾自动释放资源,不需要显示释放,当然也可以
不使用using,那么需要显示调用Dispose释放资源*/
}
UnityWebRequest.Result 枚举值
讲一下出现的这个枚举值,这个是较新版本的判断方式
InProgress
表示请求仍在进行中。通常在开始请求后到请求完成前的这段时间内会出现这个状态。
Success
表示请求成功完成,没有发生错误,服务器返回了有效的响应。
ConnectionError
表示请求过程中发生了连接错误,通常是因为网络问题或无法与服务器建立连接。
ProtocolError
表示请求成功发送到服务器,但服务器返回了错误响应,例如 404 Not Found 或 500 Internal Server Error。
DataProcessingError
表示请求完成,但在处理服务器返回的数据时发生了错误,例如无法解析 JSON 响应。
POST 请求
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
using System.Text;
public class PostRequestExample : MonoBehaviour
{
private void Start()
{
StartCoroutine(PostRequest("https://jsonplaceholder.typicode.com/posts", "
{\"title\":\"foo\",\"body\":\"bar\",\"userId\":1}"));
//与GET不同,这里上传了一个json给后端使用
}
IEnumerator PostRequest(string uri, string jsonData)
{
var request = new UnityWebRequest(uri, "POST");//构造POST对象
byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonData);//将json数据转为二进制
//这里讲下updownloadHandler属性,UnityWebRequest实例管理着
//该属性和downloadHandler属性,这两个属性来进行上传和下载数据
//使用低级方法配置UnityWebRequest实例需要自己对这两个属性赋值
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
request.downloadHandler = new DownloadHandlerBuffer();
//发生请求头,这里只处理了媒体类型Content-Type,设置为application/json
request.SetRequestHeader("Content-Type", "application/json");
/*这里和GET还有很大不同,上面直接调用一个Get()的静态方法返回一个可以直接使用的对象,
这里我将其称为一种高级方法,就是Unity官方进行了封装,而POST例子中这样,
自己配置各种属性,这里称为低级方法,低级方法相对高级方法有更大的自由度,
开发者可以掌握更多的细节,当然POST对应的高级方法就是.POST()方法*/
yield return request.SendWebRequest();
/*注意,这里就是发生网络请求,在发生网络求请后不允许对UnityWebRequest再进行配置,
也就是说所有的配置都需要在发送请求之前.*/
if (request.result == UnityWebRequest.Result.ConnectionError || request.result == UnityWebRequest.Result.ProtocolError)
{
Debug.LogError(request.error);
}
else
{
Debug.Log(request.downloadHandler.text);
}
}
}
总结
HTTP 协议 是一种应用层协议,用于在客户端和服务器之间传输数据。
HTTP 方法 定义了对资源的操作,如 GET、POST、PUT、DELETE 等。
UnityWebRequest 实现了 HTTP 协议,提供了发送和处理 HTTP 请求的功能。
Content-Type
这里还要再讲解一下HTTP中请求头的Content-Type,主要是在POST或者PUT中应用,因为GET不需要请求头,GET仅请求数据并不提交数据或者仅在请求行提供简单数据(且是以明文形式),举个例子用户登录需要输入账号密码,这通常需要被设计成一个POST请求,除了请求地址我们还要加上信息发送给后端,我们需要和后端约定使用什么格式的媒体类型,方便后端解析.
下面深入介绍下媒体类型
在HTTP请求中,Content-Type 是一个非常重要的HTTP头部(header),它告诉服务器发送的数据是什么格式。通过这个头部,服务器可以理解如何解析接收到的数据内容。Content-Type头部的主要功能是指明HTTP消息体中内容的类型。这是通过指定MIME类型来实现的,MIME类型通常包含两部分:一种类型和一个子类型,用斜线/分隔。例如,在application/json中,application是类型,json是子类型。
1. Content-Type 的定义
Content-Type 是一个标准的HTTP头部,用于定义HTTP请求或响应中的媒体类型(即数据格式)。这个头部告诉服务器和客户端实体体(entity body)的媒体类型是什么。例如,当您向服务器发送数据时,Content-Type 告诉服务器怎样解析这些数据。
2. 常见的媒体类型
application/json:表示发送或接收的内容格式是JSON(JavaScript Object Notation)。JSON是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。
application/xml 或 text/xml:
用途:用于传输XML格式的数据。虽然JSON在Web应用中更常用,XML仍广泛用于支持遗留系统的接口或特定的行业标准。
text/html:表示HTML格式,通常在浏览器中请求网页时使用。
application/x-www-form-urlencoded:通常在提交HTML表单时使用。表单数据会编码为键值对,类似于URL查询字符串。
multipart/form-data:一种用于HTML表单的编码类型,允许上传文件。
text/plain:纯文本格式,没有特定格式。
3. 在UnityWebRequest中设置Content-Type
当使用 UnityWebRequest 创建请求时,尤其是在发送POST或PUT请求时(这些请求通常会包含数据),正确设置Content-Type是非常重要的,因为它决定了数据如何被服务器解释。以下是设置 Content-Type 的代码示例:
using (UnityWebRequest request = new UnityWebRequest(url, "POST"))
{
string jsonData = JsonConvert.SerializeObject(someObject);
byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonData);
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
}
- 为什么选择 application/json,
选择 application/json 作为 Content-Type:
通用性:JSON格式在现代网络应用中广泛使用,特别是在REST API中,因为它提供了一个简洁且高效的方式来交换数据。
易于处理:大多数编程语言都有解析和生成JSON数据的库,如JavaScript, Python, C#, Java等。
结构化数据:JSON支持复杂的数据结构,如嵌套对象和数组,这对于表达复杂的数据非常有用。
通过设置 Content-Type 为 application/json,确保数据以JSON格式发送,并且期望服务器按JSON格式处理接收到的数据。这样的明确声明有助于避免数据解析错误和相关的问题。
具体选择什么类型需要和后端约定,application/x-www-form-urlencoded也是十分常用的格式,且如果直接使用静态Post方法构造一个实例,不显式配置格式默认就是使用application/x-www-form-urlencoded,且在Unity中存在WWWForm类帮我们轻松处理信息而不需要我们关注细节.
选择合适的Content-Type取决于想如何格式化数据以及接收方预期如何处理这些数据。选择正确的Content-Type可以确保数据正确地被接收和解析,从而避免数据格式错误、解析错误和相关的通信问题。
- 使用SetRequestHeader设置任何有效的HTTP头部
SetRequestHeader方法允许你自定义请求的HTTP头部信息。这些头部信息用于向服务器提供额外的指令或信息,如内容类型、认证信息、接受类型等。
常见的HTTP请求头部
Content-Type:
描述了请求正文的媒体类型(MIME类型),如application/json、multipart/form-data等。
Authorization:
用于支持HTTP认证机制,如Bearer tokens、Basic Auth等。
示例:request.SetRequestHeader("Authorization", "Bearer your_access_token_here");
User-Agent:
标识发出请求的浏览器或其他客户端应用的类型和版本。
示例:request.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
Accept:
指定客户端能够接收的内容类型,如application/json、text/html等。
示例:request.SetRequestHeader("Accept", "application/json");
Cookie:
发送存储在用户本地的Cookie到服务器。
示例:request.SetRequestHeader("Cookie", "username=JohnDoe");
Referer:
表示请求发起源的地址,有时用于防止 CSRF 攻击或日志记录。
示例:request.SetRequestHeader("Referer", "http://example.com");
设置多个HTTP头部的示例:
using UnityEngine.Networking;
UnityWebRequest request = new UnityWebRequest("http://example.com/api/data", "POST");
// 设置Content-Type头部
request.SetRequestHeader("Content-Type", "application/json");
// 设置Authorization头部
request.SetRequestHeader("Authorization", "Bearer your_access_token_here");
// 设置Accept头部
request.SetRequestHeader("Accept", "application/json");
// 确保添加对应的上传和下载处理器
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader是一个灵活的方法,允许根据需求设置各种HTTP头部。这些头部信息对于控制HTTP请求和响应的处理具有重要作用.
封装GET方法和POST方法
这里我使用了协程封装,但是使用协程有好有坏,好处是Unity官方实现,集成好并且简单,缺点是一切异步只能在内部处理,不能像使用async和await一样有返回值,不能使用try/catch,所以这里传递一个委托来回调处理返回结果,这样提高了阅读成本
public IEnumerator PostRequestAboutJsonCoroutine(string uri, Action<string>
callback, WWWForm formData, int timeout = 0)
{
var getWebRequest = UnityWebRequest.Post(uri, formData);
getWebRequest.timeout = timeout;
yield return getWebRequest.SendWebRequest();
#if UNITY_2020_1_OR_NEWER
if (getWebRequest.result == UnityWebRequest.Result.Success)
{
#else
if (!unityWebRequest.isNetworkError && !unityWebRequest.isHttpError)
{
#endif
if (!getWebRequest.downloadHandler.text.IsNullOrEmpty())
callback(getWebRequest.downloadHandler.text);
else
Debug.Log("json error");
}
}
public IEnumerator GetRequestAboutJsonCoroutine(string uri, Action<string>
callback, int timeout = 0)
{
var getWebRequest = UnityWebRequest.Get(uri);
getWebRequest.timeout = timeout;
yield return getWebRequest.SendWebRequest();
#if UNITY_2020_1_OR_NEWER
if (getWebRequest.result == UnityWebRequest.Result.Success)
{
#else
if (!unityWebRequest.isNetworkError && !unityWebRequest.isHttpError)
{
#endif
if (!getWebRequest.downloadHandler.text.IsNullOrEmpty())
callback(getWebRequest.downloadHandler.text);
else
Debug.Log("json error");
}
}
所以我这里提供一个使用UniTask库来处理网络请求并将GET和POST都集成到该方法中,第二个参数建议使用UnityWebRequest.kHttpVerbGET或kHttpVerbPOST避免出错
public async UniTask<T> WebRequestAboutJsonAsync<T>(string uri, string type, int
timeout = 0,WWWForm formData = null)
{
UnityWebRequest unityWebRequest = new UnityWebRequest(uri);
unityWebRequest.method = type.ToString();
var cts = new CancellationTokenSource();
cts.CancelAfterSlim(TimeSpan.FromSeconds(timeout));
try
{
switch (type)
{
case UnityWebRequest.kHttpVerbGET:
return await WebRequestAboutJsonBaseAsync<T>(unityWebRequest, cts);
case UnityWebRequest.kHttpVerbPOST:
if (formData != null)
{
var bodyRaw = formData.data;
unityWebRequest.uploadHandler = newUploadHandlerRaw(bodyRaw);
foreach (var header in formData.headers)
{
unityWebRequest.SetRequestHeader(header.Key,header.Value);
}
//unityWebRequest.SetRequestHeader("Content-Type",
//"application/x-www-form-urlencoded");
return await WebRequestAboutJsonBaseAsync<T>(unityWebRequest,cts);
}
else
{
Debug.LogError($"<color=red><b><size=15>未传入有效的POST对象</size></b></color>");
return default(T);
}
default:
return default(T);
}
}
catch (OperationCanceledException ex)
{
if (ex.CancellationToken == cts.Token)
{
Debug.Log($"<color=red><b><size=15>Timeout</size></b></color>");
//这里有点奇怪,使用.Error会卡死程序
}
return default(T);
}
catch (System.Exception e)
{
Debug.LogError($"<color=red><b><size=15>{e.Message}</size></b></color>");
return default(T);
}
finally
{
unityWebRequest.Dispose();
cts.Dispose();
}
}
private async UniTask<T> WebRequestAboutJsonBaseAsync<T>(UnityWebRequest
unityWebRequest,CancellationTokenSource cts)
{
unityWebRequest.downloadHandler = new DownloadHandlerBuffer();
await unityWebRequest.SendWebRequest().WithCancellation(cts.Token);
#if UNITY_2020_1_OR_NEWER
if (unityWebRequest.result == UnityWebRequest.Result.Success)
{
#else
if (!unityWebRequest.isNetworkError && !unityWebRequest.isHttpError)
{
#endif
string responseText = unityWebRequest.downloadHandler.text;
return JsonConvert.DeserializeObject<T>(responseText);
}
else
{
Debug.LogError("网络错误: " + unityWebRequest.error);
return default(T);
}
}