EP Interaction

This commit is contained in:
2025-10-09 14:15:28 +08:00
parent 9b9264a6cc
commit dfa00340f5

View File

@@ -0,0 +1,679 @@
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)
{
yield return LoadAsBinaryAsync(
progress,
data =>
{
try
{
AssetBundle bundle = AssetBundle.LoadFromMemory(data);
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}");
}
},
onError
);
}
#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
}
}