以下所有代碼都是基于Unity2019.4.0f1版本進行開發
文章目錄
前言
一、網絡加載圖檔的兩種方式
二、Texture和Sprite的轉換
三、制作網絡加載圖檔單例類
四、網絡圖檔的緩存及本地存儲
五、圖檔完整性的驗證
結語
前言
在日常的項目制作當中,我們難免會涉及到使用到伺服器上面的圖檔資源,就會需要我們去進行加載、顯示等操作。但是在加載過程當中也會有意外的産生,導緻圖檔加載失敗,在未經處理的情況下,就會顯示一個大大紅問号,這會對使用者體驗産生一定的問題。沒關系,咱們一個一個的解決加載網絡圖檔的問題。
一、網絡加載圖檔的兩種方式
首先我們想要把伺服器的圖檔加載到本地,不可擷取的就是這個圖檔的URL,這一點我相信對于看到這篇文章的各位并不是問題,不知道怎麼擷取的可自行去網上随便找一個圖檔~,而後我們就需要對這個連接配接進行讀取,這裡我用到的是Unity的WWW類。但是現在Unity官方比較推薦的是使用UnityWebRequest來進行加載,我會将代碼都貼到下面,示例代碼如下:
// 使用 WWW 加載
public void downImageAction(string url)
{
StartCoroutine(downloadImage(url));
}
private IEnumerator downloadImage(string url)
{
WWW www = new WWW(url);
yield return www;
Texture2D texture = www.texture;
}
// 使用 UnityWebRequest 加載
public void downImageAction(string url)
{
Uri uri = new Uri(url);
StartCoroutine(downloadImage(uri));
}
private IEnumerator downloadImage(Uri uri)
{
UnityWebRequest unityWebRequest = UnityWebRequestTexture.GetTexture(uri);
DownloadHandlerTexture downloadHandlerTexture = new DownloadHandlerTexture(true);
unityWebRequest.downloadHandler = downloadHandlerTexture;
yield return unityWebRequest.SendWebRequest();
Texture2D texture = downloadHandlerTexture.texture;
}
通過上述代碼不論是哪一種方式,我們都可以通過URL擷取到了這個圖檔的Texture,而這兩種方式我再實際使用的過程當中并沒有感受到有什麼差別,目前來說大家可以随意選擇。
二、Texture和Sprite的轉換
言歸正傳,當我們擷取到了圖檔的Texture之後可以滿足一部分操作了,比如Material的貼圖、RawImage等可接收Texture的元件就實作了使用網絡圖檔資源了,但是如果要用到Image當中就需要将Texture轉換成Sprite後再對Image進行指派,轉換腳本如下:
public Sprite textureConvert(Texture2D texture)
{
texture.wrapMode = TextureWrapMode.Clamp;
texture.filterMode = FilterMode.Point;
Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);
return sprite;
}
到這裡基本上加載網絡圖檔的功能就已經實作了,可是這些腳本還是過于簡單,對于一些測試、或者是學習了解加載網絡資源來說是可以滿足了的,但我們不能滿足于此,在針對實際使用環境來進行進一步的制作:将加載腳本制作成單例執行個體、隊列加載及存儲、緩存至記憶體及本地、增加圖檔完整性的判斷。好了,廢話不多說我們直接上幹貨!
三、制作網絡加載圖檔單例類
對于單例模式我想我在這裡就不用多說些什麼,直接上代碼!
public class AsyncImageDownload : MonoBehaviour
{
private static AsyncImageDownload _instance;
public static AsyncImageDownload GetInstance()
{
return Instance;
}
public static AsyncImageDownload Instance
{
get
{
if (_instance == null)
{
GameObject obj = new GameObject("AsyncImageDownload");
_instance = obj.AddComponent<AsyncImageDownload>();
_instance.init();
}
return _instance;
}
}
}
用到單例之後,就不用擔心引用不到,或者是在物體上挂載太多元件的問題了,直接在腳本中調用就好了,接下來就可以制作緩存相關的腳本了。因為緩存跟隊列加載都屬于優化使用者體驗的問題,就放到一起說了。使用隊列是因為想把加載内容拆成X份,在設定的間隔時間内隻執行這一份,用來減少CPU的負擔,優化使用者體驗。而緩存則是避免了資源重複加載的問題。
四、網絡圖檔的緩存及本地存儲
緩存分為兩種存儲在記憶體中和儲存到本地磁盤當中,這兩種可以根據實際情況使用,也可以都使用,在記憶體當中進行緩存操作是為了避免相同的一份資源被多次從伺服器中拉取,在所需資源量比較少的情況下還不是很明顯,一旦需要加載的數量大了,我們的工程就會出現因為過多的加載網絡資源産生卡頓,是以相同的資源我們隻需要下載下傳一次就可以了。而儲存在本地磁盤的操作是進一步去優化了這一點,相同的資源我們隻進行一次下載下傳後,儲存到本地磁盤當中,等下一次啟動項目時則直接可以從磁盤當中讀取,避免了二次下載下傳的問題。而這樣做,需要考慮到磁盤空間是否足夠,在什麼時機進行儲存等等條件,我這裡把代碼貼出來你們自行取用~
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using UnityEngine.UI;
public class AsyncImageDownload : MonoBehaviour
{
private static AsyncImageDownload _instance;
public static AsyncImageDownload GetInstance()
{
return Instance;
}
public static AsyncImageDownload Instance
{
get
{
if (_instance == null)
{
GameObject obj = new GameObject("AsyncImageDownload");
_instance = obj.AddComponent<AsyncImageDownload>();
}
return _instance;
}
}
private Dictionary<string, Sprite> spriteDic;
private void Start()
{
InvokeRepeating("readLoadImageQueue", 0, 0.05f);
InvokeRepeating("readSaveImageQueue", 0, 1);
}
private void readLoadImageQueue()
{
AsyncImageQueue.readLoadImageQueue();
}
private void readSaveImageQueue()
{
AsyncImageQueue.readSaveImageQueue();
}
private void init()
{
if (!Directory.Exists(imageCacheFolderPath))
{
Directory.CreateDirectory(imageCacheFolderPath);
}
spriteDic = new Dictionary<string, Sprite>();
}
public void setAsyncImage(string url, Image image, bool isReload = false)
{
AsyncImageInfo asyncImageInfo = new AsyncImageInfo
{
asyncImageDownload = this,
URL = url,
image = image
};
if (!File.Exists(imageCacheFolderPath + url.GetHashCode() + ".png"))
{
if (Application.internetReachability == NetworkReachability.ReachableViaLocalAreaNetwork)
{
if (!spriteDic.ContainsKey(imageCacheFolderPath + url.GetHashCode()))
{
asyncImageInfo.type = AsyncImageType.net;
}
else
{
asyncImageInfo.type = AsyncImageType.local;
}
}
}
else
{
asyncImageInfo.type = AsyncImageType.local;
}
}
public void downloadImageAction(string url, Image image)
{
StartCoroutine(downloadImage(url, image));
}
public void localImageAction(string url, Image image)
{
StartCoroutine(loadLocalImage(url, image));
}
private IEnumerator downloadImage(string url, Image image)
{
WWW www = new WWW(url);
yield return www;
Texture2D texture = www.texture;
try
{
SaveImageInfo saveImageInfo = new SaveImageInfo
{
pngData = texture.EncodeToPNG(),
fileName = imageCacheFolderPath + url.GetHashCode() + ".png"
};
AsyncImageQueue.addSaveImageQueue(saveImageInfo);
}
catch
{
}
texture.wrapMode = TextureWrapMode.Clamp;
texture.filterMode = FilterMode.Point;
Sprite m_sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);
image.sprite = m_sprite;
if (!spriteDic.ContainsKey(imageCacheFolderPath + url.GetHashCode()))
{
spriteDic.Add(imageCacheFolderPath + url.GetHashCode(), m_sprite);
}
}
private IEnumerator loadLocalImage(string url, Image image)
{
if (!spriteDic.ContainsKey(imageCacheFolderPath + url.GetHashCode()))
{
string filePath = "file:///" + imageCacheFolderPath + url.GetHashCode() + ".png";
WWW www = new WWW(filePath);
yield return www;
Texture2D texture = www.texture;
texture.wrapMode = TextureWrapMode.Clamp;
texture.filterMode = FilterMode.Point;
Sprite m_sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);
image.sprite = m_sprite;
if (!spriteDic.ContainsKey(imageCacheFolderPath + url.GetHashCode()))
{
spriteDic.Add(imageCacheFolderPath + url.GetHashCode(), m_sprite);
}
}
else
{
image.sprite = spriteDic[imageCacheFolderPath + url.GetHashCode()];
}
}
private string imageCacheFolderPath
{
get { return Application.persistentDataPath + "/xxx/ImageCaChe/"; }
}
}
在上述腳本中,用到了一個隊列結構:
public class AsyncImageQueue
{
private static Queue loadImageQueue = new Queue();
private static Queue saveImageQueue = new Queue();
public static void addLoadImageQueue(AsyncImageInfo asyncImageInfo)
{
loadImageQueue.Enqueue(asyncImageInfo);
}
public static void readLoadImageQueue()
{
if (loadImageQueue.Count > 0)
{
AsyncImageInfo asyncImageInfo = (AsyncImageInfo) loadImageQueue.Dequeue();
if (asyncImageInfo.image)
{
switch (asyncImageInfo.type)
{
case AsyncImageType.local:
asyncImageInfo.asyncImageDownload.localImageAction(asyncImageInfo.URL, asyncImageInfo.image);
break;
case AsyncImageType.net:
asyncImageInfo.asyncImageDownload.downloadImageAction(asyncImageInfo.URL, asyncImageInfo.image);
break;
default:
break;
}
}
}
}
public static void addSaveImageQueue(SaveImageInfo saveImageInfo)
{
saveImageQueue.Enqueue(saveImageInfo);
}
public static void readSaveImageQueue()
{
if (saveImageQueue.Count > 0)
{
SaveImageInfo saveImageInfo = (SaveImageInfo) saveImageQueue.Dequeue();
File.WriteAllBytes(saveImageInfo.fileName, saveImageInfo.pngData);
}
}
}
以及隊列資訊和存儲資訊
using UnityEngine;
using UnityEngine.UI;
public class AsyncImageInfo
{
public AsyncImageDownload asyncImageDownload;
public string URL;
public Image image;
public AsyncImageType type;
}
public enum AsyncImageType
{
local,
net
}
public class SaveImageInfo
{
public byte[] pngData;
public string fileName;
}
至此,我們網絡圖檔加載已經緩存功能都已經完成了。下面我們來解決如何判斷圖檔完整性的問題,因為這一部分我個人涉獵的比較少,如果有哪些不對的地方,歡迎大家的指正。
五、圖檔完整性的驗證
簡單的思路是這樣的,根據相同類型的圖檔(jpg、png等),頭尾的兩位位元組是相同的,以此我們可以來判斷這個圖檔的類型以及是否下載下傳完成。
根據查詢和實驗可以得到:
類型 | 頭 | 尾 |
---|---|---|
JPG | 255216 | 255217 |
PNG | 13780 | 96130 |
private bool checkImage(byte[] pngData)
{
if (pngData.Length > 4)
{
string fileHead = pngData[0].ToString() + pngData[1].ToString();
string flieTail = pngData[pngData.Length - 2].ToString() + pngData[pngData.Length - 1].ToString();
return checkImageFileFormat(fileHead, flieTail);
}
else
{
return false;
}
}
private bool checkImage(string filePath)
{
try
{
FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
BinaryReader reader = new BinaryReader(fs);
if (fs.Length > 0)
{
byte[] pngData = reader.ReadBytes((int) fs.Length);
string fileHead = pngData[0].ToString() + pngData[1].ToString();
string flieTail = pngData[pngData.Length - 2].ToString() + pngData[pngData.Length - 1].ToString();
fs.Close();
reader.Close();
return checkImageFileFormat(fileHead, flieTail);
}
else
{
fs.Close();
reader.Close();
return false;
}
}
catch
{
return false;
}
}
private bool checkImageFileFormat(string fileHead, string fileTail)
{
if ((fileHead == "255216" && fileTail == "255217") ||
(fileHead == "13780" && fileTail == "96130"))
{
return true;
}
else
{
return false;
}
}
結語
以上就是今天要講的内容,希望會對大家有所幫助!