using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
namespace Convention
{
///
/// 统一的文件交互类,支持本地文件、网络文件和localhost路径的自适应处理
/// 使用UnityWebRequest实现跨平台兼容,支持WebGL和IL2CPP
///
/// 是依赖项
///
[Serializable]
public sealed class Interaction
{
#region Fields and Properties
private string originalPath;
private string processedPath;
private PathType pathType;
private object cachedData;
public string OriginalPath => originalPath;
public string ProcessedPath => processedPath;
public PathType Type => pathType;
public enum PathType
{
LocalFile, // 本地文件 (file://)
NetworkHTTP, // 网络HTTP (http://)
NetworkHTTPS, // 网络HTTPS (https://)
LocalServer, // 本地服务器 (localhost)
StreamingAssets // StreamingAssets目录
}
#endregion
public Interaction(string path)
{
SetPath(path);
}
#region Setup
///
/// 设置并处理路径
///
public Interaction SetPath(string path)
{
originalPath = path;
processedPath = ProcessPath(path);
pathType = DeterminePathType(path);
return this;
}
///
/// 处理路径,转换为UnityWebRequest可识别的格式
///
private string ProcessPath(string path)
{
if (string.IsNullOrEmpty(path))
return path;
// 网络路径直接返回
if (path.StartsWith("http://") || path.StartsWith("https://"))
{
return path;
}
// localhost处理
if (path.StartsWith("localhost"))
{
return path.StartsWith("localhost/") ? "http://" + path : "http://localhost/" + path;
}
// StreamingAssets路径处理
if (path.StartsWith("StreamingAssets/") || path.StartsWith("StreamingAssets\\"))
{
return Path.Combine(Application.streamingAssetsPath, path.Substring(16)).Replace("\\", "/");
}
// 本地文件路径处理
string fullPath = Path.IsPathRooted(path) ? path : Path.GetFullPath(path);
// WebGL平台特殊处理
if (Application.platform == RuntimePlatform.WebGLPlayer)
{
// WebGL只能访问StreamingAssets,尝试构建StreamingAssets路径
return Application.streamingAssetsPath + "/" + Path.GetFileName(fullPath);
}
// 其他平台使用file://协议
return "file:///" + fullPath.Replace("\\", "/");
}
///
/// 确定路径类型
///
private PathType DeterminePathType(string path)
{
if (string.IsNullOrEmpty(path))
return PathType.LocalFile;
if (path.StartsWith("https://"))
return PathType.NetworkHTTPS;
if (path.StartsWith("http://"))
return PathType.NetworkHTTP;
if (path.StartsWith("localhost"))
return PathType.LocalServer;
if (path.StartsWith("StreamingAssets"))
return PathType.StreamingAssets;
return PathType.LocalFile;
}
#endregion
#region Load
#region LoadAsync
public IEnumerator LoadAsTextAsync(Action onSuccess, Action onError = null)
{
using UnityWebRequest request = UnityWebRequest.Get(processedPath);
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
cachedData = request.downloadHandler.text;
onSuccess?.Invoke((string)cachedData);
}
else
{
string errorMsg = $"Failed to load text from {originalPath}: {request.error}";
onError?.Invoke(errorMsg);
}
}
public IEnumerator LoadAsBinaryAsync(Action onSuccess, Action onError = null)
{
using UnityWebRequest request = UnityWebRequest.Get(processedPath);
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
cachedData = request.downloadHandler.data;
onSuccess?.Invoke((byte[])cachedData);
}
else
{
string errorMsg = $"Failed to load binary from {originalPath}: {request.error}";
onError?.Invoke(errorMsg);
}
}
public IEnumerator LoadAsBinaryAsync(Action progress, Action onSuccess, Action onError = null)
{
using UnityWebRequest request = UnityWebRequest.Get(processedPath);
var result = request.SendWebRequest();
while (result.isDone == false)
{
progress(result.progress);
yield return null;
}
if (request.result == UnityWebRequest.Result.Success)
{
cachedData = request.downloadHandler.data;
onSuccess?.Invoke((byte[])cachedData);
}
else
{
string errorMsg = $"Failed to load binary from {originalPath}: {request.error}";
onError?.Invoke(errorMsg);
}
}
public IEnumerator LoadAsRawJsonAsync(Action onSuccess, Action onError = null)
{
yield return LoadAsTextAsync(
text =>
{
try
{
T result = JsonUtility.FromJson(text);
cachedData = result;
onSuccess?.Invoke(result);
}
catch (Exception e)
{
onError?.Invoke($"Failed to parse JSON: {e.Message}");
}
},
onError
);
}
public IEnumerator LoadAsJsonAsync(Action onSuccess, Action onError = null)
{
yield return LoadAsTextAsync(
text =>
{
try
{
using StreamReader reader = new(new MemoryStream(System.Text.Encoding.UTF8.GetBytes(text)));
var jsonReader = new ES3Internal.ES3JSONReader(reader.BaseStream, new(originalPath));
onSuccess?.Invoke(jsonReader.Read());
}
catch (Exception e)
{
onError?.Invoke($"Failed to parse JSON: {e.Message}");
}
},
onError
);
}
public IEnumerator LoadAsImageAsync(Action onSuccess, Action onError = null)
{
using (UnityWebRequest request = UnityWebRequestTexture.GetTexture(processedPath))
{
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
Texture2D texture = DownloadHandlerTexture.GetContent(request);
cachedData = texture;
onSuccess?.Invoke(texture);
}
else
{
string errorMsg = $"Failed to load image from {originalPath}: {request.error}";
onError?.Invoke(errorMsg);
}
}
}
public IEnumerator LoadAsAudioAsync(Action onSuccess, Action onError = null, AudioType audioType = AudioType.UNKNOWN)
{
if (audioType == AudioType.UNKNOWN)
audioType = GetAudioType(originalPath);
using (UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(processedPath, audioType))
{
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
AudioClip audioClip = DownloadHandlerAudioClip.GetContent(request);
cachedData = audioClip;
onSuccess?.Invoke(audioClip);
}
else
{
string errorMsg = $"Failed to load audio from {originalPath}: {request.error}";
onError?.Invoke(errorMsg);
}
}
}
public IEnumerator LoadAsAssetBundle(Action progress, Action onSuccess, Action onError = null)
{
AssetBundleCreateRequest request = null;
yield return LoadAsBinaryAsync(x => progress(x * 0.5f), data => request = AssetBundle.LoadFromMemoryAsync(data), onError);
while (request.isDone == false)
{
progress(0.5f + request.progress * 0.5f);
yield return null;
}
try
{
AssetBundle bundle = request.assetBundle;
if (bundle != null)
{
cachedData = bundle;
onSuccess?.Invoke(bundle);
}
else
{
onError?.Invoke($"Failed to load AssetBundle from data.");
}
}
catch (Exception e)
{
onError?.Invoke($"Failed to load AssetBundle: {e.Message}");
}
}
#endregion
#region Load Sync
public string LoadAsText()
{
string buffer = null;
bool isEnd = false;
bool IsError = false;
var it = LoadAsTextAsync(x =>
{
buffer = x;
isEnd = true;
}, e =>
{
IsError = true;
throw new Exception(e);
});
try
{
while (!isEnd)
{
it.MoveNext();
}
}
catch
{
if(IsError)
{
throw;
}
}
return buffer;
}
public byte[] LoadAsBinary()
{
byte[] buffer = null;
bool isEnd = false;
bool IsError = false;
var it = LoadAsBinaryAsync(x =>
{
buffer = x;
isEnd = true;
}, e =>
{
IsError = true;
throw new Exception(e);
});
try
{
while (!isEnd)
{
it.MoveNext();
}
}
catch
{
if (IsError)
{
throw;
}
}
return buffer;
}
public T LoadAsRawJson()
{
T buffer = default;
bool isEnd = false;
bool IsError = false;
var it = LoadAsRawJsonAsync(x =>
{
buffer = x;
isEnd = true;
}, e =>
{
IsError = true;
throw new Exception(e);
});
try
{
while (!isEnd)
{
it.MoveNext();
}
}
catch
{
if (IsError)
{
throw;
}
}
return buffer;
}
public T LoadAsJson()
{
T buffer = default;
bool isEnd = false;
bool IsError = false;
var it = LoadAsJsonAsync(x =>
{
buffer = x;
isEnd = true;
}, e =>
{
IsError = true;
throw new Exception(e);
});
try
{
while (!isEnd)
{
it.MoveNext();
}
}
catch
{
if (IsError)
{
throw;
}
}
return buffer;
}
public Texture2D LoadAsImage()
{
Texture2D buffer = null;
bool isEnd = false;
bool IsError = false;
var it = LoadAsImageAsync(x =>
{
buffer = x;
isEnd = true;
}, e =>
{
IsError = true;
throw new Exception(e);
});
try
{
while (!isEnd)
{
it.MoveNext();
}
}
catch
{
if (IsError)
{
throw;
}
}
return buffer;
}
public AudioClip LoadAsAudio(AudioType audioType = AudioType.UNKNOWN)
{
AudioClip buffer = null;
bool isEnd = false;
bool IsError = false;
var it = LoadAsAudioAsync(x =>
{
buffer = x;
isEnd = true;
}, e =>
{
IsError = true;
throw new Exception(e);
}, audioType);
try
{
while (!isEnd)
{
it.MoveNext();
}
}
catch
{
if (IsError)
{
throw;
}
}
return buffer;
}
#endregion
#endregion
#region Save
public Interaction SaveAsText(string content)
{
if (Application.platform == RuntimePlatform.WebGLPlayer)
{
throw new NotSupportedException("WebGL平台不支持文件保存。");
}
if (pathType != PathType.LocalFile)
{
throw new NotSupportedException("仅支持保存到本地文件路径。");
}
new ToolFile(originalPath).SaveAsText(content);
return this;
}
public Interaction SaveAsBinary(byte[] data)
{
if (Application.platform == RuntimePlatform.WebGLPlayer)
{
throw new NotSupportedException("WebGL平台不支持文件保存。");
}
if (pathType != PathType.LocalFile)
{
throw new NotSupportedException("仅支持保存到本地文件路径。");
}
new ToolFile(originalPath).SaveAsBinary(data);
return this;
}
public Interaction SaveAsRawJson(T obj)
{
new ToolFile(originalPath).SaveAsRawJson(obj);
return this;
}
public Interaction SaveAsJson(T obj)
{
new ToolFile(originalPath).SaveAsJson(obj);
return this;
}
#endregion
#region Tools
///
/// 获取本地文件路径
///
private string GetLocalPath()
{
if (processedPath.StartsWith("file:///"))
{
return processedPath.Substring(8);
}
return originalPath;
}
///
/// 根据文件扩展名获取音频类型
///
private AudioType GetAudioType(string path)
{
return BasicAudioSystem.GetAudioType(path);
}
///
/// 检查文件是否存在
///
public bool Exists()
{
if (pathType == PathType.LocalFile && Application.platform != RuntimePlatform.WebGLPlayer)
{
return File.Exists(GetLocalPath());
}
else
{
// TODO : 网络文件和WebGL平台需要通过请求检查
// 当前默认存在
return true;
}
}
///
/// 异步检查文件是否存在
///
public IEnumerator ExistsAsync(Action callback)
{
using UnityWebRequest request = UnityWebRequest.Head(processedPath);
yield return request.SendWebRequest();
callback?.Invoke(request.result == UnityWebRequest.Result.Success);
}
#endregion
#region Operator
public static implicit operator string(Interaction interaction) => interaction?.originalPath;
public override string ToString() => originalPath;
///
/// 路径连接操作符
///
public static Interaction operator |(Interaction left, string rightPath)
{
if (left.pathType == PathType.NetworkHTTP || left.pathType == PathType.NetworkHTTPS || left.pathType == PathType.LocalServer)
{
string baseUrl = left.originalPath;
string newUrl = baseUrl.EndsWith("/") ? baseUrl + rightPath : baseUrl + "/" + rightPath;
return new Interaction(newUrl);
}
else
{
string newPath = Path.Combine(left.originalPath, rightPath);
return new Interaction(newPath);
}
}
#endregion
#region Tools
///
/// 获取文件名
///
public string GetFilename()
{
if (pathType == PathType.NetworkHTTP || pathType == PathType.NetworkHTTPS || pathType == PathType.LocalServer)
{
var uri = new Uri(processedPath);
return Path.GetFileName(uri.AbsolutePath);
}
return Path.GetFileName(originalPath);
}
///
/// 获取文件扩展名
///
public string GetExtension()
{
return Path.GetExtension(GetFilename());
}
///
/// 检查扩展名
///
public bool ExtensionIs(params string[] extensions)
{
string ext = GetExtension().ToLower();
string extWithoutDot = ext.Length > 1 ? ext[1..] : "";
foreach (string extension in extensions)
{
string checkExt = extension.ToLower();
if (ext == checkExt || extWithoutDot == checkExt || ext == "." + checkExt)
return true;
}
return false;
}
#endregion
#region Tools
///
/// 创建一个新的Interaction实例
///
public static Interaction Create(string path) => new(path);
///
/// 从StreamingAssets创建
///
public static Interaction FromStreamingAssets(string relativePath)
{
return new Interaction("StreamingAssets/" + relativePath);
}
///
/// 从本地服务器创建
///
public static Interaction FromLocalhost(string path, int port = 80)
{
string url = port == 80 ? $"localhost/{path}" : $"localhost:{port}/{path}";
return new Interaction(url);
}
#endregion
}
}