Commit 3e267a81 authored by hex's avatar hex

Optimized GameTextureManager

parent a992169d
Pipeline #39190 failed
......@@ -367,7 +367,13 @@ public class Program : MonoBehaviour
InterString.initialize("config/translation.conf");
// 显示一个简单的加载提示
PrintToChat(InterString.Get("正在加载本地数据..."));
// GameTextureManager.initialize();
if (!GameTextureManager.IsInitialized)
{
GameTextureManager.InitializeAssets(); // 1. 同步加载核心资源
GameTextureManager.StartBackgroundThread(); // 2. 启动后台图片加载线程
}
Config.initialize("config/config.conf");
// [新增] 封装文件加载逻辑,避免重复检查
......@@ -1205,6 +1211,39 @@ public class Program : MonoBehaviour
Resources.UnloadUnusedAssets();
onRESIZED();
}
if (GameTextureManager.IsInitialized)
{
// 1. 检查并处理下载请求
if (GameTextureManager.HasDownloadRequests())
{
var request = GameTextureManager.GetNextDownloadRequest();
if(!string.IsNullOrEmpty(request.Url))
{
// Program 自身就是 MonoBehaviour,所以可以直接启动协程
StartCoroutine(DownloadAndProcessFile(request));
}
}
// 2. 检查并处理纹理创建任务 (分帧处理避免卡顿)
int tasksProcessedThisFrame = 0;
int maxTasksPerFrame = 5;
while (GameTextureManager.HasMainThreadTasks() && tasksProcessedThisFrame < maxTasksPerFrame)
{
var resource = GameTextureManager.GetNextMainThreadTask();
if (resource != null)
{
GameTextureManager.CreateTextureFromResource(resource);
tasksProcessedThisFrame++;
}
else
{
break;
}
}
}
fixALLcamerasPreFrame();
wheelValue = UICamera.GetAxis("Mouse ScrollWheel") * 50;
pointedGameObject = null;
......@@ -1269,6 +1308,15 @@ public class Program : MonoBehaviour
}
}
// NEW: 这个辅助协程也从 Runner 移到了这里
private static IEnumerator DownloadAndProcessFile(GameTextureManager.DownloadRequest request)
{
yield return UnityFileDownloader.DownloadFileAsync(request.Url, request.FilePath, (success) =>
{
GameTextureManager.OnDownloadComplete(success, request);
});
}
private void onRESIZED()
{
preWid = Screen.width;
......@@ -1330,6 +1378,10 @@ public class Program : MonoBehaviour
{
servants[i].OnQuit();
}
// 在应用退出时,优雅地关闭 GameTextureManager 的后台线程
GameTextureManager.Shutdown();
Running = false;
try
{
......
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using UnityEngine;
using YGOSharp.OCGWrapper.Enums;
public enum GameTextureType
{
card_picture = 0,
......@@ -17,153 +16,87 @@ public enum GameTextureType
public class GameTextureManager
{
#region --- 线程安全与主线程桥接 ---
#region Main-Thread Bridge & Initialization
// ADDED: A structure to hold download request details.
internal struct DownloadRequest // 'internal' is good practice for helper structs
{
public string Url;
public string FilePath;
public PictureResource PicResource;
}
// ADDED: A thread-safe queue for download requests from the background thread.
private static readonly Queue<DownloadRequest> downloadRequestQueue = new Queue<DownloadRequest>();
// ADDED: Unity will automatically call this method once when the game loads,
// before any scene loads. This is the perfect place to set up our helper.
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void InitializeOnLoad()
{
// Create a hidden GameObject to host our runner.
GameObject runnerObject = new GameObject("GameTextureManagerRunner");
// Add our runner component to it.
runnerObject.AddComponent<GameTextureManagerRunner>();
// Ensure it persists across scene changes.
GameObject.DontDestroyOnLoad(runnerObject);
// The original initialize logic now goes here.
Initialize();
}
// ADDED: Helper methods for the runner to interact with the queue.
internal static bool HasDownloadRequests()
{
return downloadRequestQueue.Count > 0;
}
internal static DownloadRequest GetNextDownloadRequest()
{
lock(downloadRequestQueue)
{
return downloadRequestQueue.Dequeue();
}
}
// 真正的线程锁,用于保护所有共享的集合
private static readonly object _lock = new object();
#endregion
// 待加载队列: 主线程请求 -> 背景线程处理
private static readonly Stack<PictureResource> _requestStack = new Stack<PictureResource>();
static bool bLock = false;
// 待应用队列: 背景线程处理完毕 -> 主线程创建Texture2D
private static readonly Queue<PictureResource> _mainThreadApplyQueue = new Queue<PictureResource>();
static Stack<PictureResource> waitLoadStack = new Stack<PictureResource>();
// 已加载资源缓存
private static readonly Dictionary<ulong, PictureResource> _loadedCache = new Dictionary<ulong, PictureResource>();
static Dictionary<ulong, PictureResource> loadedList = new Dictionary<ulong, PictureResource>();
// 防止重复请求的字典
private static readonly HashSet<ulong> _requestedSet = new HashSet<ulong>();
static Dictionary<ulong, bool> addedMap = new Dictionary<ulong, bool>();
// 下载请求队列
private static readonly Queue<DownloadRequest> _downloadRequestQueue = new Queue<DownloadRequest>();
static readonly HttpDldFile df = new HttpDldFile();
// 背景I/O线程
private static Thread _ioThread;
private static bool _isRunning = false;
public class BitmapHelper
{
public System.Drawing.Color[,] colors = null;
public static bool IsInitialized = false;
public BitmapHelper(string path)
// 内部结构体,用于传递下载任务
internal struct DownloadRequest
{
Bitmap bitmap;
try
{
bitmap = (Bitmap)Image.FromFile(path);
public string Url;
public string FilePath;
public PictureResource PicResource;
}
catch (Exception)
{
bitmap = new Bitmap(10, 10);
for (int i = 0; i < 10; i++)
// 主线程Runner的交互接口
internal static bool HasMainThreadTasks()
{
for (int w = 0; w < 10; w++)
lock (_lock)
{
bitmap.SetPixel(i, w, System.Drawing.Color.White);
return _mainThreadApplyQueue.Count > 0;
}
}
}
var bmpData = bitmap.LockBits(
new Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb
);
IntPtr ptr = bmpData.Scan0;
int bytes = Math.Abs(bmpData.Stride) * bitmap.Height;
byte[] rgbValues = new byte[bytes];
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
colors = new System.Drawing.Color[bitmap.Width, bitmap.Height];
for (int counter = 0; counter < rgbValues.Length; counter += 4)
{
int i_am = counter / 4;
colors[i_am % bitmap.Width, i_am / bitmap.Width] = System.Drawing.Color.FromArgb(
rgbValues[counter + 3],
rgbValues[counter + 2],
rgbValues[counter + 1],
rgbValues[counter + 0]
);
}
bitmap.UnlockBits(bmpData);
bitmap.Dispose();
}
public System.Drawing.Color GetPixel(int a, int b)
internal static PictureResource GetNextMainThreadTask()
{
lock (_lock)
{
return colors[a, b];
return _mainThreadApplyQueue.Count > 0 ? _mainThreadApplyQueue.Dequeue() : null;
}
}
public static void clearUnloaded()
{
while (true)
{
try
internal static bool HasDownloadRequests()
{
while (waitLoadStack.Count > 0)
lock (_lock)
{
var a = waitLoadStack.Pop();
addedMap.Remove((UInt64)a.type << 32 | (UInt64)a.code);
return _downloadRequestQueue.Count > 0;
}
break;
}
catch (Exception e)
internal static DownloadRequest GetNextDownloadRequest()
{
Thread.Sleep(10);
Debug.Log(e);
}
lock (_lock)
{
return _downloadRequestQueue.Count > 0 ? _downloadRequestQueue.Dequeue() : default(DownloadRequest);
}
}
public static void clearAll()
{
while (true)
public static void Shutdown()
{
try
_isRunning = false;
if (_ioThread != null && _ioThread.IsAlive)
{
waitLoadStack.Clear();
loadedList.Clear();
addedMap.Clear();
break;
}
catch (Exception e)
{
Thread.Sleep(10);
Debug.Log(e);
}
// 给线程一个正常退出的机会
_ioThread.Join(500);
}
}
#endregion
#region --- 资源数据结构 ---
public class PictureResource
{
public GameTextureType type;
......@@ -171,928 +104,569 @@ public class GameTextureManager
public bool pCard = false;
public float k = 1;
//public bool autoMade = false;
public byte[] data = null;
public float[,,] hashed_data = null;
public Texture2D u_data = null;
public Texture2D nullReturen = null;
// --- 数据流 ---
// 1. [背景线程] 从文件读取的原始字节
public byte[] rawData = null;
// 2. [主线程] 从rawData转换来的像素数据,用于处理
public Color32[] pixelData = null;
public int pixelWidth;
public int pixelHeight;
// 3. [主线程] 最终生成的Unity纹理
public Texture2D finalTexture = null;
public Texture2D nullReturn = null;
public PictureResource(GameTextureType t, long c, Texture2D n)
{
type = t;
code = c;
nullReturen = n;
}
nullReturn = n;
}
private class UIPictureResource
// 辅助方法,用于在Texture创建后释放中间内存
public void ClearIntermediateData()
{
public string name;
public Texture2D data = null;
rawData = null;
pixelData = null;
}
}
static BetterList<UIPictureResource> allUI = new BetterList<UIPictureResource>();
public static Texture2D myBack = null;
public static Texture2D opBack = null;
public static Texture2D unknown = null;
public static Texture2D attack = null;
public static Texture2D negated = null;
public static Texture2D bar = null;
public static Texture2D exBar = null;
public static Texture2D lp = null;
public static Texture2D time = null;
public static Texture2D L = null;
public static Texture2D R = null;
public static Texture2D Chain = null;
public static Texture2D Mask = null;
public static Texture2D N = null;
public static Texture2D LINK = null;
public static Texture2D LINKm = null;
public static Texture2D nt = null;
public static Texture2D bp = null;
public static Texture2D ep = null;
public static Texture2D mp1 = null;
public static Texture2D mp2 = null;
public static Texture2D dp = null;
public static Texture2D sp = null;
public static Texture2D phase = null;
public static bool AutoPicDownload;
#endregion
public static Texture2D rs = null;
#region --- 核心公共接口 (API) ---
public static Texture2D ts = null;
// 对外接口保持不变
public static Texture2D get(long code, GameTextureType type, Texture2D nullReturnValue = null)
{
UInt64 key = hashPic(code, type);
internal static IEnumerator DownloadAndProcessFile(DownloadRequest request)
lock (_lock)
{
yield return UnityFileDownloader.DownloadFileAsync(request.Url, request.FilePath, (success) =>
PictureResource resource;
if (_loadedCache.TryGetValue(key, out resource))
{
if (success)
// 如果已经有最终纹理,直接返回
if (resource.finalTexture != null)
{
Debug.Log("Download successful, processing card picture: " + request.PicResource.code);
LoadCardPicture(request.PicResource, request.FilePath);
// myBack作为未找到图片时的替代品,这里返回用户指定的默认值
return resource.finalTexture == myBack ? nullReturnValue : resource.finalTexture;
}
else
{
Debug.LogWarning("Download failed for card: " + request.PicResource.code);
LoadCardPicture(request.PicResource, request.FilePath); // Let it handle the missing file
}
});
// 否则返回 null,主线程Runner正在处理它
return null;
}
private static Thread main_thread;
// MODIFIED: In your original code, Program.Running was used. This is a more robust way.
private static bool IsRunning = true;
// Call this method from your main game logic on application quit to stop the thread.
public static void Shutdown()
{
IsRunning = false;
if(main_thread != null && main_thread.IsAlive)
// 如果已经请求过,但还未处理完,则直接返回null,防止重复请求
if (_requestedSet.Contains(key))
{
main_thread.Join(100);
return null;
}
// 创建新请求
PictureResource newResource = new PictureResource(type, code, nullReturnValue);
_requestStack.Push(newResource);
_requestedSet.Add(key);
}
static void thread_run()
{
while (IsRunning)
{
try
return null;
}
public static float getK(long code, GameTextureType type)
{
Thread.Sleep(50);
int thu = 0;
while (waitLoadStack.Count > 0)
UInt64 key = hashPic(code, type);
lock (_lock)
{
thu++;
if (thu == 10)
PictureResource r;
if (_loadedCache.TryGetValue(key, out r))
{
Thread.Sleep(50);
thu = 0;
return r.k;
}
if (bLock == false)
{
PictureResource pic;
pic = waitLoadStack.Pop();
try
{
pic.pCard =
(
YGOSharp.CardsManager.Get((int)pic.code).Type
& (int)CardType.Pendulum
) > 0;
}
catch (Exception e)
{
Debug.Log("e 0" + e.ToString());
return 1.0f;
}
switch (pic.type)
#endregion
#region --- 背景线程处理 (Background I/O Thread) ---
// 背景线程只做文件I/O,不做任何Unity API调用
static void IoThreadRun()
{
case GameTextureType.card_feature:
while (_isRunning)
{
ProcessingCardFeature(pic);
break;
}
case GameTextureType.card_picture:
{
ProcessingCardPicture(pic);
break;
}
case GameTextureType.card_verticle_drawing:
PictureResource resourceToProcess = null;
lock (_lock)
{
ProcessingVerticleDrawing(pic);
break;
}
}
}
}
}
catch (Exception e)
if (_requestStack.Count > 0)
{
Debug.Log("error 1" + e.ToString());
}
resourceToProcess = _requestStack.Pop();
}
}
private static void ProcessingCardFeature(PictureResource pic)
if (resourceToProcess != null)
{
try
{
if (File.Exists("picture/closeup/" + pic.code.ToString() + ".png"))
{
string path = "picture/closeup/" + pic.code.ToString() + ".png";
if (Program.ANDROID_API_N)
{
BitmapHelper bitmap = new BitmapHelper(path);
int left;
int right;
int up;
int down;
CutTop(bitmap, out left, out right, out up, out down);
up = CutLeft(bitmap, up);
down = CutRight(bitmap, down);
right = CutButton(bitmap, right);
int width = right - left;
int height = down - up;
pic.hashed_data = new float[width, height, 4];
for (int w = 0; w < width; w++)
{
for (int h = 0; h < height; h++)
{
System.Drawing.Color color = bitmap.GetPixel(left + w, up + h);
float a = (float)color.A / 255f;
if (w < 40)
if (a > (float)w / (float)40)
a = (float)w / (float)40;
if (w > (width - 40))
if (a > 1f - (float)(w - (width - 40)) / (float)40)
a = 1f - (float)(w - (width - 40)) / (float)40;
if (h < 40)
if (a > (float)h / (float)40)
a = (float)h / (float)40;
if (h > (height - 40))
if (a > 1f - (float)(h - (height - 40)) / (float)40)
a = 1f - (float)(h - (height - 40)) / (float)40;
pic.hashed_data[w, height - h - 1, 0] = (float)color.R / 255f;
pic.hashed_data[w, height - h - 1, 1] = (float)color.G / 255f;
pic.hashed_data[w, height - h - 1, 2] = (float)color.B / 255f;
pic.hashed_data[w, height - h - 1, 3] = a;
}
}
caculateK(pic);
}
else
{
byte[] data;
using (FileStream file = new FileStream(path, FileMode.Open, FileAccess.Read))
// 检查卡片是否是灵摆怪兽
var card = YGOSharp.CardsManager.Get((int)resourceToProcess.code);
if (card != null)
{
file.Seek(0, SeekOrigin.Begin);
data = new byte[file.Length];
file.Read(data, 0, (int)file.Length);
resourceToProcess.pCard = (card.Type & (int)CardType.Pendulum) > 0;
}
pic.data = data;
}
if (!loadedList.ContainsKey(hashPic(pic.code, pic.type)))
catch (Exception e)
{
loadedList.Add(hashPic(pic.code, pic.type), pic);
Debug.LogWarning("Get card type failed for code " + resourceToProcess.code + ": " + e.Message);
}
ProcessResourceRequest(resourceToProcess);
}
else
{
string path = "picture/card/" + pic.code.ToString() + ".png";
if (!File.Exists(path))
{
path = "picture/card/" + pic.code.ToString() + ".jpg";
// 没有任务时短暂休眠
Thread.Sleep(30);
}
bool Iam8 = false;
if (!File.Exists(path))
{
Iam8 = true;
path = "expansions/pics/" + pic.code.ToString() + ".jpg";
}
if (!File.Exists(path))
{
Iam8 = true;
path = "pics/" + pic.code.ToString() + ".jpg";
}
if (!File.Exists(path))
{
Iam8 = true;
path = "picture/cardIn8thEdition/" + pic.code.ToString() + ".jpg";
}
if (!File.Exists(path))
private static void ProcessResourceRequest(PictureResource pic)
{
pic.hashed_data = new float[10, 10, 4];
for (int w = 0; w < 10; w++)
string path = FindImagePath(pic);
if (!string.IsNullOrEmpty(path) && File.Exists(path))
{
for (int h = 0; h < 10; h++)
try
{
pic.hashed_data[w, h, 0] = 0;
pic.hashed_data[w, h, 1] = 0;
pic.hashed_data[w, h, 2] = 0;
pic.hashed_data[w, h, 3] = 0;
}
pic.rawData = File.ReadAllBytes(path);
}
if (!loadedList.ContainsKey(hashPic(pic.code, pic.type)))
catch (Exception e)
{
loadedList.Add(hashPic(pic.code, pic.type), pic);
Debug.LogError("Failed to read file: " + path + ". Error: " + e.Message);
pic.rawData = null; // 确保失败时rawData为null
}
}
else
{
pic.hashed_data = getCuttedPic(path, pic.pCard, Iam8);
int width = pic.hashed_data.GetLength(0);
int height = pic.hashed_data.GetLength(1);
int size = (int)(height * 0.8);
int empWidth = (width - size) / 2;
int empHeight = (height - size) / 2;
int right = width - empWidth;
int buttom = height - empHeight;
for (int w = 0; w < width; w++)
else if (pic.type == GameTextureType.card_picture && pic.code != 0 && AutoPicDownload)
{
for (int h = 0; h < height; h++)
// 文件不存在,且是卡图,则尝试下载
string url = "https://cdn02.moecube.com:444/images/ygopro-images-zh-CN/" + pic.code.ToString() + ".jpg";
string finalPath = "picture/card/" + pic.code.ToString() + ".jpg";
lock (_lock)
{
float a = pic.hashed_data[w, h, 3];
if (w < empWidth)
if (a > ((float)w) / (float)empWidth)
a = ((float)w) / (float)empWidth;
if (h < empHeight)
if (a > ((float)h) / (float)empHeight)
a = ((float)h) / (float)empHeight;
if (w > right)
if (a > 1f - ((float)(w - right)) / (float)empWidth)
a = 1f - ((float)(w - right)) / (float)empWidth;
if (h > buttom)
if (a > 1f - ((float)(h - buttom)) / (float)empHeight)
a = 1f - ((float)(h - buttom)) / (float)empHeight;
pic.hashed_data[w, h, 3] = a * 0.7f;
}
}
if (!loadedList.ContainsKey(hashPic(pic.code, pic.type)))
_downloadRequestQueue.Enqueue(new DownloadRequest
{
loadedList.Add(hashPic(pic.code, pic.type), pic);
}
}
Url = url,
FilePath = finalPath,
PicResource = pic
});
}
// 下载任务已入队,直接返回,等待下载回调来继续处理
return;
}
catch (Exception e)
// I/O操作完成,将资源推入主线程处理队列
lock (_lock)
{
Debug.Log("e 1" + e.ToString());
_mainThreadApplyQueue.Enqueue(pic);
}
}
private static void caculateK(PictureResource pic)
{
int width = pic.hashed_data.GetLength(0);
int height = pic.hashed_data.GetLength(1);
int h = 0;
for (h = height - 1; h > 0; h--)
{
int all = 0;
for (int w = 0; w < width; w++)
private static string FindImagePath(PictureResource pic)
{
if (pic.hashed_data[w, h, 3] > 0.05f)
{
all += 1;
}
}
if (all * 5 > width)
// ... (原代码中的路径查找逻辑,这里可以保持或优化)
// 为了清晰,我把它提取成了一个独立方法
string path = "";
switch (pic.type)
{
case GameTextureType.card_picture:
path = "picture/card/" + pic.code + ".png";
if (!File.Exists(path)) path = "picture/card/" + pic.code + ".jpg";
if (!File.Exists(path)) path = "expansions/pics/" + pic.code + ".jpg";
if (!File.Exists(path)) path = "pics/" + pic.code + ".jpg";
if (!File.Exists(path)) path = "picture/cardIn8thEdition/" + pic.code + ".jpg";
break;
}
}
pic.k = ((float)h) / ((float)height);
if (pic.k > 1)
case GameTextureType.card_feature:
case GameTextureType.card_verticle_drawing:
path = "picture/closeup/" + pic.code + ".png";
if (!File.Exists(path))
{
pic.k = 1f;
// 回退到卡图路径
path = "picture/card/" + pic.code + ".png";
if (!File.Exists(path)) path = "picture/card/" + pic.code + ".jpg";
if (!File.Exists(path)) path = "expansions/pics/" + pic.code + ".jpg";
if (!File.Exists(path)) path = "pics/" + pic.code + ".jpg";
if (!File.Exists(path)) path = "picture/cardIn8thEdition/" + pic.code + ".jpg";
}
if (pic.k < 0)
{
pic.k = 0.1f;
break;
}
return path;
}
private static float[,,] getCuttedPic(string path, bool pCard, bool EightEdition)
// 下载完成后的回调
internal static void OnDownloadComplete(bool success, DownloadRequest request)
{
BitmapHelper bitmap = new BitmapHelper(path);
int left = 0,
top = 0,
right = bitmap.colors.GetLength(0),
buttom = bitmap.colors.GetLength(1);
//right is width and buttom is height now
if (EightEdition)
if (success)
{
if (pCard)
Debug.Log("Download successful, reading file: " + request.FilePath);
try
{
left = (int)(16f * ((float)right) / 177f);
right = (int)(162f * ((float)right) / 177f);
top = (int)(50f * ((float)buttom) / 254f);
buttom = (int)(158f * ((float)buttom) / 254f);
request.PicResource.rawData = File.ReadAllBytes(request.FilePath);
}
else
catch (Exception ex)
{
left = (int)(26f * ((float)right) / 177f);
right = (int)(152f * ((float)right) / 177f);
top = (int)(55f * ((float)buttom) / 254f);
buttom = (int)(180f * ((float)buttom) / 254f);
Debug.LogError("Failed to read downloaded file: " + ex.Message);
request.PicResource.rawData = null;
}
}
else
{
if (pCard)
{
left = (int)(25f * ((float)right) / 322f);
right = (int)(290f * ((float)right) / 322f);
top = (int)(73f * ((float)buttom) / 402f);
buttom = (int)(245f * ((float)buttom) / 402f);
}
else
{
left = (int)(40f * ((float)right) / 322f);
right = (int)(280f * ((float)right) / 322f);
top = (int)(75f * ((float)buttom) / 402f);
buttom = (int)(280f * ((float)buttom) / 402f);
}
Debug.LogWarning("Download failed for card: " + request.PicResource.code);
request.PicResource.rawData = null;
}
float[,,] returnValue = new float[right - left, buttom - top, 4];
for (int w = 0; w < right - left; w++)
{
for (int h = 0; h < buttom - top; h++)
// 将任务推入主线程处理队列
lock (_lock)
{
System.Drawing.Color color = bitmap.GetPixel(
(int)(left + w),
(int)(buttom - 1 - h)
);
returnValue[w, h, 0] = (float)color.R / 255f;
returnValue[w, h, 1] = (float)color.G / 255f;
returnValue[w, h, 2] = (float)color.B / 255f;
returnValue[w, h, 3] = (float)color.A / 255f;
_mainThreadApplyQueue.Enqueue(request.PicResource);
}
}
return returnValue;
}
#endregion
private static int CutButton(BitmapHelper bitmap, int right)
{
for (int w = bitmap.colors.GetLength(0) - 1; w >= 0; w--)
{
for (int h = 0; h < bitmap.colors.GetLength(1); h++)
{
System.Drawing.Color color = bitmap.GetPixel(w, h);
if (color.A > 10)
{
right = w;
return right;
}
}
}
return right;
}
#region --- 主线程纹理处理 (Main Thread Texture Creation) ---
private static int CutRight(BitmapHelper bitmap, int down)
{
for (int h = bitmap.colors.GetLength(1) - 1; h >= 0; h--)
{
for (int w = 0; w < bitmap.colors.GetLength(0); w++)
// 这个方法由 GameTextureManagerRunner 在主线程的 Update 中调用
public static void CreateTextureFromResource(PictureResource pic)
{
System.Drawing.Color color = bitmap.GetPixel(w, h);
if (color.A > 10)
{
down = h;
return down;
}
}
}
return down;
}
Texture2D texture = null;
private static int CutLeft(BitmapHelper bitmap, int up)
{
for (int h = 0; h < bitmap.colors.GetLength(1); h++)
// 步骤1: 加载原始图像数据
if (pic.rawData != null && pic.rawData.Length > 0)
{
for (int w = 0; w < bitmap.colors.GetLength(0); w++)
// 使用LoadImage将字节数组解码为纹理
// 它会自动处理PNG/JPG格式
// 创建一个临时的2x2纹理,LoadImage会自动调整其大小
Texture2D tempTexture = new Texture2D(2, 2);
if (tempTexture.LoadImage(pic.rawData))
{
System.Drawing.Color color = bitmap.GetPixel(w, h);
if (color.A > 10)
// 步骤2: 根据类型进行图像处理
switch (pic.type)
{
up = h;
return up;
}
}
}
return up;
}
case GameTextureType.card_picture:
// 卡图直接使用,无需额外处理
texture = tempTexture;
// 因为我们直接用了tempTexture,所以不要销毁它
break;
private static void CutTop(
BitmapHelper bitmap,
out int left,
out int right,
out int up,
out int down
)
{
///////切边算法
left = 0;
right = bitmap.colors.GetLength(0);
up = 0;
down = bitmap.colors.GetLength(1);
for (int w = 0; w < bitmap.colors.GetLength(0); w++)
{
for (int h = 0; h < bitmap.colors.GetLength(1); h++)
{
System.Drawing.Color color = bitmap.GetPixel(w, h);
if (color.A > 10)
{
left = w;
return;
}
}
}
}
case GameTextureType.card_feature:
texture = ProcessFeatureTexture(pic, tempTexture);
Destroy(tempTexture); // 销毁临时纹理
break;
private static void ProcessingVerticleDrawing(PictureResource pic)
{
try
{
string path = "picture/closeup/" + pic.code.ToString() + ".png";
if (!File.Exists(path))
{
if (Program.ANDROID_API_N)
{
path = "picture/card/" + pic.code.ToString() + ".png";
if (!File.Exists(path))
{
path = "picture/card/" + pic.code.ToString() + ".jpg";
}
bool Iam8 = false;
if (!File.Exists(path))
{
Iam8 = true;
path = "expansions/pics/" + pic.code.ToString() + ".jpg";
}
if (!File.Exists(path))
{
Iam8 = true;
path = "pics/" + pic.code.ToString() + ".jpg";
}
if (!File.Exists(path))
{
Iam8 = true;
path = "picture/cardIn8thEdition/" + pic.code.ToString() + ".jpg";
case GameTextureType.card_verticle_drawing:
texture = ProcessVerticleDrawingTexture(pic, tempTexture);
Destroy(tempTexture); // 销毁临时纹理
break;
}
LoadCloseupFromCardPicture(pic, path, Iam8);
}
else
{
path = "picture/null.png";
byte[] data;
using (FileStream file = new FileStream(path, FileMode.Open, FileAccess.Read))
{
file.Seek(0, SeekOrigin.Begin);
data = new byte[file.Length];
file.Read(data, 0, (int)file.Length);
Debug.LogWarning("LoadImage failed for card code: " + pic.code);
Destroy(tempTexture);
}
}
pic.data = data;
if (!loadedList.ContainsKey(hashPic(pic.code, pic.type)))
// 步骤3: 处理加载失败或无数据的情况
if (texture == null)
{
loadedList.Add(hashPic(pic.code, pic.type), pic);
}
}
}
else
if (pic.type == GameTextureType.card_picture)
{
LoadCloseupPicture(pic, path);
texture = (pic.code > 0) ? unknown : myBack;
}
}
catch (Exception e)
else
{
Debug.Log("e 3" + e.ToString());
// 对于feature等,失败时返回一个完全透明的小纹理
texture = N;
}
}
private static void LoadCloseupFromCardPicture(PictureResource pic, string path, bool Iam8)
{
try
{
if (!File.Exists(path))
// 步骤4: 完成并缓存
pic.finalTexture = texture;
pic.ClearIntermediateData(); // 释放中间数据内存
UInt64 key = hashPic(pic.code, pic.type);
lock (_lock)
{
path = "picture/null.png";
}
if (!File.Exists(path))
if (!_loadedCache.ContainsKey(key))
{
path = "textures/unknown.jpg"; //YGOMobile Paths
_loadedCache.Add(key, pic);
}
pic.hashed_data = getCuttedPic(path, pic.pCard, Iam8);
softVtype(pic, 0.5f);
pic.k = 1;
if (!loadedList.ContainsKey(hashPic(pic.code, pic.type)))
else
{
loadedList.Add(hashPic(pic.code, pic.type), pic);
// 如果因为某些原因(例如,下载失败后重试)已经存在,则更新它
_loadedCache[key] = pic;
}
}
catch { }
}
private static void LoadCloseupPicture(PictureResource pic, string path)
{
try
{
if (Program.ANDROID_API_N)
{
BitmapHelper bitmap = new BitmapHelper(path);
int left;
int right;
int up;
int down;
CutTop(bitmap, out left, out right, out up, out down);
up = CutLeft(bitmap, up);
down = CutRight(bitmap, down);
right = CutButton(bitmap, right);
int width = right - left;
int height = down - up;
pic.hashed_data = new float[width, height, 4];
for (int w = 0; w < width; w++)
{
for (int h = 0; h < height; h++)
// 从Unity的Texture2D中裁剪出需要的区域
private static RectInt GetCardArtRect(PictureResource pic, int texWidth, int texHeight)
{
System.Drawing.Color color = bitmap.GetPixel(left + w, up + h);
pic.hashed_data[w, height - h - 1, 0] = (float)color.R / 255f;
pic.hashed_data[w, height - h - 1, 1] = (float)color.G / 255f;
pic.hashed_data[w, height - h - 1, 2] = (float)color.B / 255f;
pic.hashed_data[w, height - h - 1, 3] = (float)color.A / 255f;
}
}
float wholeUNalpha = 0;
for (int w = 0; w < width; w++)
{
if (pic.hashed_data[w, 0, 3] > 0.1f)
// 根据原代码的比例进行裁剪
// 注意:原代码的路径有好几种,对应不同尺寸的卡图,这里简化为一种常见比例
// 你可能需要根据实际卡图源的尺寸来微调这些比值
// 以YGOPRO标准卡图尺寸 177x254 为例
float ratioW = (float)texWidth / 177f;
float ratioH = (float)texHeight / 254f;
int x, y, width, height;
if (pic.pCard) // 灵摆
{
wholeUNalpha += ((float)Math.Abs(w - width / 2)) / ((float)(width / 2));
x = (int)(16f * ratioW);
width = (int)((162f - 16f) * ratioW);
y = (int)((254f - 158f) * ratioH); // Y坐标在Unity中从下往上
height = (int)((158f - 50f) * ratioH);
}
if (pic.hashed_data[w, height - 1, 3] > 0.1f)
else // 普通
{
wholeUNalpha += 1;
x = (int)(26f * ratioW);
width = (int)((152f - 26f) * ratioW);
y = (int)((254f - 180f) * ratioH); // Y坐标在Unity中从下往上
height = (int)((180f - 55f) * ratioH);
}
// 边界检查,防止计算出的区域超出纹理范围
x = Math.Max(0, x);
y = Math.Max(0, y);
width = Math.Min(width, texWidth - x);
height = Math.Min(height, texHeight - y);
return new RectInt(x, y, width, height);
}
for (int h = 0; h < height; h++)
private static Texture2D ProcessFeatureTexture(PictureResource pic, Texture2D source)
{
if (pic.hashed_data[0, h, 3] > 0.1f)
RectInt cropRect = GetCardArtRect(pic, source.width, source.height);
if (cropRect.width <= 0 || cropRect.height <= 0) return null;
Color32[] pixels = source.GetPixels32();
int sourceWidth = source.width;
int newWidth = cropRect.width;
int newHeight = cropRect.height;
Color32[] newPixels = new Color32[newWidth * newHeight];
// 复制裁剪区域的像素
for (int row = 0; row < newHeight; row++)
{
wholeUNalpha += 1;
}
if (pic.hashed_data[width - 1, h, 3] > 0.1f)
for (int col = 0; col < newWidth; col++)
{
wholeUNalpha += 1;
int sourceIndex = (cropRect.y + row) * sourceWidth + (cropRect.x + col);
int destIndex = row * newWidth + col;
newPixels[destIndex] = pixels[sourceIndex];
}
}
if (wholeUNalpha >= ((width + height) * 0.5f * 0.12f))
{
softVtype(pic, 0.7f);
}
caculateK(pic);
}
else
// 应用边缘淡出效果
float fadeMargin = 40f;
for (int y = 0; y < newHeight; y++)
{
byte[] data;
using (FileStream file = new FileStream(path, FileMode.Open, FileAccess.Read))
for (int x = 0; x < newWidth; x++)
{
file.Seek(0, SeekOrigin.Begin);
data = new byte[file.Length];
file.Read(data, 0, (int)file.Length);
}
pic.data = data;
}
float alpha = 1.0f;
// 计算到四条边的距离
float distToLeft = x;
float distToRight = newWidth - 1 - x;
float distToTop = newHeight - 1 - y;
float distToBottom = y;
if (!loadedList.ContainsKey(hashPic(pic.code, pic.type)))
{
loadedList.Add(hashPic(pic.code, pic.type), pic);
if (distToLeft < fadeMargin) alpha = Mathf.Min(alpha, distToLeft / fadeMargin);
if (distToRight < fadeMargin) alpha = Mathf.Min(alpha, distToRight / fadeMargin);
if (distToTop < fadeMargin) alpha = Mathf.Min(alpha, distToTop / fadeMargin);
if (distToBottom < fadeMargin) alpha = Mathf.Min(alpha, distToBottom / fadeMargin);
int index = y * newWidth + x;
newPixels[index].a = (byte)(newPixels[index].a * alpha * 0.7f); // 乘以0.7f以匹配原效果
}
}
catch { }
// 创建最终纹理
Texture2D finalTexture = new Texture2D(newWidth, newHeight, TextureFormat.RGBA32, false);
finalTexture.SetPixels32(newPixels);
finalTexture.Apply();
// 计算k值(基于alpha不为0的最后一行)
CalculateKValue(pic, newPixels, newWidth, newHeight);
return finalTexture;
}
private static void softVtype(PictureResource pic, float si)
private static Texture2D ProcessVerticleDrawingTexture(PictureResource pic, Texture2D source)
{
int width = pic.hashed_data.GetLength(0);
int height = pic.hashed_data.GetLength(1);
int size = (int)(height * si);
// 对于立绘,我们通常直接使用整个 PNG,但要应用软边
int width = source.width;
int height = source.height;
Color32[] pixels = source.GetPixels32();
// 应用软边效果 (类似原softVtype)
float softenSizeRatio = 0.7f;
int size = (int)(height * softenSizeRatio);
int empWidth = (width - size) / 2;
int empHeight = (height - size) / 2;
int right = width - empWidth;
int buttom = height - empHeight;
float dui = (float)Math.Sqrt((width / 2) * (width / 2) + (height / 2) * (height / 2));
for (int w = 0; w < width; w++)
for (int y = 0; y < height; y++)
{
for (int h = 0; h < height; h++)
for (int x = 0; x < width; x++)
{
float a = pic.hashed_data[w, h, 3];
float alpha = 1.0f;
float distToLeft = x;
float distToRight = width - 1 - x;
float distToTop = height - 1 - y;
float distToBottom = y;
if (h < height / 2)
{
float l = (float)
Math.Sqrt(
(width / 2 - w) * (width / 2 - w) + (height / 2 - h) * (height / 2 - h)
);
l -= width * 0.3f;
if (l < 0)
if (empWidth > 0)
{
l = 0;
if (distToLeft < empWidth) alpha = Mathf.Min(alpha, distToLeft / empWidth);
if (distToRight < empWidth) alpha = Mathf.Min(alpha, distToRight / empWidth);
}
float alpha = 1f - l / (0.6f * (dui - width * 0.3f));
if (alpha < 0)
if (empHeight > 0)
{
alpha = 0;
}
if (a > alpha)
a = alpha;
if (distToTop < empHeight) alpha = Mathf.Min(alpha, distToTop / empHeight);
if (distToBottom < empHeight) alpha = Mathf.Min(alpha, distToBottom / empHeight);
}
if (w < empWidth)
if (a > ((float)w) / (float)empWidth)
a = ((float)w) / (float)empWidth;
if (h < empHeight)
if (a > ((float)h) / (float)empHeight)
a = ((float)h) / (float)empHeight;
if (w > right)
if (a > 1f - ((float)(w - right)) / (float)empWidth)
a = 1f - ((float)(w - right)) / (float)empWidth;
if (h > buttom)
if (a > 1f - ((float)(h - buttom)) / (float)empHeight)
a = 1f - ((float)(h - buttom)) / (float)empHeight;
pic.hashed_data[w, h, 3] = a;
}
int index = y * width + x;
pixels[index].a = (byte)(pixels[index].a * alpha);
}
}
private static void ProcessingCardPicture(PictureResource pic)
{
try
{
string path = "picture/card/" + pic.code.ToString() + ".png";
if (!File.Exists(path))
{
path = "picture/card/" + pic.code.ToString() + ".jpg";
}
if (!File.Exists(path))
{
path = "expansions/pics/" + pic.code.ToString() + ".jpg";
}
if (!File.Exists(path))
{
path = "pics/" + pic.code.ToString() + ".jpg";
}
if (!File.Exists(path))
{
path = "picture/cardIn8thEdition/" + pic.code.ToString() + ".jpg";
}
if (!File.Exists(path) && pic.code != 0 && AutoPicDownload)
{
// //YGOMobile (177x254)
// df.Download(
// "http://cdn01.moestart.com/images/ygopro-images-zh-CN/"
// + pic.code.ToString()
// + ".jpg",
// "picture/card/" + pic.code.ToString() + ".jpg"
// );
// path = "picture/card/" + pic.code.ToString() + ".jpg";
string url = "https://cdn02.moecube.com:444/images/ygopro-images-zh-CN/" + pic.code.ToString() + ".jpg";
// string url = "http://cdn01.moestart.com/images/ygopro-images-zh-CN/" + pic.code.ToString() + ".jpg";
string finalPath = "picture/card/" + pic.code.ToString() + ".jpg";
Texture2D finalTexture = new Texture2D(width, height, TextureFormat.RGBA32, false);
finalTexture.SetPixels32(pixels);
finalTexture.Apply();
// Enqueue the request. The runner on the main thread will pick it up.
lock (downloadRequestQueue)
{
downloadRequestQueue.Enqueue(new DownloadRequest
{
Url = url,
FilePath = finalPath,
PicResource = pic
});
}
}
else
{
LoadCardPicture(pic, path);
}
}
catch (Exception e)
{
Debug.Log("e 2" + e.ToString());
}
CalculateKValue(pic, pixels, width, height);
return finalTexture;
}
private static void LoadCardPicture(PictureResource pic, string path)
private static void CalculateKValue(PictureResource pic, Color32[] pixels, int width, int height)
{
try
int lastVisibleRow = 0;
for (int y = height - 1; y >= 0; y--)
{
if (!File.Exists(path))
{
if (pic.code > 0)
{
pic.u_data = unknown;
}
else
int opaquePixelsInRow = 0;
for (int x = 0; x < width; x++)
{
pic.u_data = myBack;
}
if (!loadedList.ContainsKey(hashPic(pic.code, pic.type)))
if (pixels[y * width + x].a > 12) // 0.05 * 255 ≈ 12
{
loadedList.Add(hashPic(pic.code, pic.type), pic);
opaquePixelsInRow++;
}
}
else
{
byte[] data;
using (FileStream file = new FileStream(path, FileMode.Open, FileAccess.Read))
{
file.Seek(0, SeekOrigin.Begin);
data = new byte[file.Length];
file.Read(data, 0, (int)file.Length);
}
pic.data = data;
if (!loadedList.ContainsKey(hashPic(pic.code, pic.type)))
if (opaquePixelsInRow * 5 > width)
{
loadedList.Add(hashPic(pic.code, pic.type), pic);
lastVisibleRow = y;
break;
}
}
pic.k = (float)(lastVisibleRow + 1) / (float)height;
pic.k = Mathf.Clamp(pic.k, 0.1f, 1.0f);
}
catch (Exception e)
{
Debug.Log("e 2" + e.ToString());
}
}
#endregion
#region --- 工具方法和初始化 ---
private static UInt64 hashPic(long code, GameTextureType type)
{
return (((UInt64)type << 32) | ((UInt64)code));
return ((UInt64)type << 32) | (UInt64)(uint)code;
}
public static Texture2D get(long code, GameTextureType type, Texture2D nullReturnValue = null)
{
try
private static void Destroy(UnityEngine.Object obj)
{
PictureResource r;
if (loadedList.TryGetValue(hashPic(code, type), out r))
if (Application.isPlaying)
{
Texture2D re = null;
if (r.u_data != null)
{
if (r.u_data == myBack)
{
return nullReturnValue;
UnityEngine.Object.Destroy(obj);
}
else
{
return r.u_data;
// 在编辑器模式下且非播放状态时使用 DestroyImmediate
UnityEngine.Object.DestroyImmediate(obj);
}
}
if (r.data != null)
{
re = new Texture2D(400, 600);
re.LoadImage(r.data);
r.u_data = re;
return re;
}
if (r.hashed_data != null)
// 省略UI部分和静态资源定义,它们与原版类似,但get(string)方法已优化
public static Texture2D myBack, opBack, unknown, attack, negated, bar, exBar, lp, time, L, R, Chain, Mask, N;
public static Texture2D nt, bp, ep, mp1, mp2, dp, sp, phase, rs, ts, LINK, LINKm;
public static bool AutoPicDownload;
public static UnityEngine.Color chainColor = UnityEngine.Color.white;
public static bool uiLoaded = false;
private static Dictionary<string, Texture2D> _uiCache = new Dictionary<string, Texture2D>();
public static Texture2D get(string name)
{
int width = r.hashed_data.GetLength(0);
int height = r.hashed_data.GetLength(1);
UnityEngine.Color[] cols = new UnityEngine.Color[width * height];
re = new Texture2D(width, height);
for (int h = 0; h < height; h++)
if (!uiLoaded)
{
for (int w = 0; w < width; w++)
uiLoaded = true;
string uiPath = "textures/ui";
if (Directory.Exists(uiPath))
{
cols[h * width + w] = new UnityEngine.Color(
r.hashed_data[w, h, 0],
r.hashed_data[w, h, 1],
r.hashed_data[w, h, 2],
r.hashed_data[w, h, 3]
);
}
}
re.SetPixels(0, 0, width, height, cols);
re.Apply();
r.u_data = re;
return re;
}
}
else
FileInfo[] fileInfos = (new DirectoryInfo(uiPath)).GetFiles("*.png");
foreach (var fileInfo in fileInfos)
{
if (!addedMap.ContainsKey(hashPic(code, type)))
string key = Path.GetFileNameWithoutExtension(fileInfo.Name);
if (!_uiCache.ContainsKey(key))
{
PictureResource a = new PictureResource(type, code, nullReturnValue);
bLock = true;
waitLoadStack.Push(a);
bLock = false;
addedMap.Add((UInt64)type << 32 | (UInt64)code, true);
// 这里假设UIHelper.getTexture2D是同步加载,如果也是异步需要相应修改
Texture2D tex = UIHelper.getTexture2D(fileInfo.FullName);
_uiCache.Add(key, tex);
}
}
}
catch (Exception e)
{
Debug.Log("BIGERROR1:" + e.ToString());
}
return null;
Texture2D result = null;
_uiCache.TryGetValue(name, out result);
return result;
}
public static float getK(long code, GameTextureType type)
// 清理方法也需要加锁
public static void clearUnloaded()
{
float ret = 1;
PictureResource r;
if (loadedList.TryGetValue(hashPic(code, type), out r))
lock (_lock)
{
ret = r.k;
_requestStack.Clear();
_requestedSet.Clear();
}
return ret;
}
public static bool uiLoaded = false;
public static Texture2D get(string name)
public static void clearAll()
{
if (uiLoaded == false)
lock (_lock)
{
uiLoaded = true;
FileInfo[] fileInfos = (new DirectoryInfo("textures/ui")).GetFiles(); //YGOMobile Paths
for (int i = 0; i < fileInfos.Length; i++)
_requestStack.Clear();
_mainThreadApplyQueue.Clear();
foreach (var pair in _loadedCache)
{
if (fileInfos[i].Name.Length > 4)
if (pair.Value.finalTexture != null && pair.Value.finalTexture != N)
{
if (fileInfos[i].Name.Substring(fileInfos[i].Name.Length - 4, 4) == ".png")
// 确保不销毁共享的静态纹理
if (pair.Value.finalTexture != unknown && pair.Value.finalTexture != myBack)
{
UIPictureResource r = new UIPictureResource();
r.name = fileInfos[i].Name.Substring(0, fileInfos[i].Name.Length - 4);
r.data = UIHelper.getTexture2D("textures/ui/" + fileInfos[i].Name); //YGOMobile Paths
allUI.Add(r);
Destroy(pair.Value.finalTexture);
}
}
}
_loadedCache.Clear();
_requestedSet.Clear();
}
Texture2D re = null;
for (int i = 0; i < allUI.size; i++)
{
if (allUI[i].name == name)
{
re = allUI[i].data;
break;
}
}
if (re == null) { }
return re;
}
public static UnityEngine.Color chainColor = UnityEngine.Color.white;
internal static void Initialize()
// 初始化方法
public static void InitializeAssets()
{
if (IsInitialized) return;
attack = UIHelper.getTexture2D("textures/attack.png"); //YGOMobile Paths
myBack = UIHelper.getTexture2D("textures/cover.jpg"); //YGOMobile Paths
opBack = UIHelper.getTexture2D("textures/cover2.jpg"); //YGOMobile Paths
......@@ -1122,27 +696,33 @@ public class GameTextureManager
rs = UIHelper.getTexture2D("textures/duel/phase/rs.png"); //YGOMobile Paths
ts = UIHelper.getTexture2D("textures/duel/phase/ts.png"); //YGOMobile Paths
N = new Texture2D(10, 10);
for (int i = 0; i < 10; i++)
{
for (int a = 0; a < 10; a++)
{
N.SetPixel(i, a, new UnityEngine.Color(0, 0, 0, 0));
}
}
// 创建一个1x1的透明纹理,用于失败时的占位符
N = new Texture2D(1, 1, TextureFormat.RGBA32, false);
N.SetPixel(0, 0, new Color(0, 0, 0, 0));
N.Apply();
try
{
ColorUtility.TryParseHtmlString(
File.ReadAllText("textures/duel/chainColor.txt"),
out chainColor
); //YGOMobile Paths
ColorUtility.TryParseHtmlString(File.ReadAllText("textures/duel/chainColor.txt"), out chainColor);
}
catch (Exception) { }
if (main_thread == null)
// 标记为初始化完成
IsInitialized = true;
Debug.Log("[GameTextureManager] Assets initialized successfully.");
}
// NEW: 这个方法现在只负责启动后台线程
public static void StartBackgroundThread()
{
if (_ioThread == null)
{
main_thread = new Thread(thread_run);
main_thread.Start();
_isRunning = true;
_ioThread = new Thread(IoThreadRun);
_ioThread.IsBackground = true; // 确保主程序退出时线程也退出
_ioThread.Start();
Debug.Log("[GameTextureManager] Background I/O thread started.");
}
}
#endregion
}
\ No newline at end of file
using System.Collections;
using UnityEngine;
/// <summary>
/// 这是一个 MonoBehaviour 帮助器,专为静态的 GameTextureManager 服务。
/// 它的职责是在主线程上执行任务,例如轮询下载队列和启动协程。
/// 这个类的实例由 GameTextureManager 通过 [RuntimeInitializeOnLoadMethod] 自动创建。
/// </summary>
public class GameTextureManagerRunner : MonoBehaviour
{
void Update()
{
// 在主线程的每一帧检查是否有待处理的下载请求
if (GameTextureManager.HasDownloadRequests())
{
// 从 GameTextureManager 获取一个请求
var request = GameTextureManager.GetNextDownloadRequest();
// 使用这个 MonoBehaviour 实例来启动下载协程
StartCoroutine(GameTextureManager.DownloadAndProcessFile(request));
}
}
}
fileFormatVersion: 2
guid: cb7d20dd5a7074bc089a6c75c87e34da
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
......@@ -15,12 +15,22 @@ public class UnityFileDownloader
Action<bool> onComplete,
Action<float> onProgress = null
)
{
// 确保目录存在
try
{
string directoryPath = Path.GetDirectoryName(filePath);
if (!Directory.Exists(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}
}
catch (Exception e)
{
Debug.LogError("Failed to create directory for " + filePath + ". Error: " + e.Message);
if (onComplete != null) onComplete.Invoke(false);
yield break; // 提前退出协程
}
string tempFilePath = filePath + ".tmp";
if (File.Exists(tempFilePath))
......@@ -28,6 +38,8 @@ public class UnityFileDownloader
File.Delete(tempFilePath);
}
Debug.Log(string.Format("Downloading: {0} -> {1}", url, filePath));
using (UnityWebRequest uwr = new UnityWebRequest(url, UnityWebRequest.kHttpVerbGET))
{
uwr.downloadHandler = new DownloadHandlerFile(tempFilePath);
......@@ -80,15 +92,15 @@ public class UnityFileDownloader
switch (extension)
{
case ".png":
return 20;
return 40;
case ".jpg":
return 20;
return 40;
case ".cdb":
return 50;
return 80;
case ".conf":
return 20;
return 40;
default:
return 20;
return 40;
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment