Unity UnityWebRequest替换WWW(转载)

博客原文

Chinar blog www.chinar.xin

UnityWebRequest


本文提供全流程,中文翻译。

Chinar 的初衷是将一种简单的生活方式带给世人

使有限时间 具备无限可能

Chinar ―― 心分享、心创新!

助力快速完成 UnityWebRequest 的认识和使用

为新手节省宝贵的时间,避免采坑!


文章目录

  • 1
    • introduce ―― 介绍
  • 2
    • Method ―― 常用方法
      • 2.1 Constructor ―― 构造函数
      • 2.2 SendWebRequest ―― SendWebRequest方法
      • 2.3 Get ―― Get方法
      • 2.4 Post ―― Post方法
      • 2.5 Put ―― Put方法
      • 2.6 Abort ―― Abort方法
      • 2.7 Head ―― Head方法
      • 2.8 GetResponseHeader ―― GetResponseHeader方法
  • 3
    • attribute ―― 常用属性
      • 3.1 timeout ―― 超时
      • 3.2 isHttpError ―― http错误
      • 3.3 isNetworkError ―― 系统错误
      • 3.4 error ―― 错误
      • 3.5 downloadProgress ―― 下载进度
      • 3.6 uploadProgress ―― 上传进度
      • 3.7 isDone ―― 是否完成
  • 4
    • Example ―― 例子
      • 4.1 DownloadTexture ―― 下载图片
      • 4.2 DownloadFile ―― 下载文件
      • 4.3 Breakpoint Resume ―― 断点续传
  • 5
    • Project ―― 项目文件
  • 支持
    • May Be ―― 开发者,总有一天要做的事!


全文高清图片,点击即可放大观看 (很多人竟然不知道)


1

introduce ―― 介绍

UnityWebReqest 是Unity提供的一种新的网络请求方式
UnityWebReqest 实例化出来的对象将用于与Web服务器的通信
它将替代已被弃用的 WWW 网络请求方式

UnityWebRequest 由三个元素组成:

1.UploadHandler 处理数据将数据上传到服务器的对象
2.DownloadHandler 从服务器下载数据的对象
3.UnityWebRequest 负责与HTTP 通信并管理上面两个对象。

UnityWebRequest支持与上传,下载及断点续传功能,十分好用


2

Method ―― 常用方法

几个常用方法:

方法 作用
SendWebRequest() 开始与远程服务器通信。在调用此方法之后,有必要的话UnityWebRequest将执行DNS解析,将HTTP请求发送到目标URL的远程服务器并处理服务器的响应。
Get(url) 创建一个http为传入url的 UnityWebRequest 对象
Post(url) 向Web服务器发送表单信息
Put(url) 将数据上传到 Web 服务器
Abort() 直接结束联网
Head() 创建一个为传输HTTP头请求的 UnityWebRequest 对象
GetResponseHeader() 返回一个字典,内容为在最新的 HTTP 响应中收到的所有响应头

除了构造函数外,UnityWebReqest提供了几个公有的静态方法,我们可以使用它们进行 UnityWebReqest 构造

2.1 Constructor ―― 构造函数


Unity几乎将所有我们需要初始化的数据,封装为构造函数,并进行重载。
我们通过这些构造函数可以方便的初始化一个UnityWebReqest对象。

首先看其中两个构造函数:
public UnityWebRequest(); public UnityWebRequest(Uri uri);

参数Uri其实就是一个Url对象
同样,如果嫌麻烦,可以将uri直接改为 string传入进去
举个例子
我们创建一个脚本,在Start方法中写下如下代码

    void Start()
    {
        //首先初始化一个 UnityWebRequest 对象,将我们想连接的地址传入
        //第一种:直接传string
        UnityWebRequest uwr  = new UnityWebRequest("http://www.baidu.com");
        //第二种:Uri
        Uri             uri  = new Uri("http://www.baidu.com"); //Uri 是 System 命名空间下的一个类,注意引用该命名空间
        UnityWebRequest uwr2 = new UnityWebRequest(uri);        //创建UnityWebRequest对象
        print(uwr.url);                                         //打印一下url属性
        
    }

下面看另外两种构造函数
public UnityWebRequest(Uri uri,string method);
public UnityWebRequest(Uri uri,string method,Networking.DownloadHandler downloadHandler, Networking.UploadHandler uploadHandler);

解释一下参数的含义:

参数 含义
method 相当于方法名,只有GET,POST,PUT,HEAD四种,默认为GET,一旦调用SendWebRequest(),就无法更改
downloadHandler 下载数据的委托方法
uploadHandler 上传数据的委托方法

运行查看一下打印结果
在这里插入图片描述
看到 UnityWebReqest 该对象,已经将该地址保存

那如何向一个URL地址对应的服务器发送请求呢?

我们来使用协程,完成网络请求,并等待返回信息

SendWebRequest 会按照我们指定的地址,去请求并处理远程服务器的响应

Uri 是 System 命名空间下的一个类,注意引用该命名空间!

using System.Collections;
using UnityEngine;
using UnityEngine.Networking;

/// 
/// 网络请求测试
/// 
public class ChinarWebRequest : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(SendRequest());
    }


    /// 
    /// 开启一个协程,发送请求
    /// 
    /// 
    IEnumerator SendRequest()
    {
        Uri             uri = new Uri("http://www.baidu.com"); //Uri 是 System 命名空间下的一个类,注意引用该命名空间
        UnityWebRequest uwr = new UnityWebRequest(uri);        //创建UnityWebRequest对象
        yield return uwr.SendWebRequest();                     //等待返回请求的信息
        if (uwr.isHttpError || uwr.isNetworkError)             //如果其 请求失败,或是 网络错误
        {
            print(uwr.error); //打印错误原因
        }
        else //请求成功
        {
            print("请求成功");
        }
    }
}

运行看打印结果
在这里插入图片描述
当我们断开网络后:连接失败,不能解析远程主机
在这里插入图片描述


2.2 SendWebRequest ―― SendWebRequest方法

SendWebRequest这种方法只能在任何给定的 UnityWebRequest 对象中调用一次。一旦这个方法被调用,您就不能更改 UnityWebRequest 的任何属性。这个方法返回一个
WebRequestAsyncOperation (异步操作对象)。在一个协程中产生
WebRequestAsyncOperation(异步操作对象)会导致协程暂停,直到 UnityWebRequest
遇到系统错误或完成通信


2.3 Get ―― Get方法

Get 方法为创建一个http为传入url的 UnityWebReqest 对象
将代码中new UnityWebRequest 改为UnityWebRequest.Get()

举个例子

using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;


/// 
/// 网络请求测试
/// 
public class ChinarWebRequest : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(SendRequest());
    }


    /// 
    /// 开启一个协程,发送请求
    /// 
    /// 
    IEnumerator SendRequest()

    {
        Uri             uri = new Uri("http://www.baidu.com"); //Uri 是 System 命名空间下的一个类,注意引用该命名空间
        UnityWebRequest uwr = new UnityWebRequest(uri);        //创建UnityWebRequest对象
        yield return uwr.SendWebRequest();                     //等待返回请求的信息
        if (uwr.isHttpError || uwr.isNetworkError)             //如果其 请求失败,或是 网络错误
        {
            print(uwr.error); //打印错误原因
        }
        else //请求成功
        {
            print("UnityWebRequest:请求成功");
            print(uwr.downloadedBytes);
        }

        print("--------------------");
        yield return SendRequest1(); //等待返回请求的信息
    }


    /// 
    /// 开启一个协程,发送请求
    /// 
    /// 
    IEnumerator SendRequest1()
    {
        UnityWebRequest uwr = UnityWebRequest.Get("http://www.baidu.com"); //创建UnityWebRequest对象
        yield return uwr.SendWebRequest();                                 //等待返回请求的信息
        if (uwr.isHttpError || uwr.isNetworkError)                         //如果其 请求失败,或是 网络错误
        {
            print(uwr.error); //打印错误原因
        }
        else //请求成功
        {
            print("Get:请求成功");
            print(uwr.downloadedBytes);
        }
    }
}

运行看打印结果
在这里插入图片描述

使用 Get 方法创建 UnityWebRequest 对象与使用只传入 Url 构造函数的区别: 使用Get方法时会默认的给对象创建一个DownloadHander与UploadHandler对象。 而使用只传入 Url
的构造函数创建只是给 UnityWebRequest 对象赋值了一个 Url,调用downloadHander.txt时会报错


2.4 Post ―― Post方法

Post方法将一个表上传到远程的服务器,一般来说我们登陆某个网站的时候会用到这个方法,我们的账号密码会以一个表单的形式传过去,表单是什么?请参考WWWForm类

using System.Collections;
using UnityEngine;
using UnityEngine.Networking;

/// 
/// 网络请求测试
/// 
public class ChinarWebRequest : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(Post());
    }
    /// 
    /// 开启一个协程,发送请求
    /// 
    /// 
    IEnumerator Post()
    {
        WWWForm form = new WWWForm();
        //键值对
        form.AddField("key",  "value");
        form.AddField("name", "Chinar");
        //请求链接,并将form对象发送到远程服务器
        UnityWebRequest webRequest = UnityWebRequest.Post("http://www.baidu.com", form);

        yield return webRequest.SendWebRequest();
        if (webRequest.isHttpError || webRequest.isNetworkError)
        {
            Debug.Log(webRequest.error);
        }
        else
        {
            Debug.Log("发送成功"); 
        }
    }
}

运行查看结果
在这里插入图片描述


2.5 Put ―― Put方法

Put方法将数据发送到远程的服务器

using System.Collections;
using UnityEngine;
using UnityEngine.Networking;

/// 
/// 网络请求测试
/// 
public class ChinarWebRequest : MonoBehaviour
{
        void Start()
    {
        StartCoroutine(Upload());
    }
    /// 
    /// 开启协程
    /// 
    /// 
    IEnumerator Upload()
    {
        byte[] myData = System.Text.Encoding.UTF8.GetBytes("Chinar的测试数据");
        using (UnityWebRequest uwr = UnityWebRequest.Put("http://www.baidu.com", myData))
        {
            yield return uwr.SendWebRequest();

            if (uwr.isNetworkError || uwr.isHttpError)
            {
                Debug.Log(uwr.error);
            }
            else
            {
                Debug.Log("上传成功!");
            }
        }
    }
}

运行查看结果
在这里插入图片描述


2.6 Abort ―― Abort方法

Abort 方法会尽快结束联网,可以随时调用此方法。 如果 UnityWebRequest 尚未完成,那么 UnityWebRequest 将尽快停止上传或下载数据。 中止的 UnityWebRequests 被认为遇到了系统错误。
isNetworkErrorisHttpError属性将返回true,error属性将为 “User Aborted”

如果在调用 SendWebRequest 之前调用此方法,则 UnityWebRequest 将在调用SendWebRequest 后立即中止联网。在此 UnityWebRequest 遇到其他错误或已成功完成与远程服务器的通信后,那么此方法调用无效。


2.7 Head ―― Head方法

Head方法与Get方法用法一致,都是传入一个Url

/// 
    /// 开启一个协程,发送请求
    /// 
    /// 
    IEnumerator SendRequest1()
    {
        UnityWebRequest uwr = UnityWebRequest.Head("http://www.chinar.xin/chinarweb/WebRequest/Get/00-效果.mp4"); //创建UnityWebRequest对象
        yield return uwr.SendWebRequest();                                 //等待返回请求的信息
        if (uwr.isHttpError || uwr.isNetworkError)                         //如果其 请求失败,或是 网络错误
        {
            print(uwr.error); //打印错误原因
        }
        else //请求成功
        {
            print("Head:请求成功");
        }
    }

2.8 GetResponseHeader ―― GetResponseHeader方法

GetResponseHeader方法可以用来获取请求文件的长度 传入参数 "Content-Length" 字符串,表示获取文件内容长度。

/// 
    /// 开启一个协程,发送请求
    /// 
    /// 
    IEnumerator SendRequest1()
    {
        UnityWebRequest uwr = UnityWebRequest.Head("http://www.chinar.xin/chinarweb/WebRequest/Get/00-效果.mp4"); //创建UnityWebRequest对象
        yield return uwr.SendWebRequest();                                 //等待返回请求的信息
        if (uwr.isHttpError || uwr.isNetworkError)                         //如果其 请求失败,或是 网络错误
        {
            print(uwr.error); //打印错误原因
        }
        else //请求成功
        {
        long   totalLength = long.Parse(huwr.GetResponseHeader("Content-Length")); //首先拿到文件的全部长度
            print("totalLength");//打印文件长度
        }
    }


3

attribute ―― 常用属性

几个常用属性:

属性 类型 含义
timeout int 等待时间(秒)超过此数值是 UnityWebReqest 的尝试连接将终止
isHttpError bool http响应出现出现错误
isNetworkError bool 系统出现错误
error string 描述 UnityWebRequest 对象在处理HTTP请求或响应时遇到的任何系统错误
downloadProgress float 表示从服务器下载数据的进度
uploadProgress float 表示从服务器上传数据的进度
isDone bool 是否完成与远程服务器的通信

3.1 timeout ―― 超时

UnityWebRequest 对象将在 timeout 秒后尝试终止 发生超时时,错误返回“Request
timeout”(请求超时)。timeout 设置为0且此属性默认为时,不应用超时

设置超时后可能使网址重定向,这会导致响应时间更长


3.2 isHttpError ―― http错误

属性为只读 表示HTTP响应是否出现错误

只有响应代码大于400的时候才会返回 True,比如常见的404


3.3 isNetworkError ―― 系统错误

UnityWebRequest 对象遇到系统错误后返回true
系统错误的示例包括无法解析DNS条目,套接字错误或超出重定向限制。当此属性返回true时,error 属性将包含描述错误的原因。

错误类型的服务器返回代码(例如404 / Not Found和500 / Internal Server Error)反映在isHttpError 属性中


3.4 error ―― 错误

字符串类型,只读,返回UnityWebRequest对象在处理HTTP请求或响应时遇到的任何系统错误 如果
UnityWebRequest 未遇到系统错误,则此属性将返回
null。系统错误的示例包括套接字错误,解析DNS条目的错误或超出重定向限制

来自服务器的错误类型返回码(例如404 / File Not Found或500 / Internal Server Error)不被视为系统错误。


3.5 downloadProgress ―― 下载进度

float类型,只读,返回一个0.0~1.0的值,指示从服务器下载数据的进度。
如果 UnityWebRequest 已成功完成链接或遇到系统错误,则此属性将返回1.
如果UnityWebRequest 仍在与远程服务器通信,并且downloadHandler 为null,则此属性将返回0.5。
如果尚未调用 SendWebRequest(),则此属性将返回-1。

仅当服务器的响应包含 Content-Length 标头并且 UnityWebRequest 具有附加到 downloadHandler 属性的 DownloadHandler对象 时,此属性才有效。


3.6 uploadProgress ―― 上传进度

与downloadProgress相同,float类型,只读,返回一个0.0~1.0的值,指示从服务器上传数据的进度。
如果 UnityWebRequest 已成功完成链接或遇到系统错误,则此属性将返回1.
如果UnityWebRequest 仍在与远程服务器通信,并且downloadHandler 为null,则此属性将返回0.5。
如果尚未调用 SendWebRequest(),则此属性将返回-1。


3.7 isDone ―― 是否完成

只读, UnityWebRequest 完成与远程服务器通信后或遇到系统错误时返回true 。
DownloadHandler (如果有)的所有下载处理将在此属性返回true之前完成。我们可以用它来判断下载是否完成


4

Example ―― 例子

例子中需要一个Web服务器,没有的同学不要担心,Chinar为大家提供一个测试的Web服务器,地址在例子中

4.1 DownloadTexture ―― 下载图片

我们将下面的图片放到设置好的Web服务器目录下面,命名为Chinar.png
在这里插入图片描述

创建一个脚本:

using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;


/// 
/// 02-ChinarDownLoadTexture:
/// 用来测试服务器上传
/// 
public class ChinarDownLoadTexture : MonoBehaviour
{
    public string url = "http://www.chinar.xin/chinarweb/WebRequest/Get/Chinar.png";

    public Image downImage;


    void Start()
    {
        StartCoroutine(GetTexture(url));
    }


    /// 
    /// 请求图片
    /// 
    /// 图片地址,like 'http://www.chinar.xin/chinarweb/WebRequest/Get/Chinar.png '
    IEnumerator GetTexture(string url)
    {
        UnityWebRequest        uwr             = UnityWebRequest.Get(url);
        DownloadHandlerTexture downloadTexture = new DownloadHandlerTexture(true);
        uwr.downloadHandler = downloadTexture;
        yield return uwr.SendWebRequest();
        if (uwr.isNetworkError || uwr.isHttpError)
        {
            print(uwr.error);
        }
        else
        {
            Texture2D t = downloadTexture.texture;
            downImage.sprite = Sprite.Create(t, new Rect(0, 0, t.width, t.height), Vector2.one);
        }
    }
        
}

举个例子
在这里插入图片描述


4.2 DownloadFile ―― 下载文件

首先制作我们的UI界面:
在这里插入图片描述
开始写代码

using System;
using System.Collections;
using System.IO;
using UnityEditor;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;


/// 
/// 
/// 
public class ChinarDownLoadFile : MonoBehaviour
{
    public  Slider ProgressBar; //进度条
    public  Text   SliderValue; //滑动条值
    private Button startBtn;    //开始按钮


    void Start()
    {
        //初始化进度条和文本框
        ProgressBar.value = 0;
        SliderValue.text  = "0.0%";
        startBtn          = GameObject.Find("Start Button").GetComponent<Button>();
        startBtn.onClick.AddListener(OnClickStartDownload);
    }


    /// 
    /// 回调函数:开始下载
    /// 
    public void OnClickStartDownload()
    {
        StartCoroutine(DownloadFile());
    }


    /// 
    /// 协程:下载文件
    /// 
    IEnumerator DownloadFile()
    {
        UnityWebRequest uwr = UnityWebRequest.Get("http://www.chinar.xin/chinarweb/WebRequest/Get/00-效果.mp4"); //创建UnityWebRequest对象,将Url传入
        uwr.SendWebRequest();                                                                                  //开始请求
        if (uwr.isNetworkError || uwr.isHttpError)                                                             //如果出错
        {
            Debug.Log(uwr.error); //输出 错误信息
        }
        else
        {
            while (!uwr.isDone) //只要下载没有完成,一直执行此循环
            {
                ProgressBar.value = uwr.downloadProgress; //展示下载进度
                SliderValue.text  = Math.Floor(uwr.downloadProgress * 100) + "%";
                yield return 0;
            }

            if (uwr.isDone) //如果下载完成了
            {
                print("完成");
                ProgressBar.value = 1; //改变Slider的值
                SliderValue.text  = 100 + "%";
            }

            byte[] results = uwr.downloadHandler.data;
            CreateFile(Application.streamingAssetsPath + "/MP4" + "/00-效果.mp4", results, uwr.downloadHandler.data.Length);
            AssetDatabase.Refresh(); //刷新一下
        }
    }


    /// 
    /// 这是一个创建文件的方法
    /// 
    /// 保存文件的路径
    /// 文件的字节数组
    /// 数据长度
    void CreateFile(string path, byte[] bytes, int length)
    {
        Stream   sw;
        FileInfo file = new FileInfo(path);
        if (!file.Exists)
        {
            sw = file.Create();
        }
        else
        {
            return;
        }

        sw.Write(bytes, 0, length);
        sw.Close();
        sw.Dispose();
    }
}


举个例子
在这里插入图片描述


4.3 Breakpoint Resume ―― 断点续传

首先看UI界面,界面仅增加了一个暂停下载的按钮
在这里插入图片描述
创建脚本并写下如下代码:

// ========================================================
// 描述:基于UnityWebReqest的断点续传
// 作者:Chinar 
// 创建时间:2019-04-22 17:59:20
// 版 本:1.0
// ========================================================

using System;
using System.Collections;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;

public class ChinarBreakpointRenewal : MonoBehaviour
{
    private bool _isStop;       //是否暂停

    public  Slider ProgressBar; //进度条
    public  Text   SliderValue; //滑动条值
    private Button startBtn;    //开始按钮
    private Button pauseBtn;    //暂停按钮
    public  string Url = "http://www.chinar.xin/chinarweb/WebRequest/Get/00-效果.mp4";

    /// 
    /// 初始化UI界面及给按钮绑定方法
    /// 
    void Start()
    {
        //初始化进度条和文本框
        ProgressBar.value = 0;
        SliderValue.text  = "0.0%";
        startBtn          = GameObject.Find("Start Button").GetComponent<Button>();
        startBtn.onClick.AddListener(OnClickStartDownload);
        pauseBtn = GameObject.Find("Pause Button").GetComponent<Button>();
        pauseBtn.onClick.AddListener(OnClickStop);
    }


    /// 
    /// 回调函数:开始下载
    /// 
    public void OnClickStartDownload()
    {
        StartCoroutine(DownloadFile(Url, Application.streamingAssetsPath + "/MP4/00-效果.mp4", CallBack));
    }


    /// 
    /// 协程:下载文件
    /// 
    /// 请求的Web地址
    /// 文件保存路径
    /// 下载完成的回调函数
    /// 
    IEnumerator DownloadFile(string url, string filePath, Action callBack)
    {
        UnityWebRequest huwr = UnityWebRequest.Head(url); //Head方法可以获取到文件的全部长度
        yield return huwr.SendWebRequest();
        if (huwr.isNetworkError || huwr.isHttpError) //如果出错
        {
            Debug.Log(huwr.error); //输出 错误信息
        }
        else
        {
            long   totalLength = long.Parse(huwr.GetResponseHeader("Content-Length")); //首先拿到文件的全部长度
            string dirPath     = Path.GetDirectoryName(filePath);
            if (!Directory.Exists(dirPath)) //判断路径是否存在
            {
                Directory.CreateDirectory(dirPath);
            }

            //创建一个文件流,指定路径为filePath,模式为打开或创建,访问为写入
            using (FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write))
            {
                long nowFileLength = fs.Length; //当前文件长度
                print(fs.Length);
                if (nowFileLength < totalLength)
                {
                    print("还没下载完成");
                    fs.Seek(nowFileLength, SeekOrigin.Begin);       //从头开始索引,长度为当前文件长度
                    UnityWebRequest uwr = UnityWebRequest.Get(url); //创建UnityWebRequest对象,将Url传入
                    uwr.SetRequestHeader("Range", "bytes=" + nowFileLength + "-" + totalLength);
                    uwr.SendWebRequest();                      //开始请求
                    if (uwr.isNetworkError || uwr.isHttpError) //如果出错
                    {
                        Debug.Log(uwr.error); //输出 错误信息
                    }
                    else
                    {
                        long index = 0;     //从该索引处继续下载
                        while (!uwr.isDone) //只要下载没有完成,一直执行此循环
                        {
                            if (_isStop) break;
                            yield return null;
                            byte[] data = uwr.downloadHandler.data;
                            if (data != null)
                            {
                                long length = data.Length - index;
                                fs.Write(data, (int) index, (int) length); //写入文件
                                index             += length;
                                nowFileLength     += length;
                                ProgressBar.value =  (float) nowFileLength / totalLength;
                                SliderValue.text  =  Math.Floor((float) nowFileLength / totalLength * 100) + "%";
                                if (nowFileLength >= totalLength) //如果下载完成了
                                {
                                    ProgressBar.value = 1; //改变Slider的值
                                    SliderValue.text  = 100 + "%";
                                    callBack?.Invoke();
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    /// 
    /// 下载完成后的回调函数
    /// 
    void CallBack()
    {
        print("下载完成");
    }

    /// 
    /// 暂停下载
    /// 
    public void OnClickStop()
    {
        if (_isStop)
        {
            pauseBtn.GetComponentInChildren<Text>().text = "暂停下载";
            print("继续下载");
            _isStop = !_isStop;
            OnClickStartDownload();
        }
        else
        {
            pauseBtn.GetComponentInChildren<Text>().text = "继续下载";
            print("暂停下载");
            _isStop = !_isStop;
        }
    }
}

举个例子
在这里插入图片描述


5

Project ―― 项目文件



项目文件为 unitypackage 文件包:

下载导入 Unity 即可使用
举个例子


支持

May Be ―― 开发者,总有一天要做的事!


拥有自己的服务器,无需再找攻略

Chinar 提供一站式《零》基础教程

使有限时间 具备无限可能!

Chinar 知你所想,予你所求!( Chinar Blog )


Chinar

END

本博客为非营利性个人原创,除部分有明确署名的作品外,所刊登的所有作品的著作权均为本人所拥有,本人保留所有法定权利。违者必究

对于需要复制、转载、链接和传播博客文章或内容的,请及时和本博主进行联系,留言,Email: [email protected]

对于经本博主明确授权和许可使用文章及内容的,使用时请注明文章或内容出处并注明网址

                                

你可能感兴趣的:(Unity引擎)