Files
Convention-Unity/Convention/[Runtime]/Interaction.cs

680 lines
20 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
namespace Convention
{
/// <summary>
/// 统一的文件交互类支持本地文件、网络文件和localhost路径的自适应处理
/// 使用UnityWebRequest实现跨平台兼容支持WebGL和IL2CPP
///
/// <list type="bullet"><see cref="ToolFile"/>是依赖项</list>
/// </summary>
[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
/// <summary>
/// 设置并处理路径
/// </summary>
public Interaction SetPath(string path)
{
originalPath = path;
processedPath = ProcessPath(path);
pathType = DeterminePathType(path);
return this;
}
/// <summary>
/// 处理路径转换为UnityWebRequest可识别的格式
/// </summary>
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("\\", "/");
}
/// <summary>
/// 确定路径类型
/// </summary>
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<string> onSuccess, Action<string> 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<byte[]> onSuccess, Action<string> 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<float> progress, Action<byte[]> onSuccess, Action<string> 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<T>(Action<T> onSuccess, Action<string> onError = null)
{
yield return LoadAsTextAsync(
text =>
{
try
{
T result = JsonUtility.FromJson<T>(text);
cachedData = result;
onSuccess?.Invoke(result);
}
catch (Exception e)
{
onError?.Invoke($"Failed to parse JSON: {e.Message}");
}
},
onError
);
}
public IEnumerator LoadAsJsonAsync<T>(Action<T> onSuccess, Action<string> 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<T>());
}
catch (Exception e)
{
onError?.Invoke($"Failed to parse JSON: {e.Message}");
}
},
onError
);
}
public IEnumerator LoadAsImageAsync(Action<Texture2D> onSuccess, Action<string> 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<AudioClip> onSuccess, Action<string> 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<float> progress, Action<AssetBundle> onSuccess, Action<string> 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>()
{
T buffer = default;
bool isEnd = false;
bool IsError = false;
var it = LoadAsRawJsonAsync<T>(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>()
{
T buffer = default;
bool isEnd = false;
bool IsError = false;
var it = LoadAsJsonAsync<T>(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>(T obj)
{
new ToolFile(originalPath).SaveAsRawJson(obj);
return this;
}
public Interaction SaveAsJson<T>(T obj)
{
new ToolFile(originalPath).SaveAsJson(obj);
return this;
}
#endregion
#region Tools
/// <summary>
/// 获取本地文件路径
/// </summary>
private string GetLocalPath()
{
if (processedPath.StartsWith("file:///"))
{
return processedPath.Substring(8);
}
return originalPath;
}
/// <summary>
/// 根据文件扩展名获取音频类型
/// </summary>
private AudioType GetAudioType(string path)
{
return BasicAudioSystem.GetAudioType(path);
}
/// <summary>
/// 检查文件是否存在
/// </summary>
public bool Exists()
{
if (pathType == PathType.LocalFile && Application.platform != RuntimePlatform.WebGLPlayer)
{
return File.Exists(GetLocalPath());
}
else
{
// TODO : 网络文件和WebGL平台需要通过请求检查
// 当前默认存在
return true;
}
}
/// <summary>
/// 异步检查文件是否存在
/// </summary>
public IEnumerator ExistsAsync(Action<bool> 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;
/// <summary>
/// 路径连接操作符
/// </summary>
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
/// <summary>
/// 获取文件名
/// </summary>
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);
}
/// <summary>
/// 获取文件扩展名
/// </summary>
public string GetExtension()
{
return Path.GetExtension(GetFilename());
}
/// <summary>
/// 检查扩展名
/// </summary>
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
/// <summary>
/// 创建一个新的Interaction实例
/// </summary>
public static Interaction Create(string path) => new(path);
/// <summary>
/// 从StreamingAssets创建
/// </summary>
public static Interaction FromStreamingAssets(string relativePath)
{
return new Interaction("StreamingAssets/" + relativePath);
}
/// <summary>
/// 从本地服务器创建
/// </summary>
public static Interaction FromLocalhost(string path, int port = 80)
{
string url = port == 80 ? $"localhost/{path}" : $"localhost:{port}/{path}";
return new Interaction(url);
}
#endregion
}
}